Global Video Filter Overlay

Global Video Filter Overlay enhances any HTML5 video in your browser with real-time color grading, sharpening, HDR and LUTs. It provides instant profile switching and on-video controls to improve visual quality without re-encoding or downloads.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Global Video Filter Overlay
// @name:de      Global Video Filter Overlay
// @namespace    gvf
// @author       Freak288
// @version      1.8.7
// @description  Global Video Filter Overlay enhances any HTML5 video in your browser with real-time color grading, sharpening, HDR and LUTs. It provides instant profile switching and on-video controls to improve visual quality without re-encoding or downloads.
// @description:de  Globale Video Filter Overlay verbessert jedes HTML5-Video in Ihrem Browser mit Echtzeit-Farbkorrektur, Schärfung, HDR und LUTs. Es bietet sofortiges Profilwechseln und Steuerelemente direkt im Video, um die Bildqualität ohne Neucodierung oder Downloads zu verbessern.
// @match        *://*/*
// @run-at       document-idle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addValueChangeListener
// @grant        GM_info
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// @connect      raw.githubusercontent.com
// @connect      github.com
// @iconURL      https://raw.githubusercontent.com/nextscript/Globale-Video-Filter-Overlay/refs/heads/main/logomes.png
// ==/UserScript==

(function () {
    'use strict';
    if (typeof window === 'undefined') return;

    // -------------------------
    // GVF SVG Import Page Handler
    // -------------------------
    (function handleImportPage() {
        try {
            const host = (location.hostname || '').toLowerCase();
            const isImportPage = host === 'svg.ts3x.cc' || document.documentElement.hasAttribute('data-gvf-import-page');
            if (!isImportPage) return;
            window.__GVF_IMPORT_PAGE__ = true;
            window.GVF_DETECTED = true;
            // Dispatch event so the page can detect GVF regardless of timing
            try {
                document.dispatchEvent(new CustomEvent('gvf-detected'));
            } catch (_) {}

            function wireButtons() {
                document.querySelectorAll('[data-gvf-install]').forEach(btn => {
                    if (btn.__gvfWired) return;
                    btn.__gvfWired = true;
                    btn.addEventListener('click', () => {
                        try {
                            const entry = JSON.parse(btn.getAttribute('data-gvf-install') || '{}');
                            if (!entry.label || !entry.code) { alert('Invalid entry.'); return; }

                            let codes = [];
                            try {
                                const raw = GM_getValue('gvf_custom_svg_codes', null);
                                if (raw) { const p = JSON.parse(raw); if (Array.isArray(p)) codes = p; }
                            } catch (_) {}

                            const exists = codes.find(e => e.label === entry.label);
                            if (exists) {
                                if (!confirm(`"${entry.label}" already exists. Overwrite?`)) return;
                                exists.code = entry.code;
                                exists.enabled = true;
                            } else {
                                codes.push({ id: 'csvg_' + Date.now(), label: entry.label, code: entry.code, enabled: true });
                            }

                            GM_setValue('gvf_custom_svg_codes', JSON.stringify(codes));

                            const orig = btn.innerHTML;
                            btn.innerHTML = '✓ Installed!';
                            btn.disabled = true;
                            btn.classList.remove('btn-success');
                            btn.classList.add('btn-outline-success');
                            setTimeout(() => {
                                btn.innerHTML = orig;
                                btn.disabled = false;
                                btn.classList.add('btn-success');
                                btn.classList.remove('btn-outline-success');
                            }, 2000);
                        } catch (e) { alert('Install failed: ' + e.message); }
                    });
                });
            }

            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', wireButtons, { once: true });
            } else {
                wireButtons();
            }
            new MutationObserver(wireButtons).observe(document.documentElement, { childList: true, subtree: true });
        } catch (_) {}
    })();

    if (window.__GVF_IMPORT_PAGE__) return;
    if (window.__GLOBAL_VIDEO_FILTER__) return;
    window.__GLOBAL_VIDEO_FILTER__ = true;

    // -------------------------
    // IDs / Constants
    // -------------------------
    const STYLE_ID = 'global-video-filter-style';
    const SVG_ID = 'global-video-filter-svg';
    const GPU_SVG_ID = 'gvf-gpu-svg';
    const GPU_GAIN_FILTER_ID = 'gvf-gpu-gain-filter';
    const GPU_PROFILE_FILTER_ID = 'gvf-gpu-profile-filter';
    const WEBGL_CANVAS_ID = 'gvf-webgl-canvas';
    const WEBGL_WRAPPER_ATTR = 'data-gvf-webgl-wrapper';
    const RECORDING_HUD_ID = 'gvf-recording-hud';
    const CONFIG_MENU_ID = 'gvf-config-menu';
    const LUT_CONFIG_MENU_ID = 'gvf-lut-config-menu';
    const NOTIFICATION_ID = 'gvf-profile-notification';
    const svgNS = 'http://www.w3.org/2000/svg';

    // Hotkeys
    const HDR_TOGGLE_KEY = 'p';
    const PROF_TOGGLE_KEY = 'c';
    const GRADE_HUD_KEY = 'g';
    const IO_HUD_KEY = 'i';
    const AUTO_KEY = 'a';
    const SCOPES_KEY = 's';
    const GPU_MODE_KEY = 'x';
    const PROFILE_CYCLE_KEY = 'q'; // Shift+Q for profile cycling

    // -------------------------
    // Throttling for less computationally intensive operations
    // -------------------------
    let lastRenderTime = 0;
    const RENDER_THROTTLE = 41; // ~24 FPS cap to reduce GPU load

    function render() {
        if (renderMode === 'gpu') {
            applyGpuFilter();
        } else {
            regenerateSvgImmediately();
        }
    }

    function throttledRender(timestamp) {
        if (timestamp - lastRenderTime >= RENDER_THROTTLE) {
            lastRenderTime = timestamp;
            render();
        }
        requestAnimationFrame(throttledRender);
    }

    function isVideoRenderable(video) {
        if (!video) return false;
        if (video.readyState < 2 || video.videoWidth === 0 || video.videoHeight === 0) return false;
        if (video.paused || video.ended) return false;
        const cs = window.getComputedStyle(video);
        if (!cs || cs.display === 'none' || cs.visibility === 'hidden') return false;
        const r = video.getBoundingClientRect();
        if (!r || r.width < 40 || r.height < 40) return false;
        if (r.bottom <= 0 || r.right <= 0) return false;
        if (r.top >= (window.innerHeight || 0) || r.left >= (window.innerWidth || 0)) return false;
        return true;
    }

    function isHudHostTabActive() {
        try {
            if (document.hidden) return false;
            if (document.visibilityState && document.visibilityState !== 'visible') return false;
            if (typeof document.hasFocus === 'function' && !document.hasFocus()) {
                // Don't hide HUD if a GVF element currently has focus (e.g. slider, textarea, button)
                const focused = document.activeElement;
                if (!focused || !focused.closest(
                    '.gvf-video-overlay-io, .gvf-video-overlay-grade, .gvf-video-overlay, [id^="gvf-"]'
                )) return false;
            }
        } catch (_) { }
        return true;
    }

    function isHudVideoVisible(video) {
        if (!video) return false;
        if (!isHudHostTabActive()) return false;
        if (video.readyState < 1) return false;
        const cs = window.getComputedStyle(video);
        if (!cs || cs.display === 'none' || cs.visibility === 'hidden') return false;
        const r = video.getBoundingClientRect();
        if (!r || r.width < 40 || r.height < 40) return false;
        if (r.bottom <= 0 || r.right <= 0) return false;
        if (r.top >= (window.innerHeight || 0) || r.left >= (window.innerWidth || 0)) return false;
        return true;
    }

    function getHudPrimaryVideo() {
        if (!isHudHostTabActive()) return null;
        const videos = Array.from(document.querySelectorAll('video'));
        let best = null;
        let bestArea = 0;
        for (const video of videos) {
            if (!isHudVideoVisible(video)) continue;
            const r = video.getBoundingClientRect();
            const area = Math.max(0, r.width) * Math.max(0, r.height);
            if (area > bestArea) {
                bestArea = area;
                best = video;
            }
        }
        return best;
    }

    function getGpuPrimaryVideo() {
        const videos = Array.from(document.querySelectorAll('video'));
        let best = null;
        let bestArea = 0;
        for (const video of videos) {
            if (!isVideoRenderable(video)) continue;
            const r = video.getBoundingClientRect();
            const area = Math.max(0, r.width) * Math.max(0, r.height);
            if (area > bestArea) {
                bestArea = area;
                best = video;
            }
        }
        return best;
    }

    // -------------------------
    // LOG + DEBUG SWITCH
    // -------------------------
    let logs = true;    // console logs
    let debug = false;    // visual debug (Auto-dot) - DEFAULT FALSE

    // -------------------------
    // CSS.escape Polyfill
    // -------------------------
    const cssEscape = (s) => {
        try {
            if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(String(s));
        } catch (_) { }
        return String(s).replace(/[^a-zA-Z0-9_-]/g, (m) => '\\' + m);
    };

    // GM keys
    const K = {
        enabled: 'gvf_enabled',
        moody: 'gvf_moody',
        teal: 'gvf_teal',
        vib: 'gvf_vib',
        icons: 'gvf_icons',

        SL: 'gvf_sl',
        SR: 'gvf_sr',
        BL: 'gvf_bl',
        WL: 'gvf_wl',
        DN: 'gvf_dn',
        EDGE: 'gvf_edge',

        HDR: 'gvf_hdr',
        HDR_LAST: 'gvf_hdr_last',

        PROF: 'gvf_profile',

        G_HUD: 'gvf_g_hud',
        I_HUD: 'gvf_i_hud',
        S_HUD: 'gvf_s_hud',

        RENDER_MODE: 'gvf_render_mode',

        U_CONTRAST: 'gvf_u_contrast',
        U_BLACK: 'gvf_u_black',
        U_WHITE: 'gvf_u_white',
        U_HIGHLIGHTS: 'gvf_u_highlights',
        U_SHADOWS: 'gvf_u_shadows',
        U_SAT: 'gvf_u_saturation',
        U_VIB: 'gvf_u_vibrance',
        U_SHARP: 'gvf_u_sharpen',
        U_GAMMA: 'gvf_u_gamma',
        U_GRAIN: 'gvf_u_grain',
        U_HUE: 'gvf_u_hue',

        U_R_GAIN: 'gvf_u_r_gain',
        U_G_GAIN: 'gvf_u_g_gain',
        U_B_GAIN: 'gvf_u_b_gain',

        AUTO_ON: 'gvf_auto_on',
        AUTO_STRENGTH: 'gvf_auto_strength',
        AUTO_LOCK_WB: 'gvf_auto_lock_wb',
        NOTIFY: 'gvf_notify',

        LOGS: 'gvf_logs',
        DEBUG: 'gvf_debug',

        // Color blindness filter
        CB_FILTER: 'gvf_cb_filter',

        // Profile Management
        ACTIVE_USER_PROFILE: 'gvf_active_user_profile',
        USER_PROFILES: 'gvf_user_profiles',
        USER_PROFILES_REV: 'gvf_user_profiles_rev',

        // LUT Profile Management
        LUT_ACTIVE_PROFILE: 'gvf_lut_active_profile',
        LUT_PROFILES: 'gvf_lut_profiles',
        LUT_PROFILES_REV: 'gvf_lut_profiles_rev',
        LUT_GROUPS: 'gvf_lut_groups',

        USER_PROFILE_MANAGER_POS: 'gvf_user_profile_manager_pos',
        LUT_PROFILE_MANAGER_POS: 'gvf_lut_profile_manager_pos',

        // Custom SVG filter codes
        CUSTOM_SVG_CODES: 'gvf_custom_svg_codes'
    };

    // -------------------------
    // Helpers
    // -------------------------
    const clamp = (n, a, b) => Math.min(b, Math.max(a, n));
    const roundTo = (n, step) => Math.round(n / step) * step;
    const snap0 = (n, eps) => (Math.abs(n) <= eps ? 0 : n);
    const nFix = (n, digits = 1) => Number((Number(n) || 0).toFixed(digits));
    const gmGet = (key, fallback) => { try { return GM_getValue(key, fallback); } catch (_) { return fallback; } };
    const gmSet = (key, val) => { try { GM_setValue(key, val); } catch (_) { } };
    const nowMs = () => (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
    const isFirefox = () => { try { return /firefox/i.test(navigator.userAgent || ''); } catch (_) { return false; } };

    // -------------------------
    // Custom SVG Codes  { id, label, code, enabled }
    // -------------------------
    let customSvgCodes = [];

    function loadCustomSvgCodes() {
        try {
            const raw = gmGet(K.CUSTOM_SVG_CODES, null);
            if (raw) {
                const p = JSON.parse(raw);
                if (Array.isArray(p)) { customSvgCodes = p.filter(e => e && e.id); return; }
            }
        } catch (_) {}
        customSvgCodes = [];
    }

    function saveCustomSvgCodes() {
        try { gmSet(K.CUSTOM_SVG_CODES, JSON.stringify(customSvgCodes)); } catch (_) {}
        // Update count badge in HUD if visible
        const badge = document.getElementById('gvf-svg-codes-count');
        if (badge) {
            const ac = customSvgCodes.filter(e => e.enabled).length;
            badge.textContent = customSvgCodes.length ? `${ac}/${customSvgCodes.length} active` : '';
        }
    }

    function parseCustomSvgCode(codeStr) {
        // Manual parser: extract tag name + attributes, build SVGElements via createElementNS.
        // Avoids DOMParser entirely — no CSP/namespace/whitespace issues.
        try {
            const results = [];
            // Match self-closing or open tags: <tagName attr="val" .../>  or <tagName ...>
            const tagRe = /<([a-zA-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*\/?>/g;
            let m;
            while ((m = tagRe.exec(codeStr)) !== null) {
                const tagName = m[1];
                const attrStr = m[2] || '';
                const el = document.createElementNS('http://www.w3.org/2000/svg', tagName);

                // Parse attributes: name="value" or name='value'
                const attrRe = /([a-zA-Z][a-zA-Z0-9_:-]*)\s*=\s*(?:"([\s\S]*?)"|'([\s\S]*?)')/g;
                let am;
                while ((am = attrRe.exec(attrStr)) !== null) {
                    const attrName = am[1];
                    // Normalize whitespace in the value (newlines → space)
                    const attrVal = (am[2] !== undefined ? am[2] : am[3]).replace(/[\r\n\t]+/g, ' ').replace(/ {2,}/g, ' ').trim();
                    el.setAttribute(attrName, attrVal);
                }
                results.push(el);
            }
            return results.length ? results : null;
        } catch (_) { return null; }
    }

    function openCustomSvgModal() {
        const MODAL_ID = 'gvf-custom-svg-modal';
        const existing = document.getElementById(MODAL_ID);
        if (existing) { existing.remove(); return; }

        const modal = document.createElement('div');
        modal.id = MODAL_ID;
        modal.style.cssText = `position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:560px;max-width:96vw;max-height:85vh;background:rgba(18,18,22,0.98);border:2px solid #4a9eff;border-radius:14px;box-shadow:0 20px 60px rgba(0,0,0,0.85);color:#eaeaea;font-family:system-ui,sans-serif;z-index:2147483647;display:flex;flex-direction:column;padding:18px;user-select:none;pointer-events:auto;`;
        stopEventsOn(modal);

        // Header
        const hdr = document.createElement('div');
        hdr.style.cssText = `display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;padding-bottom:10px;border-bottom:2px solid #4a9eff;flex-shrink:0;`;
        const htitle = document.createElement('div');
        htitle.textContent = '⬡ Custom SVG Filter Codes';
        htitle.style.cssText = `font-size:16px;font-weight:900;color:#fff;text-shadow:0 0 8px #4a9eff;`;

        const hbtns = document.createElement('div');
        hbtns.style.cssText = `display:flex;gap:6px;align-items:center;flex-shrink:0;`;

        const libBtn = document.createElement('button');
        libBtn.textContent = '📚 Library';
        libBtn.title = 'Open SVG Filter Library';
        libBtn.style.cssText = `padding:4px 10px;background:rgba(100,180,255,0.18);color:#a0d4ff;border:1px solid rgba(100,180,255,0.45);border-radius:6px;font-size:16px;font-weight:900;cursor:pointer;`;
        libBtn.addEventListener('mouseenter', () => { libBtn.style.background = 'rgba(100,180,255,0.32)'; });
        libBtn.addEventListener('mouseleave', () => { libBtn.style.background = 'rgba(100,180,255,0.18)'; });
        libBtn.addEventListener('click', () => { window.open('https://svg.ts3x.cc/', '_blank'); });

        const hclose = document.createElement('button');
        hclose.textContent = '✕';
        hclose.style.cssText = `
        background: rgba(255, 255, 255, 0.1);
            border: none;
            color: #fff;
            font-size: 20px;
            cursor: pointer;
            width: 36px;
            height: 36px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s;
            border: 1px solid rgba(255,255,255,0.2);`;
        hclose.addEventListener('click', () => modal.remove());

        hbtns.appendChild(libBtn);
        hbtns.appendChild(hclose);
        hdr.appendChild(htitle); hdr.appendChild(hbtns);
        modal.appendChild(hdr);

        // List area
        const listWrap = document.createElement('div');
        listWrap.style.cssText = `overflow-y:auto;max-height:220px;background:rgba(0,0,0,0.3);border-radius:8px;padding:6px;margin-bottom:12px;display:flex;flex-direction:column;gap:6px;flex-shrink:0;`;
        modal.appendChild(listWrap);

        let dragSrcIndex = null;

        function renderList() {
            const scrollTop = listWrap.scrollTop;
            while (listWrap.firstChild) listWrap.removeChild(listWrap.firstChild);
            if (!customSvgCodes.length) {
                const empty = document.createElement('div');
                empty.textContent = 'No entries yet. Add one below.';
                empty.style.cssText = `color:#888;font-size:12px;padding:10px;text-align:center;`;
                listWrap.appendChild(empty);
                return;
            }
            customSvgCodes.forEach((entry, i) => {
                const row = document.createElement('div');
                row.draggable = true;
                row.dataset.idx = String(i);
                row.style.cssText = `display:flex;align-items:center;gap:8px;padding:7px 10px;background:rgba(255,255,255,0.05);border-radius:8px;border:1px solid rgba(255,255,255,0.1);cursor:default;transition:opacity 0.15s,border-color 0.15s;`;

                // Drag handle
                const handle = document.createElement('div');
                handle.textContent = '⠿';
                handle.title = 'Drag to reorder';
                handle.style.cssText = `font-size:14px;color:#666;cursor:grab;flex-shrink:0;line-height:1;padding:0 2px;`;

                // Drag events
                row.addEventListener('dragstart', (e) => {
                    dragSrcIndex = i;
                    e.dataTransfer.effectAllowed = 'move';
                    setTimeout(() => { row.style.opacity = '0.4'; }, 0);
                });
                row.addEventListener('dragend', () => {
                    row.style.opacity = '1';
                    listWrap.querySelectorAll('[data-idx]').forEach(r => {
                        r.style.borderColor = 'rgba(255,255,255,0.1)';
                        r.style.borderStyle = 'solid';
                    });
                });
                row.addEventListener('dragover', (e) => {
                    e.preventDefault();
                    e.dataTransfer.dropEffect = 'move';
                    if (dragSrcIndex !== i) row.style.borderColor = '#4a9eff';
                });
                row.addEventListener('dragleave', () => {
                    row.style.borderColor = 'rgba(255,255,255,0.1)';
                });
                row.addEventListener('drop', (e) => {
                    e.preventDefault();
                    row.style.borderColor = 'rgba(255,255,255,0.1)';
                    if (dragSrcIndex === null || dragSrcIndex === i) return;
                    const moved = customSvgCodes.splice(dragSrcIndex, 1)[0];
                    customSvgCodes.splice(i, 0, moved);
                    dragSrcIndex = null;
                    saveCustomSvgCodes();
                    regenerateSvgImmediately();
                    renderList();
                });

                const chk = document.createElement('input');
                chk.type = 'checkbox';
                chk.checked = !!entry.enabled;
                chk.style.cssText = `width:16px;height:16px;accent-color:#4a9eff;cursor:pointer;flex-shrink:0;`;
                stopEventsOn(chk);
                chk.addEventListener('change', () => {
                    customSvgCodes[i].enabled = chk.checked;
                    saveCustomSvgCodes();
                    regenerateSvgImmediately();
                });

                const lbl = document.createElement('div');
                lbl.textContent = entry.label || 'Untitled';
                lbl.style.cssText = `flex:1;font-size:12px;font-weight:700;color:#d0e8ff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`;

                const editBtn = document.createElement('button');
                editBtn.textContent = '✏';
                editBtn.title = 'Edit';
                editBtn.style.cssText = `padding:3px 8px;background:rgba(100,180,255,0.18);color:#a0d4ff;border:1px solid rgba(100,180,255,0.4);border-radius:5px;font-size:12px;cursor:pointer;`;
                stopEventsOn(editBtn);
                editBtn.addEventListener('click', () => renderEditArea(i));

                const delBtn = document.createElement('button');
                delBtn.textContent = '🗑';
                delBtn.title = 'Delete';
                delBtn.style.cssText = `padding:3px 8px;background:rgba(255,80,80,0.15);color:#ff8080;border:1px solid rgba(255,80,80,0.4);border-radius:5px;font-size:12px;cursor:pointer;`;
                stopEventsOn(delBtn);
                delBtn.addEventListener('click', () => {
                    customSvgCodes.splice(i, 1);
                    saveCustomSvgCodes();
                    regenerateSvgImmediately();
                    renderList();
                    renderEditArea();
                });

                row.appendChild(handle); row.appendChild(chk); row.appendChild(lbl); row.appendChild(editBtn); row.appendChild(delBtn);
                listWrap.appendChild(row);
            });
            listWrap.scrollTop = scrollTop;
        }

        // Allow dropping onto the list container itself (drop at end)
        listWrap.addEventListener('dragover', (e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; });
        listWrap.addEventListener('drop', (e) => {
            e.preventDefault();
            if (dragSrcIndex === null) return;
            // Only fires if dropped on listWrap background (not a row) → move to end
            const moved = customSvgCodes.splice(dragSrcIndex, 1)[0];
            customSvgCodes.push(moved);
            dragSrcIndex = null;
            saveCustomSvgCodes();
            regenerateSvgImmediately();
            renderList();
        });

        // Edit / Add form
        const editArea = document.createElement('div');
        editArea.style.cssText = `display:flex;flex-direction:column;gap:8px;flex-shrink:0;`;
        modal.appendChild(editArea);

        function renderEditArea(idx) {
            const editing = (idx !== undefined && idx >= 0);
            while (editArea.firstChild) editArea.removeChild(editArea.firstChild);

            const formTitle = document.createElement('div');
            formTitle.textContent = editing ? `✏ Edit: ${customSvgCodes[idx].label}` : '➕ Add new SVG Code';
            formTitle.style.cssText = `font-size:12px;font-weight:900;color:#4a9eff;`;
            editArea.appendChild(formTitle);

            const labelInput = document.createElement('input');
            labelInput.type = 'text';
            labelInput.placeholder = 'Label (z.B. "Sharpen 3x3")';
            labelInput.value = editing ? (customSvgCodes[idx].label || '') : '';
            labelInput.style.cssText = `width:100%;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.2);border-radius:7px;padding:7px 10px;color:#fff;font-size:12px;outline:none;box-sizing:border-box;`;
            stopEventsOn(labelInput);
            editArea.appendChild(labelInput);

            const codeInput = document.createElement('textarea');
            codeInput.placeholder = 'SVG Filter-Primitive Code, z.B.:\n<feConvolveMatrix kernelMatrix="0 -1 0 -1 5 -1 0 -1 0"/>';
            codeInput.value = editing ? (customSvgCodes[idx].code || '') : '';
            codeInput.style.cssText = `width:100%;height:100px;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.2);border-radius:7px;padding:8px 10px;color:#d0ffb0;font-size:11px;font-family:monospace;outline:none;resize:vertical;box-sizing:border-box;`;
            stopEventsOn(codeInput);
            editArea.appendChild(codeInput);

            const errMsg = document.createElement('div');
            errMsg.style.cssText = `font-size:11px;color:#ff7070;min-height:14px;`;
            editArea.appendChild(errMsg);

            const btnRow = document.createElement('div');
            btnRow.style.cssText = `display:flex;gap:8px;justify-content:flex-end;`;

            if (editing) {
                const cancelBtn = document.createElement('button');
                cancelBtn.textContent = 'Cancel';
                cancelBtn.style.cssText = `padding:7px 14px;background:rgba(255,255,255,0.1);color:#ccc;border:1px solid rgba(255,255,255,0.2);border-radius:7px;font-size:12px;cursor:pointer;`;
                stopEventsOn(cancelBtn);
                cancelBtn.addEventListener('click', () => renderEditArea());
                btnRow.appendChild(cancelBtn);
            }

            const saveBtn = document.createElement('button');
            saveBtn.textContent = editing ? '💾 Save' : '➕ Add';
            saveBtn.style.cssText = `padding:7px 16px;background:#2a6fdb;color:#fff;border:none;border-radius:7px;font-size:12px;font-weight:900;cursor:pointer;`;
            stopEventsOn(saveBtn);
            saveBtn.addEventListener('click', () => {
                const label = labelInput.value.trim() || 'Untitled';
                const code = codeInput.value.trim();
                if (!code) { errMsg.textContent = 'Code must not be empty.'; return; }
                const parsed = parseCustomSvgCode(code);
                if (!parsed) { errMsg.textContent = '❌ Invalid SVG code — parse error.'; return; }
                errMsg.textContent = '';
                if (editing) {
                    customSvgCodes[idx].label = label;
                    customSvgCodes[idx].code = code;
                } else {
                    customSvgCodes.push({ id: 'csvg_' + Date.now(), label, code, enabled: true });
                }
                saveCustomSvgCodes();
                regenerateSvgImmediately();
                renderList();
                renderEditArea();
            });
            btnRow.appendChild(saveBtn);
            editArea.appendChild(btnRow);
        }

        renderList();
        renderEditArea();
        makeFloatingManagerDraggable(modal, hdr, 'gvf_custom_svg_modal_pos');
        // Expose renderList so the sync handler can refresh the modal live
        modal._gvfRenderList = renderList;
        (document.body || document.documentElement).appendChild(modal);
    }

    // -------------------------
    // Bulk-update guard
    // -------------------------
    let _inSync = false;
    let _suspendSync = false;

    // Debug/Load settings from storage
    logs = !!gmGet(K.LOGS, true);
    debug = !!gmGet(K.DEBUG, false); // Default false

    // -------------------------
    // User Profile Management
    // -------------------------
    let userProfiles = [];
    let activeUserProfile = null;
    let _lastProfileStorageRev = 0;
    let _lastProfileStorageActiveId = '';
    let _applyingRemoteProfileSync = false;
    let _isApplyingUserProfileSettings = false;
    let _isSwitchingUserProfile = false;
    let _suppressValueSyncUntil = 0;

    function suppressValueSync(ms = 250) {
        const until = Date.now() + Math.max(0, Number(ms) || 0);
        if (until > _suppressValueSyncUntil) _suppressValueSyncUntil = until;
    }

    function isValueSyncSuppressed() {
        return Date.now() < _suppressValueSyncUntil;
    }

    // -------------------------
    // LUT Profile Management
    // -------------------------
    let lutProfiles = [];
    let lutGroups = [];

    // Active LUT selection is stored as a composite key so duplicate names are allowed across groups.
    // Key format: "<group>||<name>" (group may be empty for ungrouped, e.g. "||Warm").
    let activeLutProfileKey = String(gmGet(K.LUT_ACTIVE_PROFILE, 'none') || 'none');
    let activeLutMatrix4x5 = null; // Array[20] or null

    function _lutNormGroup(g) {
        const s = (g === undefined || g === null) ? '' : String(g);
        return s.trim();
    }
    function _lutNormName(n) { return String(n || '').trim(); }
    function lutMakeKey(name, group) {
        const nm = _lutNormName(name);
        const gr = _lutNormGroup(group);
        if (!nm) return 'none';
        return `${gr}||${nm}`;
    }
    function lutParseKey(key) {
        const k = String(key || '').trim();
        if (!k || k === 'none') return { group: '', name: 'none', key: 'none' };
        const i = k.indexOf('||');
        if (i >= 0) {
            const g = k.slice(0, i);
            const n = k.slice(i + 2);
            return { group: _lutNormGroup(g), name: _lutNormName(n), key: `${_lutNormGroup(g)}||${_lutNormName(n)}` };
        }
        // Back-compat: old storage used only the name.
        return { group: '', name: _lutNormName(k), key: `||${_lutNormName(k)}` };
    }
    function lutKeyFromProfile(p) {
        const n = _lutNormName(p && p.name);
        const g = _lutNormGroup(p && p.group);
        return lutMakeKey(n, g);
    }

    let lutSelectEl = null;
    let refreshLutDropdownFn = null;

    // Default user profile
    const DEFAULT_USER_PROFILE = {
        id: 'default',
        name: 'Default',
        createdAt: Date.now(),
        settings: {
            enabled: true,
            darkMoody: true,
            tealOrange: false,
            vibrantSat: false,
            sl: 1.0,
            sr: 0.5,
            bl: -1.2,
            wl: 0.2,
            dn: 0.0,
            edge: 0.1,
            hdr: 0.0,
            profile: 'user',
            renderMode: 'svg',
            lutProfile: 'none',
            autoOn: true,
            autoStrength: 0.65,
            autoLockWB: true,
            u_contrast: 0,
            u_black: 0,
            u_white: 0,
            u_highlights: 0,
            u_shadows: 0,
            u_sat: 0,
            u_vib: 0,
            u_sharp: 0,
            u_gamma: 0,
            u_grain: 0,
            u_hue: 0,
            u_r_gain: 128,
            u_g_gain: 128,
            u_b_gain: 128,
            cbFilter: 'none'
        }
    };

    // Firefox-specific default profile
    const DEFAULT_USER_PROFILE_FIREFOX = {
        id: 'default',
        name: 'Default',
        createdAt: Date.now(),
        settings: {
            enabled: true,
            darkMoody: true,
            tealOrange: false,
            vibrantSat: false,
            sl: 1.3,
            sr: -1.1,
            bl: 0.3,
            wl: 0.2,
            dn: 0.0,
            edge: 0.0,
            hdr: 0.0,
            profile: 'off',
            renderMode: 'svg',
            lutProfile: 'none',
            autoOn: true,
            autoStrength: 0.65,
            autoLockWB: true,
            u_contrast: 0,
            u_black: 0,
            u_white: 0,
            u_highlights: 0,
            u_shadows: 0,
            u_sat: 0,
            u_vib: 0,
            u_sharp: 0,
            u_gamma: 0,
            u_grain: 0,
            u_hue: 0,
            u_r_gain: 128,
            u_g_gain: 128,
            u_b_gain: 128,
            cbFilter: 'none'
        }
    };

    // Profile Management Functions
    function getDefaultUserProfilesFallback() {
        const isFirefoxBrowser = isFirefox();
        const defaultProfile = isFirefoxBrowser ? DEFAULT_USER_PROFILE_FIREFOX : DEFAULT_USER_PROFILE;
        return [JSON.parse(JSON.stringify(defaultProfile))];
    }

    function getDefaultUserProfileSettingsSnapshot() {
        const isFirefoxBrowser = isFirefox();
        const defaults = isFirefoxBrowser ? DEFAULT_USER_PROFILE_FIREFOX.settings : DEFAULT_USER_PROFILE.settings;
        return JSON.parse(JSON.stringify(defaults || {}));
    }

    const PROFILE_UI_ONLY_KEYS = ['iconsShown', 'gradingHudShown', 'ioHudShown', 'scopesHudShown'];

    function stripUiOnlySettings(settingsObj) {
        const src = (settingsObj && typeof settingsObj === 'object') ? JSON.parse(JSON.stringify(settingsObj)) : {};
        for (const key of PROFILE_UI_ONLY_KEYS) {
            if (Object.prototype.hasOwnProperty.call(src, key)) delete src[key];
        }
        return src;
    }

    function settingsEqualNormalized(a, b) {
        try {
            return JSON.stringify(stripUiOnlySettings(a)) === JSON.stringify(stripUiOnlySettings(b));
        } catch (_) {
            return false;
        }
    }

    function buildImportedUserProfileSettings(settingsObj) {
        const defaults = stripUiOnlySettings(getDefaultUserProfileSettingsSnapshot());
        const src = stripUiOnlySettings(settingsObj);
        return {
            ...defaults,
            ...src
        };
    }

    function normalizeUserProfilesForStorage(list) {
        const src = Array.isArray(list) ? list : [];
        const out = [];
        const seen = new Set();
        for (const raw of src) {
            if (!raw || typeof raw !== 'object') continue;
            const id = String(raw.id || '').trim();
            const name = String(raw.name || '').trim();
            if (!id || !name || seen.has(id)) continue;
            seen.add(id);
            out.push({
                id,
                name,
                createdAt: Number(raw.createdAt || Date.now()),
                updatedAt: Number(raw.updatedAt || raw.createdAt || Date.now()),
                settings: buildImportedUserProfileSettings(raw.settings && typeof raw.settings === 'object' ? raw.settings : {})
            });
        }
        return out;
    }

    function readUserProfilesFromLocalStorage() {
        try {
            const raw = localStorage.getItem(K.USER_PROFILES);
            if (!raw) return null;
            const parsed = JSON.parse(raw);
            const normalized = normalizeUserProfilesForStorage(parsed);
            return normalized.length ? normalized : null;
        } catch (_) {
            return null;
        }
    }

    function writeUserProfilesToLocalStorage(profiles, activeId, rev) {
        try { localStorage.setItem(K.USER_PROFILES, JSON.stringify(profiles)); } catch (_) { }
        try { localStorage.setItem(K.ACTIVE_USER_PROFILE, String(activeId || 'default')); } catch (_) { }
        try { localStorage.setItem(K.USER_PROFILES_REV, String(Number(rev || Date.now()) || Date.now())); } catch (_) { }
    }

    function readUserProfilesRevFromLocalStorage() {
        try {
            const raw = localStorage.getItem(K.USER_PROFILES_REV);
            const n = Number(raw);
            return Number.isFinite(n) && n > 0 ? n : 0;
        } catch (_) {
            return 0;
        }
    }

    function readUserProfilesRevFromGM() {
        try {
            const n = Number(gmGet(K.USER_PROFILES_REV, 0));
            return Number.isFinite(n) && n > 0 ? n : 0;
        } catch (_) {
            return 0;
        }
    }

    function resolveStoredActiveUserProfileId(profiles, preferLocal = false) {
        const list = Array.isArray(profiles) ? profiles : [];
        const hasId = (id) => !!id && list.some(p => p && p.id === id);

        let lsId = '';
        let gmId = '';
        try { lsId = String(localStorage.getItem(K.ACTIVE_USER_PROFILE) || '').trim(); } catch (_) { }
        try { gmId = String(gmGet(K.ACTIVE_USER_PROFILE, '') || '').trim(); } catch (_) { }

        if (preferLocal && hasId(lsId)) return lsId;
        if (hasId(gmId)) return gmId;
        if (hasId(lsId)) return lsId;
        if (hasId(_lastProfileStorageActiveId)) return String(_lastProfileStorageActiveId || '').trim();

        const first = list[0] && list[0].id ? String(list[0].id).trim() : '';
        return first || 'default';
    }

    function loadUserProfiles() {
        try {
            const storedGm = gmGet(K.USER_PROFILES, null);
            const normalizedGm = normalizeUserProfilesForStorage(storedGm);
            const storedLs = readUserProfilesFromLocalStorage();

            const gmRev = readUserProfilesRevFromGM();
            const lsRev = readUserProfilesRevFromLocalStorage();
            const useLs = (!!storedLs && storedLs.length && lsRev >= gmRev);
            const useGm = (!!normalizedGm.length && !useLs);

            let needsPersist = false;

            if (useLs) {
                userProfiles = storedLs;
            } else if (useGm) {
                userProfiles = normalizedGm;
            } else if (storedLs && storedLs.length) {
                userProfiles = storedLs;
            } else {
                userProfiles = getDefaultUserProfilesFallback();
                needsPersist = true;
            }

            const activeId = resolveStoredActiveUserProfileId(userProfiles, useLs || (!!storedLs && storedLs.length && lsRev >= gmRev));
            activeUserProfile = userProfiles.find(p => p.id === activeId) || userProfiles[0] || null;
            if (!activeUserProfile && userProfiles.length) activeUserProfile = userProfiles[0];
            if (!activeUserProfile) {
                userProfiles = getDefaultUserProfilesFallback();
                activeUserProfile = userProfiles[0] || null;
                needsPersist = true;
            }

            _lastProfileStorageRev = Math.max(gmRev, lsRev, 0);
            _lastProfileStorageActiveId = String(activeUserProfile && activeUserProfile.id ? activeUserProfile.id : 'default');

            if (needsPersist || !normalizedGm.length || !storedLs || !storedLs.length) {
                saveUserProfiles();
            } else {
                writeUserProfilesToLocalStorage(JSON.parse(JSON.stringify(userProfiles)), _lastProfileStorageActiveId, _lastProfileStorageRev || Date.now());
            }

            log('User profiles loaded:', userProfiles.length, 'Active:', activeUserProfile?.name, 'Source:', useLs ? 'localStorage' : (useGm ? 'GM' : 'fallback'));
        } catch (e) {
            logW('Error loading user profiles:', e);
            userProfiles = getDefaultUserProfilesFallback();
            activeUserProfile = userProfiles[0] || null;
            saveUserProfiles();
        }
    }

    function persistActiveUserProfileSelection(profileId, revMaybe) {
        const nextActiveId = String(profileId || 'default').trim() || 'default';
        const rev = Number(revMaybe);
        const nextRev = Number.isFinite(rev) && rev > 0 ? rev : Date.now();

        _lastProfileStorageRev = nextRev;
        _lastProfileStorageActiveId = nextActiveId;

        try { gmSet(K.ACTIVE_USER_PROFILE, nextActiveId); } catch (_) { }
        try { gmSet(K.USER_PROFILES_REV, nextRev); } catch (_) { }

        try { localStorage.setItem(K.ACTIVE_USER_PROFILE, nextActiveId); } catch (_) { }
        try { localStorage.setItem(K.USER_PROFILES_REV, String(nextRev)); } catch (_) { }

        return nextRev;
    }

    function saveUserProfiles(revMaybe) {
        try {
            suppressValueSync(300);
            userProfiles = normalizeUserProfilesForStorage(userProfiles);
            if (!userProfiles.length) {
                userProfiles = getDefaultUserProfilesFallback();
            }

            if (activeUserProfile) {
                const freshActive = userProfiles.find(p => p.id === activeUserProfile.id);
                activeUserProfile = freshActive || userProfiles[0] || null;
            } else {
                activeUserProfile = userProfiles[0] || null;
            }

            const snapshot = JSON.parse(JSON.stringify(userProfiles));
            const rev = persistActiveUserProfileSelection(activeUserProfile ? activeUserProfile.id : 'default', revMaybe);
            gmSet(K.USER_PROFILES, snapshot);
            gmSet(K.USER_PROFILES_REV, rev);
            writeUserProfilesToLocalStorage(snapshot, activeUserProfile ? activeUserProfile.id : 'default', rev);
        } catch (e) {
            logW('Error saving user profiles:', e);
            try {
                const snapshot = JSON.parse(JSON.stringify(normalizeUserProfilesForStorage(userProfiles)));
                const rev = persistActiveUserProfileSelection(activeUserProfile ? activeUserProfile.id : 'default', revMaybe);
                try { gmSet(K.USER_PROFILES, snapshot); } catch (_) { }
                try { gmSet(K.USER_PROFILES_REV, rev); } catch (_) { }
                writeUserProfilesToLocalStorage(snapshot, activeUserProfile ? activeUserProfile.id : 'default', rev);
            } catch (_) { }
        }
    }

    function refreshUserProfileManagerUi() {
        try { updateProfileList(); } catch (_) { }
        try {
            const activeInfo = document.getElementById('gvf-active-profile-info');
            if (activeInfo) {
                setActiveProfileInfo(activeInfo, activeUserProfile?.name);
            }
        } catch (_) { }
    }

    function pullUserProfilesFromSharedStorage(reason = '', force = false) {
        if (_applyingRemoteProfileSync || _isSwitchingUserProfile) return false;

        try {
            const storedGm = gmGet(K.USER_PROFILES, null);
            const normalizedGm = normalizeUserProfilesForStorage(storedGm);
            const storedLs = readUserProfilesFromLocalStorage();
            const gmRev = readUserProfilesRevFromGM();
            const lsRev = readUserProfilesRevFromLocalStorage();
            const newestRev = Math.max(gmRev, lsRev, 0);

            let nextProfiles = [];
            if (storedLs && storedLs.length && lsRev > gmRev) {
                nextProfiles = storedLs;
            } else if (normalizedGm.length) {
                nextProfiles = normalizedGm;
            } else if (storedLs && storedLs.length) {
                nextProfiles = storedLs;
            } else {
                nextProfiles = getDefaultUserProfilesFallback();
            }

            const nextActiveId = resolveStoredActiveUserProfileId(
                nextProfiles,
                !!(storedLs && storedLs.length && lsRev >= gmRev)
            );

            const currentSnapshot = JSON.stringify(normalizeUserProfilesForStorage(userProfiles));
            const nextSnapshot = JSON.stringify(normalizeUserProfilesForStorage(nextProfiles));
            const currentActiveId = String(activeUserProfile && activeUserProfile.id ? activeUserProfile.id : '');
            const snapshotChanged = nextSnapshot != currentSnapshot;
            const activeChanged = nextActiveId !== currentActiveId;

            if (!force) {
                if (newestRev < _lastProfileStorageRev) return false;
                if (activeChanged && !snapshotChanged && newestRev <= _lastProfileStorageRev) {
                    log('Ignored stale active-profile sync:', reason || 'unknown', 'Incoming:', nextActiveId, 'Current:', currentActiveId);
                    return false;
                }
            }

            const hasChanged = force
                || newestRev > _lastProfileStorageRev
                || activeChanged
                || snapshotChanged;

            if (!hasChanged) return false;

            userProfiles = normalizeUserProfilesForStorage(nextProfiles);
            if (!userProfiles.length) userProfiles = getDefaultUserProfilesFallback();
            activeUserProfile = userProfiles.find(p => p.id === nextActiveId) || userProfiles[0] || null;
            if (!activeUserProfile && userProfiles.length) activeUserProfile = userProfiles[0] || null;

            _lastProfileStorageRev = newestRev || Date.now();
            _lastProfileStorageActiveId = String(activeUserProfile && activeUserProfile.id ? activeUserProfile.id : 'default');

            if (activeUserProfile && activeUserProfile.settings && typeof activeUserProfile.settings === 'object') {
                _applyingRemoteProfileSync = true;
                try {
                    applyUserProfileSettings(activeUserProfile.settings);
                } finally {
                    _applyingRemoteProfileSync = false;
                }
            }

            refreshUserProfileManagerUi();
            log('User profiles synced from shared storage:', reason || 'unknown', 'Active:', activeUserProfile && activeUserProfile.name ? activeUserProfile.name : 'none');
            return true;
        } catch (e) {
            _applyingRemoteProfileSync = false;
            logW('User profile sync from shared storage failed:', reason, e);
            return false;
        }
    }

    let _remoteProfilePullTimer = null;
    function schedulePullUserProfilesFromSharedStorage(reason = '', force = false, delay = 60) {
        if (_remoteProfilePullTimer) clearTimeout(_remoteProfilePullTimer);
        _remoteProfilePullTimer = setTimeout(() => {
            _remoteProfilePullTimer = null;
            pullUserProfilesFromSharedStorage(reason, force);
        }, Math.max(0, Number(delay) || 0));
    }

    // LUT Profiles (Storage + Apply)
    // -------------------------
    function normalizeLutProfilesForStorage(input) {
        const list = Array.isArray(input) ? input : [];
        const out = [];
        const seen = new Set();
        for (const raw of list) {
            if (!raw || typeof raw !== 'object') continue;
            const name = String(raw.name || '').trim();
            if (!name) continue;
            const group = (raw.group === undefined || raw.group === null) ? undefined : String(raw.group).trim();
            const key = `${_lutNormGroup(group)}||${_lutNormName(name)}`;
            if (seen.has(key)) continue;
            const m = Array.isArray(raw.matrix4x5) ? raw.matrix4x5.map(v => Number(v)) : [];
            if (m.length !== 20 || m.some(v => !Number.isFinite(v))) continue;
            seen.add(key);
            out.push({
                name,
                group: group || undefined,
                createdAt: Number(raw.createdAt || Date.now()),
                updatedAt: Number(raw.updatedAt || raw.createdAt || Date.now()),
                matrix4x5: m
            });
        }
        return out;
    }

    function readLutProfilesFromLocalStorage() {
        try {
            const raw = localStorage.getItem(K.LUT_PROFILES);
            if (!raw) return null;
            const parsed = JSON.parse(raw);
            const normalized = normalizeLutProfilesForStorage(parsed);
            return normalized.length ? normalized : null;
        } catch (_) {
            return null;
        }
    }

    function writeLutProfilesToLocalStorage(profiles, activeKey, rev) {
        try { localStorage.setItem(K.LUT_PROFILES, JSON.stringify(profiles)); } catch (_) { }
        try { localStorage.setItem(K.LUT_ACTIVE_PROFILE, String(activeKey || 'none')); } catch (_) { }
        try { localStorage.setItem(K.LUT_PROFILES_REV, String(Number(rev || Date.now()) || Date.now())); } catch (_) { }
    }

    function readLutProfilesRevFromLocalStorage() {
        try {
            const raw = localStorage.getItem(K.LUT_PROFILES_REV);
            const n = Number(raw);
            return Number.isFinite(n) && n > 0 ? n : 0;
        } catch (_) {
            return 0;
        }
    }

    function readLutProfilesRevFromGM() {
        try {
            const n = Number(gmGet(K.LUT_PROFILES_REV, 0));
            return Number.isFinite(n) && n > 0 ? n : 0;
        } catch (_) {
            return 0;
        }
    }

    function loadLutProfiles() {
        try {
            const storedGm = normalizeLutProfilesForStorage(gmGet(K.LUT_PROFILES, null));
            const storedLs = readLutProfilesFromLocalStorage();
            const gmRev = readLutProfilesRevFromGM();
            const lsRev = readLutProfilesRevFromLocalStorage();
            const useLs = (!!storedLs && storedLs.length && lsRev > gmRev);
            const useGm = (!!storedGm.length && !useLs);

            if (useLs) lutProfiles = storedLs;
            else if (useGm) lutProfiles = storedGm;
            else lutProfiles = storedLs || storedGm || [];
        } catch (e) {
            lutProfiles = [];
            logW('Error loading LUT profiles:', e);
        }

        // Load and normalize LUT group list (supports empty groups)
        loadLutGroups();
        try {
            const set = new Set(Array.isArray(lutGroups) ? lutGroups.map(g => String(g || '').trim()).filter(Boolean) : []);
            for (const p of (Array.isArray(lutProfiles) ? lutProfiles : [])) {
                const g = (p && p.group) ? String(p.group).trim() : '';
                if (g) set.add(g);
            }
            lutGroups = Array.from(set).sort((a, b) => a.localeCompare(b));
            saveLutGroups();
        } catch (_) { }

        let storedActiveLs = '';
        try { storedActiveLs = String(localStorage.getItem(K.LUT_ACTIVE_PROFILE) || '').trim(); } catch (_) { }
        let storedActiveGm = String(gmGet(K.LUT_ACTIVE_PROFILE, 'none') || 'none');
        activeLutProfileKey = (storedActiveLs && readLutProfilesRevFromLocalStorage() > readLutProfilesRevFromGM()) ? storedActiveLs : storedActiveGm;
        if (!activeLutProfileKey) activeLutProfileKey = 'none';

        const want = lutParseKey(activeLutProfileKey);
        let p = null;

        if (want.key !== 'none') {
            p = (Array.isArray(lutProfiles) ? lutProfiles : []).find(x => lutKeyFromProfile(x) === want.key) || null;
            if (!p && want.name && want.name !== 'none') {
                p = (Array.isArray(lutProfiles) ? lutProfiles : []).find(x => _lutNormName(x && x.name) === want.name) || null;
                if (p) activeLutProfileKey = lutKeyFromProfile(p);
            }
        }

        activeLutMatrix4x5 = (p && Array.isArray(p.matrix4x5) && p.matrix4x5.length === 20) ? p.matrix4x5 : null;
        saveLutProfiles();
        log('LUT profiles loaded:', lutProfiles.length, 'Active:', activeLutProfileKey);
    }

    function saveLutProfiles() {
        try {
            lutProfiles = normalizeLutProfilesForStorage(lutProfiles);
            const snapshot = JSON.parse(JSON.stringify(lutProfiles));
            const rev = Date.now();
            gmSet(K.LUT_PROFILES, snapshot);
            gmSet(K.LUT_ACTIVE_PROFILE, activeLutProfileKey || 'none');
            gmSet(K.LUT_PROFILES_REV, rev);
            writeLutProfilesToLocalStorage(snapshot, activeLutProfileKey || 'none', rev);
        } catch (e) {
            logW('Error saving LUT profiles:', e);
            try {
                const snapshot = JSON.parse(JSON.stringify(normalizeLutProfilesForStorage(lutProfiles)));
                const rev = Date.now();
                try { gmSet(K.LUT_PROFILES, snapshot); } catch (_) { }
                try { gmSet(K.LUT_ACTIVE_PROFILE, activeLutProfileKey || 'none'); } catch (_) { }
                try { gmSet(K.LUT_PROFILES_REV, rev); } catch (_) { }
                writeLutProfilesToLocalStorage(snapshot, activeLutProfileKey || 'none', rev);
            } catch (_) { }
        }
    }


    function loadLutGroups() {
        try {
            const stored = gmGet(K.LUT_GROUPS, null);
            if (stored && Array.isArray(stored)) {
                lutGroups = stored.map(v => String(v || '').trim()).filter(Boolean);
            } else {
                lutGroups = [];
            }
        } catch (e) {
            lutGroups = [];
            logW('Error loading LUT groups:', e);
        }
        // normalize / unique
        try {
            const set = new Set();
            for (const g of lutGroups) { if (g) set.add(String(g).trim()); }
            lutGroups = Array.from(set).filter(Boolean).sort((a, b) => a.localeCompare(b));
        } catch (_) { }
    }

    function saveLutGroups() {
        try {
            const set = new Set();
            for (const g of (Array.isArray(lutGroups) ? lutGroups : [])) {
                const gg = String(g || '').trim();
                if (gg) set.add(gg);
            }
            lutGroups = Array.from(set).sort((a, b) => a.localeCompare(b));
            gmSet(K.LUT_GROUPS, lutGroups);
        } catch (e) {
            logW('Error saving LUT groups:', e);
        }
    }

    function setActiveLutProfile(keyOrName, groupMaybe, opts = {}) {
        const inVal = String(keyOrName || 'none').trim() || 'none';
        const key = (inVal.includes('||') || inVal === 'none') ? inVal : lutMakeKey(inVal, groupMaybe);
        activeLutProfileKey = key;

        const want = lutParseKey(activeLutProfileKey);
        let p = null;
        if (want.key !== 'none') {
            p = (Array.isArray(lutProfiles) ? lutProfiles : []).find(x => lutKeyFromProfile(x) === want.key) || null;
            if (!p && want.name && want.name !== 'none') {
                // fallback: first match by name (legacy)
                p = (Array.isArray(lutProfiles) ? lutProfiles : []).find(x => _lutNormName(x && x.name) === want.name) || null;
                if (p) activeLutProfileKey = lutKeyFromProfile(p);
            }
        }

        activeLutMatrix4x5 = (p && Array.isArray(p.matrix4x5) && p.matrix4x5.length === 20) ? p.matrix4x5 : null;
        saveLutProfiles();

        log('Active LUT profile set:', activeLutProfileKey);

        // Sync LUT dropdown immediately
        try {
            if (lutSelectEl) lutSelectEl.value = String(activeLutProfileKey || 'none');
            if (typeof refreshLutDropdownFn === 'function') refreshLutDropdownFn();
        } catch (_) { }

        const skipProfileSave = !!(opts && opts.skipProfileSave);
        const skipVisualApply = !!(opts && opts.skipVisualApply);

        if (!skipProfileSave && !_isApplyingUserProfileSettings && !_applyingRemoteProfileSync) {
            updateCurrentProfileSettings();
        }

        if (!skipVisualApply) {
            if (renderMode === 'gpu') {
                applyGpuFilter();
            } else {
                ensureSvgFilter(true);
                applyFilter({ skipSvgIfPossible: false });
            }
            scheduleOverlayUpdate();
        }
    }

    function getActiveLutProfile() {
        const want = lutParseKey(activeLutProfileKey);
        if (want.key === 'none') return null;
        return (Array.isArray(lutProfiles) ? lutProfiles : []).find(x => lutKeyFromProfile(x) === want.key) || null;
    }

    function upsertLutProfile(profile) {
        const name = String(profile && profile.name ? profile.name : '').trim();
        if (!name) throw new Error('Profile name is empty.');

        const groupRaw = (profile && Object.prototype.hasOwnProperty.call(profile, 'group')) ? profile.group : undefined;
        let group = (groupRaw === undefined || groupRaw === null) ? undefined : String(groupRaw).trim();
        if (group === '') group = undefined;

        const idx = lutProfiles.findIndex(p => {
            const pn = _lutNormName(p && p.name);
            const pg = _lutNormGroup(p && p.group);
            const ng = _lutNormGroup(group);
            return (pn === name) && (pg === ng);
        });
        const now = Date.now();

        const prev = (idx >= 0) ? lutProfiles[idx] : null;
        const prevGroup = prev && prev.group ? String(prev.group).trim() : undefined;
        if (group === undefined) group = prevGroup;

        const next = {
            name,
            group: group || undefined,
            createdAt: (idx >= 0 && lutProfiles[idx].createdAt) ? lutProfiles[idx].createdAt : now,
            updatedAt: now,
            matrix4x5: profile.matrix4x5
        };

        if (idx >= 0) lutProfiles[idx] = next;
        else lutProfiles.push(next);

        if (next.group) {
            const g = String(next.group).trim();
            if (g) {
                if (!Array.isArray(lutGroups)) lutGroups = [];
                if (!lutGroups.some(x => String(x).trim() === g)) {
                    lutGroups.push(g);
                    saveLutGroups();
                }
            }
        }

        saveLutProfiles();
        return next;
    }

    function deleteLutProfile(keyOrName, groupMaybe) {
        const inVal = String(keyOrName || '').trim();
        if (!inVal) return;

        const key = (inVal.includes('||')) ? inVal : lutMakeKey(inVal, groupMaybe);
        const want = lutParseKey(key);
        if (want.key === 'none') return;

        lutProfiles = (Array.isArray(lutProfiles) ? lutProfiles : []).filter(p => lutKeyFromProfile(p) !== want.key);

        if (String(activeLutProfileKey) === want.key) {
            activeLutProfileKey = 'none';
            activeLutMatrix4x5 = null;
        }

        saveLutProfiles();
        log('Deleted LUT profile:', want.key);
    }

    // -------------------------
    // JSZip Loader (CDN) - DOM method only
    // -------------------------
    let _jszipPromise = null;
    function ensureJsZipLoaded() {
        if (window.JSZip) return Promise.resolve(window.JSZip);
        if (_jszipPromise) return _jszipPromise;

        _jszipPromise = new Promise((resolve, reject) => {
            const existing = document.querySelector('script[data-gvf-jszip="1"]');
            if (existing && window.JSZip) return resolve(window.JSZip);

            const s = document.createElement('script');
            s.dataset.gvfJszip = '1';
            s.setAttribute('data-gvf-jszip', '1');
            s.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
            s.async = true;
            s.onload = () => (window.JSZip ? resolve(window.JSZip) : reject(new Error('JSZip loaded but window.JSZip is missing.')));
            s.onerror = () => reject(new Error('Failed to load JSZip from CDN.'));
            (document.head || document.documentElement).appendChild(s);
        });
        return _jszipPromise;
    }

    // -------------------------
    // PNG -> 4x5 Matrix (Least Squares)
    // Supports tiled 2D LUT layouts (e.g. 512x512 => LUT_SIZE=64, tiles 8x8).
    // -------------------------
    function detectTiledLutLayout(w, h) {
        const candidates = [16, 32, 64, 128];
        for (const lutSize of candidates) {
            if ((w % lutSize) !== 0 || (h % lutSize) !== 0) continue;
            const tilesX = w / lutSize;
            const tilesY = h / lutSize;
            if (tilesX * tilesY !== lutSize) continue;
            return { lutSize, tilesX, tilesY };
        }
        return null;
    }

    function imgDataGetRGBA(imgData, x, y) {
        const i = (y * imgData.width + x) * 4;
        return [
            imgData.data[i] / 255,
            imgData.data[i + 1] / 255,
            imgData.data[i + 2] / 255,
            imgData.data[i + 3] / 255
        ];
    }

    function lutSampleTiled(imgData, r, g, b, layout, flipY) {
        const { lutSize, tilesX } = layout;
        r = clamp(r, 0, 1); g = clamp(g, 0, 1); b = clamp(b, 0, 1);

        const lerp = (a, c, t) => a + (c - a) * t;

        const bf = b * (lutSize - 1);
        const b0 = Math.floor(bf);
        const b1 = Math.min(b0 + 1, lutSize - 1);
        const bt = bf - b0;

        const sampleSlice = (bslice) => {
            const tileX = bslice % tilesX;
            const tileY = Math.floor(bslice / tilesX);

            const xf = r * (lutSize - 1);
            const yf = g * (lutSize - 1);
            const x0 = Math.floor(xf), x1 = Math.min(x0 + 1, lutSize - 1);
            const y0 = Math.floor(yf), y1 = Math.min(y0 + 1, lutSize - 1);
            const tx = xf - x0, ty = yf - y0;

            const px = (ix, iy) => {
                let x = tileX * lutSize + ix;
                let y = tileY * lutSize + iy;
                if (flipY) y = (imgData.height - 1) - y;
                return imgDataGetRGBA(imgData, x, y);
            };

            const c00 = px(x0, y0), c10 = px(x1, y0), c01 = px(x0, y1), c11 = px(x1, y1);

            const out = [0,0,0];
            for (let i = 0; i < 3; i++) {
                const c0 = lerp(c00[i], c10[i], tx);
                const c1 = lerp(c01[i], c11[i], tx);
                out[i] = lerp(c0, c1, ty);
            }
            return out;
        };

        const c0 = sampleSlice(b0);
        const c1 = sampleSlice(b1);
        return [lerp(c0[0], c1[0], bt), lerp(c0[1], c1[1], bt), lerp(c0[2], c1[2], bt)];
    }

    function invert4x4(A) {
        const M = A.map(r => r.slice());
        const I = [
            [1,0,0,0],
            [0,1,0,0],
            [0,0,1,0],
            [0,0,0,1],
        ];

        for (let col = 0; col < 4; col++) {
            let piv = col;
            let pivVal = Math.abs(M[piv][col]);
            for (let r = col + 1; r < 4; r++) {
                const v = Math.abs(M[r][col]);
                if (v > pivVal) { pivVal = v; piv = r; }
            }
            if (pivVal < 1e-12) throw new Error('Matrix inversion failed (singular).');

            if (piv !== col) {
                [M[col], M[piv]] = [M[piv], M[col]];
                [I[col], I[piv]] = [I[piv], I[col]];
            }

            const pivot = M[col][col];
            for (let j = 0; j < 4; j++) { M[col][j] /= pivot; I[col][j] /= pivot; }

            for (let r = 0; r < 4; r++) {
                if (r === col) continue;
                const f = M[r][col];
                for (let j = 0; j < 4; j++) {
                    M[r][j] -= f * M[col][j];
                    I[r][j] -= f * I[col][j];
                }
            }
        }
        return I;
    }

    function fitAffineRGB(X, Y) {
        const XtX = [
            [0,0,0,0],
            [0,0,0,0],
            [0,0,0,0],
            [0,0,0,0],
        ];
        const XtY = [
            [0,0,0],
            [0,0,0],
            [0,0,0],
            [0,0,0],
        ];

        for (let i = 0; i < X.length; i++) {
            const x = X[i];
            const y = Y[i];
            for (let a = 0; a < 4; a++) {
                for (let b = 0; b < 4; b++) XtX[a][b] += x[a] * x[b];
                XtY[a][0] += x[a] * y[0];
                XtY[a][1] += x[a] * y[1];
                XtY[a][2] += x[a] * y[2];
            }
        }

        const inv = invert4x4(XtX);

        const M = [
            [0,0,0],
            [0,0,0],
            [0,0,0],
            [0,0,0],
        ];
        for (let i = 0; i < 4; i++) {
            for (let k = 0; k < 3; k++) {
                let sum = 0;
                for (let j = 0; j < 4; j++) sum += inv[i][j] * XtY[j][k];
                M[i][k] = sum;
            }
        }
        return M;
    }

    function buildMatrix4x5FromAffine(M4x3) {
        return [
            M4x3[0][0], M4x3[1][0], M4x3[2][0], 0.0, M4x3[3][0],
            M4x3[0][1], M4x3[1][1], M4x3[2][1], 0.0, M4x3[3][1],
            M4x3[0][2], M4x3[1][2], M4x3[2][2], 0.0, M4x3[3][2],
            0.0,       0.0,       0.0,       1.0, 0.0
        ];
    }

    function _lutSrgbToLinear(v) { return (v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)); }
    function _lutLinearToSrgb(v) { return (v <= 0.0031308 ? 12.92 * v : 1.055 * Math.pow(v, 1/2.4) - 0.055); }

    function buildSamplesGrid(samplesPerAxis, linearizeIn) {
        const X = [];
        for (let bi = 0; bi < samplesPerAxis; bi++) {
            for (let gi = 0; gi < samplesPerAxis; gi++) {
                for (let ri = 0; ri < samplesPerAxis; ri++) {
                    let r = ri / (samplesPerAxis - 1);
                    let g = gi / (samplesPerAxis - 1);
                    let b = bi / (samplesPerAxis - 1);
                    if (linearizeIn) {
                        r = _lutSrgbToLinear(r);
                        g = _lutSrgbToLinear(g);
                        b = _lutSrgbToLinear(b);
                    }
                    X.push([r, g, b, 1.0]);
                }
            }
        }
        return X;
    }

    async function pngFileToMatrix4x5(file, opts = {}) {
        const flipY = !!opts.flipY;
        const linearizeIn = !!opts.linearizeIn;
        const delinearizeOut = !!opts.delinearizeOut;
        const samplesPerAxis = clamp(Number(opts.samplesPerAxis || 11), 5, 25);

        const url = URL.createObjectURL(file);
        try {
            const img = new Image();
            img.decoding = 'async';
            img.src = url;
            await new Promise((resolve, reject) => {
                img.onload = () => resolve();
                img.onerror = () => reject(new Error('Failed to load LUT PNG.'));
            });

            const w = img.naturalWidth || img.width;
            const h = img.naturalHeight || img.height;

            const layout = detectTiledLutLayout(w, h);
            if (!layout) throw new Error(`Unsupported LUT layout for ${w}x${h}. Expected tiled layout (e.g. 512x512 => LUT_SIZE=64 tiles 8x8).`);

            const canvas = document.createElement('canvas');
            canvas.width = w;
            canvas.height = h;
            const ctx = canvas.getContext('2d', { willReadFrequently: true });
            if (!ctx) throw new Error('Canvas 2D context unavailable.');

            ctx.drawImage(img, 0, 0, w, h);
            const imgData = ctx.getImageData(0, 0, w, h);

            const X = buildSamplesGrid(samplesPerAxis, linearizeIn);
            const Y = [];

            for (let i = 0; i < X.length; i++) {
                const x = X[i];
                const rgb = lutSampleTiled(imgData, x[0], x[1], x[2], layout, flipY);
                let out = rgb;
                if (delinearizeOut) out = [_lutLinearToSrgb(out[0]), _lutLinearToSrgb(out[1]), _lutLinearToSrgb(out[2])];
                Y.push(out);
            }

            const M4x3 = fitAffineRGB(X, Y);
            const m4x5 = buildMatrix4x5FromAffine(M4x3);

            return { matrix4x5: m4x5, layout, width: w, height: h };
        } finally {
            URL.revokeObjectURL(url);
        }
    }



    // -------------------------
    // CUBE 3D LUT (.cube) -> 4x5 matrix (row-major)
    // -------------------------

    function parseCubeText(text) {
        const lines = String(text || '').split(/\r?\n/);
        let size = 0;
        let domainMin = [0, 0, 0];
        let domainMax = [1, 1, 1];
        const data = [];

        for (let i = 0; i < lines.length; i++) {
            const line = lines[i].trim();
            if (!line || line.startsWith('#')) continue;

            const parts = line.split(/\s+/);
            if (parts[0] === 'TITLE') continue;

            if (parts[0] === 'LUT_3D_SIZE') {
                size = parseInt(parts[1], 10) || 0;
                continue;
            }
            if (parts[0] === 'DOMAIN_MIN' && parts.length >= 4) {
                domainMin = [Number(parts[1]), Number(parts[2]), Number(parts[3])];
                continue;
            }
            if (parts[0] === 'DOMAIN_MAX' && parts.length >= 4) {
                domainMax = [Number(parts[1]), Number(parts[2]), Number(parts[3])];
                continue;
            }

            // data line: r g b
            if (parts.length >= 3) {
                const r = Number(parts[0]), g = Number(parts[1]), b = Number(parts[2]);
                if (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) data.push([r, g, b]);
            }
        }

        if (!size || data.length !== size * size * size) {
            throw new Error('Invalid .cube file: missing LUT_3D_SIZE or wrong data length.');
        }

        return { size, domainMin, domainMax, data };
    }

    function cubeSampleTrilinear(lut, r, g, b) {
        const size = lut.size;
        const data = lut.data;
        const dmin = lut.domainMin;
        const dmax = lut.domainMax;

        const scale = (v, mn, mx) => (v - mn) / (mx - mn);

        let rr = scale(r, dmin[0], dmax[0]);
        let gg = scale(g, dmin[1], dmax[1]);
        let bb = scale(b, dmin[2], dmax[2]);

        rr = clamp(rr, 0, 1);
        gg = clamp(gg, 0, 1);
        bb = clamp(bb, 0, 1);

        const rf = rr * (size - 1);
        const gf = gg * (size - 1);
        const bf = bb * (size - 1);

        const r0 = Math.floor(rf), r1 = Math.min(r0 + 1, size - 1);
        const g0 = Math.floor(gf), g1 = Math.min(g0 + 1, size - 1);
        const b0 = Math.floor(bf), b1 = Math.min(b0 + 1, size - 1);

        const tr = rf - r0;
        const tg = gf - g0;
        const tb = bf - b0;

        // .cube standard ordering: R fastest, then G, then B (same as: idx = r + size*g + size*size*b)
        const idx = (ri, gi, bi) => ri + size * gi + size * size * bi;

        const c000 = data[idx(r0, g0, b0)];
        const c100 = data[idx(r1, g0, b0)];
        const c010 = data[idx(r0, g1, b0)];
        const c110 = data[idx(r1, g1, b0)];
        const c001 = data[idx(r0, g0, b1)];
        const c101 = data[idx(r1, g0, b1)];
        const c011 = data[idx(r0, g1, b1)];
        const c111 = data[idx(r1, g1, b1)];

        const lerp = (a, c, t) => a + (c - a) * t;

        const out = [0, 0, 0];
        for (let i = 0; i < 3; i++) {
            const x00 = lerp(c000[i], c100[i], tr);
            const x10 = lerp(c010[i], c110[i], tr);
            const x01 = lerp(c001[i], c101[i], tr);
            const x11 = lerp(c011[i], c111[i], tr);
            const y0 = lerp(x00, x10, tg);
            const y1 = lerp(x01, x11, tg);
            out[i] = lerp(y0, y1, tb);
        }
        return out;
    }

    function cubeTextToMatrix4x5(text, samplesPerAxis = 11, opts = {}) {
        const linearizeIn = !!opts.linearizeIn;
        const delinearizeOut = !!opts.delinearizeOut;

        const lut = parseCubeText(text);
        const X = buildSamplesGrid(clamp(Number(samplesPerAxis || 11), 5, 25), linearizeIn);
        const Y = [];

        for (let i = 0; i < X.length; i++) {
            const x = X[i];
            let out = cubeSampleTrilinear(lut, x[0], x[1], x[2]);
            if (delinearizeOut) out = [_lutLinearToSrgb(out[0]), _lutLinearToSrgb(out[1]), _lutLinearToSrgb(out[2])];
            Y.push(out);
        }

        const M4x3 = fitAffineRGB(X, Y);
        return buildMatrix4x5FromAffine(M4x3);
    }

    async function cubeFileToMatrix4x5(file, opts = {}) {
        const samplesPerAxis = clamp(Number(opts.samplesPerAxis || 11), 5, 25);
        const linearizeIn = !!opts.linearizeIn;
        const delinearizeOut = !!opts.delinearizeOut;

        const text = await file.text();
        const m4x5 = cubeTextToMatrix4x5(text, samplesPerAxis, { linearizeIn, delinearizeOut });
        return { matrix4x5: m4x5, size: (parseCubeText(text).size || 0) };
    }

    function matrixCopyNoBrackets(m20) {
        const arr = Array.isArray(m20) ? m20 : [];
        return arr.map(v => {
            const n = Number(v);
            if (!Number.isFinite(n)) return '0';
            // stable precision for copy/paste
            let s = n.toFixed(10);
            s = s.replace(/0+$/,'').replace(/\.$/,'');
            return s;
        }).join(', ');
    }

    function createNewUserProfile(name) {
        const now = Date.now();
        const newProfile = {
            id: 'profile_' + now + '_' + Math.random().toString(36).slice(2, 11),
            name: String(name || 'New profile').trim() || 'New profile',
            createdAt: now,
            updatedAt: now,
            settings: { ...getCurrentSettings() }
        };
        userProfiles = normalizeUserProfilesForStorage([...(Array.isArray(userProfiles) ? userProfiles : []), newProfile]);
        activeUserProfile = userProfiles.find(p => p.id === newProfile.id) || newProfile;
        saveUserProfiles();
        return activeUserProfile || newProfile;
    }

    function deleteUserProfile(profileId) {
        if (profileId === 'default') {
            log('Cannot delete standard profile');
            return false;
        }

        const index = userProfiles.findIndex(p => p.id === profileId);
        if (index !== -1) {
            userProfiles.splice(index, 1);

            // If active profile has been deleted, switch to default
            if (activeUserProfile && activeUserProfile.id === profileId) {
                switchToUserProfile('default');
            }

            saveUserProfiles();
            return true;
        }
        return false;
    }

    function switchToUserProfile(profileId) {
        const profile = userProfiles.find(p => p.id === profileId);
        if (!profile) {
            logW('Profile not found:', profileId);
            return false;
        }

        if (activeUserProfile && activeUserProfile.id === profile.id) {
            refreshUserProfileManagerUi();
            return true;
        }

        if (_autoSaveProfileTimer) {
            clearTimeout(_autoSaveProfileTimer);
            _autoSaveProfileTimer = null;
        }

        _isSwitchingUserProfile = true;
        try {
            activeUserProfile = profile;
            persistActiveUserProfileSelection(profile.id || 'default');
            try { localStorage.setItem(K.ACTIVE_USER_PROFILE, String(profile.id || 'default')); } catch (_) { }
            try { gmSet(K.ACTIVE_USER_PROFILE, String(profile.id || 'default')); } catch (_) { }
            applyUserProfileSettings(profile.settings || {});
        } finally {
            _isSwitchingUserProfile = false;
        }

        log('Switched to profile:', profile.name);

        showProfileNotification(profile.name);
        refreshUserProfileManagerUi();

        return true;
    }

    // Cycle to next profile (for Shift+Q)
    function cycleToNextProfile() {
        if (!userProfiles || userProfiles.length === 0) return;

        const currentIndex = userProfiles.findIndex(p => p.id === activeUserProfile?.id);
        if (currentIndex === -1) return;

        const nextIndex = (currentIndex + 1) % userProfiles.length;
        const nextProfile = userProfiles[nextIndex];

        switchToUserProfile(nextProfile.id);
    }

    // -------------------------
    // Profile Import / Export (ZIP per-profile JSON)
    // -------------------------
    function sanitizeProfileFilename(name) {
        const base = String(name || 'profile').trim() || 'profile';
        // Keep it Windows-safe and URL-safe
        const safe = base
            .replace(/[<>:"/\\|?*\x00-\x1F]/g, '_')
            .replace(/\s+/g, ' ')
            .trim()
            .slice(0, 80);
        return safe || 'profile';
    }

    function _u16le(v) { return [v & 255, (v >>> 8) & 255]; }
    function _u32le(v) { return [v & 255, (v >>> 8) & 255, (v >>> 16) & 255, (v >>> 24) & 255]; }

    // CRC32 (for ZIP)
    const _CRC32_TABLE = (() => {
        const t = new Uint32Array(256);
        for (let i = 0; i < 256; i++) {
            let c = i;
            for (let k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
            t[i] = c >>> 0;
        }
        return t;
    })();

    function crc32(u8) {
        let c = 0xFFFFFFFF;
        for (let i = 0; i < u8.length; i++) c = _CRC32_TABLE[(c ^ u8[i]) & 255] ^ (c >>> 8);
        return (c ^ 0xFFFFFFFF) >>> 0;
    }

    function makeZipBlob(fileEntries) {
        // Minimal ZIP writer (store-only, no compression).
        // fileEntries: [{name: "x.json", data: Uint8Array}]
        const parts = [];
        const cdParts = [];
        let offset = 0;

        const enc = new TextEncoder();

        for (const f of fileEntries) {
            const nameBytes = enc.encode(String(f.name || 'file.bin'));
            const dataBytes = (f.data instanceof Uint8Array) ? f.data : new Uint8Array(f.data || []);
            const c = crc32(dataBytes);
            const size = dataBytes.length >>> 0;

            // Local file header
            const local = [];
            local.push(..._u32le(0x04034b50)); // sig
            local.push(..._u16le(20));         // ver
            local.push(..._u16le(0));          // flags
            local.push(..._u16le(0));          // method=store
            local.push(..._u16le(0));          // mod time
            local.push(..._u16le(0));          // mod date
            local.push(..._u32le(c));          // crc
            local.push(..._u32le(size));       // comp size
            local.push(..._u32le(size));       // uncomp size
            local.push(..._u16le(nameBytes.length)); // name len
            local.push(..._u16le(0));          // extra len

            const localHdr = new Uint8Array(local);
            parts.push(localHdr, nameBytes, dataBytes);

            // Central directory header
            const cd = [];
            cd.push(..._u32le(0x02014b50)); // sig
            cd.push(..._u16le(20));         // ver made
            cd.push(..._u16le(20));         // ver needed
            cd.push(..._u16le(0));          // flags
            cd.push(..._u16le(0));          // method
            cd.push(..._u16le(0));          // mod time
            cd.push(..._u16le(0));          // mod date
            cd.push(..._u32le(c));          // crc
            cd.push(..._u32le(size));       // comp size
            cd.push(..._u32le(size));       // uncomp size
            cd.push(..._u16le(nameBytes.length)); // name len
            cd.push(..._u16le(0));          // extra len
            cd.push(..._u16le(0));          // comment len
            cd.push(..._u16le(0));          // disk start
            cd.push(..._u16le(0));          // int attrs
            cd.push(..._u32le(0));          // ext attrs
            cd.push(..._u32le(offset));     // local hdr offset

            const cdHdr = new Uint8Array(cd);
            cdParts.push(cdHdr, nameBytes);

            offset += localHdr.length + nameBytes.length + dataBytes.length;
        }

        const cdStart = offset;
        for (const part of cdParts) {
            parts.push(part);
            offset += part.length;
        }
        const cdSize = offset - cdStart;

        // EOCD
        const eocd = [];
        eocd.push(..._u32le(0x06054b50)); // sig
        eocd.push(..._u16le(0)); // disk
        eocd.push(..._u16le(0)); // cd start disk
        eocd.push(..._u16le(fileEntries.length)); // entries this disk
        eocd.push(..._u16le(fileEntries.length)); // entries total
        eocd.push(..._u32le(cdSize)); // cd size
        eocd.push(..._u32le(cdStart)); // cd offset
        eocd.push(..._u16le(0)); // comment len

        parts.push(new Uint8Array(eocd));

        return new Blob(parts, { type: 'application/zip' });
    }

    function _zipName(prefix) {
        const d = new Date();
        const pad = (n) => String(n).padStart(2, '0');
        return `${prefix}_${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}_${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}.zip`;
    }

    function exportAllUserProfilesAsZip() {
        // Always persist latest current settings into active profile before export
        try { updateCurrentProfileSettings(); } catch (_) { }

        const enc = new TextEncoder();
        const entries = [];

        (userProfiles || []).forEach((p) => {
            try {
                const fileBase = sanitizeProfileFilename(p && p.name);
                const fileName = `${fileBase}.json`;
                const jsonStr = JSON.stringify(p, null, 2);
                entries.push({ name: fileName, data: enc.encode(jsonStr) });
            } catch (_) { }
        });


        if (!entries.length) return null;
        return makeZipBlob(entries);
    }


    function exportAllLutProfilesAsZip() {
        const enc = new TextEncoder();
        const entries = [];

        (lutProfiles || []).forEach((p) => {
            try {
                const name = String(p && p.name || '').trim();
                if (!name) return;

                const fileBase = sanitizeProfileFilename(name);
                const grp = (p && p.group) ? String(p.group).trim() : '';
                const grpBase = grp ? sanitizeProfileFilename(grp) : '';
                const fileName = grpBase ? `${grpBase}__${fileBase}.json` : `${fileBase}.json`;

                const payload = {
                    schema: 'gvf-lut-profile',
                    ver: 1,
                    name,
                    group: (p && p.group) ? String(p.group) : undefined,
                    createdAt: (p && p.createdAt) || Date.now(),
                    updatedAt: (p && p.updatedAt) || Date.now(),
                    matrix4x5: (p && p.matrix4x5) || null
                };

                const jsonStr = JSON.stringify(payload, null, 2);
                entries.push({ name: fileName, data: enc.encode(jsonStr) });
            } catch (_) { }
        });

        // Export group list as separate file to support empty groups
        try {
            const set = new Set();
            for (const g0 of (Array.isArray(lutGroups) ? lutGroups : [])) {
                const g = String(g0 || '').trim();
                if (g) set.add(g);
            }
            for (const p of (Array.isArray(lutProfiles) ? lutProfiles : [])) {
                const g = (p && p.group) ? String(p.group).trim() : '';
                if (g) set.add(g);
            }
            const groupsOut = Array.from(set).sort((a, b) => a.localeCompare(b));
            if (groupsOut.length) {
                const payload = { schema: 'gvf-lut-groups', ver: 1, groups: groupsOut, exportedAt: Date.now() };
                entries.push({ name: '_lut_groups.json', data: enc.encode(JSON.stringify(payload, null, 2)) });
            }
        } catch (_) { }

        if (!entries.length) return null;
        return makeZipBlob(entries);
    }

function downloadBlob(blob, filename) {
        try {
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            a.remove();
            setTimeout(() => URL.revokeObjectURL(url), 1500);
        } catch (_) { }
    }

    async function importProfilesFromZipOrJsonFile(file, statusEl) {
        const name = String(file && file.name || '').toLowerCase();
        const isZip = name.endsWith('.zip') || (file && file.type === 'application/zip');

        const setStatus = (t) => { if (statusEl) statusEl.textContent = t; };

        try {
            const buf = await file.arrayBuffer();
            if (!buf || !buf.byteLength) { setStatus('Import failed (empty file).'); return false; }

            if (!isZip) {
                // Single JSON profile
                const raw = new TextDecoder().decode(new Uint8Array(buf));
                const obj = JSON.parse(String(raw || '').trim());

                const ok = importSingleUserProfileObject(obj, setStatus);
                if (ok) {
                    saveUserProfiles();
                    updateProfileList();
                    const activeInfo = document.getElementById('gvf-active-profile-info');
                    if (activeInfo) setActiveProfileInfo(activeInfo, activeUserProfile?.name);
                }
                return ok;
            }

            // ZIP (store-only or deflate if DecompressionStream supports it)
            const files = await unzipToFiles(new Uint8Array(buf), setStatus);
            if (!files || !files.length) { setStatus('Import failed (no files in zip).'); return false; }

            let imported = 0;
            for (const f of files) {
                if (!f || !f.name || !f.data) continue;
                if (!String(f.name).toLowerCase().endsWith('.json')) continue;
                try {
                    const raw = new TextDecoder().decode(f.data);
                    const obj = JSON.parse(String(raw || '').trim());
                    if (importSingleUserProfileObject(obj, setStatus)) imported++;
                } catch (_) { }
            }

            if (imported > 0) {
                saveUserProfiles();
                updateProfileList();
                const activeInfo = document.getElementById('gvf-active-profile-info');
                if (activeInfo) setActiveProfileInfo(activeInfo, activeUserProfile?.name);
                setStatus(`Imported ${imported} profile(s) from ZIP.`);
                return true;
            }

            setStatus('Import failed (no valid profile JSON found).');
            return false;
        } catch (e) {
            logW('Profile import error:', e);
            setStatus('Import failed (invalid file).');
            return false;
        }
    }


    async function importLutProfilesFromZipOrJsonFile(file) {
        const name = String(file && file.name || '').toLowerCase();
        const isZip = name.endsWith('.zip') || (file && file.type === 'application/zip');

        try {
            const buf = await file.arrayBuffer();
            if (!buf || !buf.byteLength) return { ok: false, msg: 'Import failed (empty file).' };

            if (!isZip) {
                // Single JSON LUT profile
                const raw = new TextDecoder().decode(new Uint8Array(buf));
                const obj = JSON.parse(String(raw || '').trim());
                let ok = false;
                if (obj && obj.schema === 'gvf-lut-groups' && Array.isArray(obj.groups)) {
                    if (!Array.isArray(lutGroups)) lutGroups = [];
                    for (const g0 of obj.groups) {
                        const g = String(g0 || '').trim();
                        if (g && !lutGroups.some(x => String(x).trim() === g)) lutGroups.push(g);
                    }
                    saveLutGroups();
                    ok = true;
                } else {
                    ok = importSingleLutProfileObject(obj);
                }
                if (ok) {
                    saveLutProfiles();
                    try { updateLutProfileList(); } catch (_) { }
                    try { setActiveLutInfo(); } catch (_) { }
                    try { setActiveLutProfile(activeLutProfileKey); } catch (_) { }
                }
                return ok ? { ok: true, msg: (obj && obj.schema === 'gvf-lut-groups') ? 'Imported LUT groups.' : 'Imported 1 LUT profile.' } : { ok: false, msg: 'Import failed (invalid LUT JSON).' };
            }

            // ZIP
            const files = await unzipToFiles(new Uint8Array(buf), null);
            if (!files || !files.length) return { ok: false, msg: 'Import failed (no files in zip).' };

            // Clear all existing LUT profiles and groups before importing
            lutProfiles = [];
            lutGroups = [];
            activeLutProfileKey = 'none';
            activeLutMatrix4x5 = null;

            let imported = 0;
            for (const f of files) {
                if (!f || !f.name || !f.data) continue;
                if (!String(f.name).toLowerCase().endsWith('.json')) continue;
                try {
                    const raw = new TextDecoder().decode(f.data);
                    const obj = JSON.parse(String(raw || '').trim());
                    if (obj && obj.schema === 'gvf-lut-groups' && Array.isArray(obj.groups)) {
                        // Merge group list
                        if (!Array.isArray(lutGroups)) lutGroups = [];
                        for (const g0 of obj.groups) {
                            const g = String(g0 || '').trim();
                            if (g && !lutGroups.some(x => String(x).trim() === g)) lutGroups.push(g);
                        }
                        saveLutGroups();
                    } else {
                        if (importSingleLutProfileObject(obj)) imported++;
                    }
                } catch (_) { }
            }

            if (imported > 0) {
                saveLutProfiles();
                try { updateLutProfileList(); } catch (_) { }
                try { setActiveLutInfo(); } catch (_) { }
                try { setActiveLutProfile(activeLutProfileKey); } catch (_) { }
                return { ok: true, msg: `Imported ${imported} LUT profile(s) from ZIP.` };
            }

            return { ok: false, msg: 'Import failed (no valid LUT JSON found).' };
        } catch (e) {
            logW('LUT import error:', e);
            return { ok: false, msg: 'Import failed (invalid file).' };
        }
    }

    function importSingleLutProfileObject(obj) {
        if (!obj || typeof obj !== 'object') return false;

        // Accept {schema:'gvf-lut-profile', name, matrix4x5, group?} or {name, matrix4x5, group?}
        const name = String(obj.name || '').trim();
        const m = obj.matrix4x5;
        const groupRaw = (Object.prototype.hasOwnProperty.call(obj, 'group')) ? obj.group : undefined;
        let group = (groupRaw === undefined || groupRaw === null) ? undefined : String(groupRaw).trim();
        if (group === '') group = undefined;

        if (!name) return false;
        if (!Array.isArray(m) || m.length !== 20) return false;

        // Normalize to numbers
        const mat = m.map(v => Number(v));
        if (mat.some(v => !isFinite(v))) return false;

        // Overwrite on duplicate name (upsert behavior)
        upsertLutProfile({ name, group, matrix4x5: mat });
        return true;
    }

    function importSingleUserProfileObject(obj, setStatus) {
        if (!obj || typeof obj !== 'object') return false;

        const isProfileObj = (obj && typeof obj.name === 'string' && obj.settings && typeof obj.settings === 'object');
        const settingsObj = isProfileObj ? obj.settings : obj;

        if (!settingsObj || typeof settingsObj !== 'object' || (!('renderMode' in settingsObj) && !('profile' in settingsObj))) {
            return false;
        }

        const profileName = sanitizeProfileFilename(isProfileObj ? obj.name : ('Imported ' + new Date().toLocaleString()));
        const norm = (s) => sanitizeProfileFilename(String(s || '')).toLowerCase();
        const targetNorm = norm(profileName);

        let existingIdx = -1;
        for (let i = 0; i < (userProfiles || []).length; i++) {
            const p = userProfiles[i];
            if (norm(p && p.name) === targetNorm) { existingIdx = i; break; }
        }

        const now = Date.now();
        const baseSettings = buildImportedUserProfileSettings(settingsObj);
        let nextProfile = null;

        if (existingIdx >= 0) {
            const prev = userProfiles[existingIdx];
            const preservedId = prev && prev.id ? String(prev.id) : ((isProfileObj && obj.id) ? String(obj.id) : ('profile_' + now + '_' + Math.random().toString(36).slice(2, 11)));
            const preservedCreatedAt = (prev && Number(prev.createdAt)) ? Number(prev.createdAt) : ((isProfileObj && Number(obj.createdAt)) ? Number(obj.createdAt) : now);
            nextProfile = {
                id: preservedId,
                name: profileName,
                createdAt: preservedCreatedAt,
                updatedAt: now,
                settings: baseSettings
            };
            userProfiles.splice(existingIdx, 1, nextProfile);
        } else {
            nextProfile = {
                id: (isProfileObj && obj.id) ? String(obj.id) : ('profile_' + now + '_' + Math.random().toString(36).slice(2, 11)),
                name: profileName,
                createdAt: (isProfileObj && Number(obj.createdAt)) ? Number(obj.createdAt) : now,
                updatedAt: now,
                settings: baseSettings
            };
            userProfiles = normalizeUserProfilesForStorage([...(Array.isArray(userProfiles) ? userProfiles : []), nextProfile]);
            nextProfile = userProfiles.find(p => p.id === nextProfile.id) || nextProfile;
        }

        if (activeUserProfile && nextProfile && activeUserProfile.id === nextProfile.id) {
            activeUserProfile = nextProfile;
            try { applyUserProfileSettings(nextProfile.settings); } catch (_) { }
        }

        if (typeof setStatus === 'function') {
            setStatus(`${existingIdx >= 0 ? 'Replaced' : 'Imported'}: ${profileName}`);
        }

        log(existingIdx >= 0 ? 'Profile replaced:' : 'Profile imported:', profileName);
        return true;
    }

    // Minimal ZIP reader (supports STORE, and DEFLATE when DecompressionStream is available).
    async function unzipToFiles(zipU8, setStatus) {
        const dv = new DataView(zipU8.buffer, zipU8.byteOffset, zipU8.byteLength);
        const u16 = (o) => dv.getUint16(o, true);
        const u32 = (o) => dv.getUint32(o, true);

        // Find EOCD by scanning from end (max comment 64k)
        let eocd = -1;
        for (let i = zipU8.length - 22; i >= Math.max(0, zipU8.length - 22 - 65535); i--) {
            if (u32(i) === 0x06054b50) { eocd = i; break; }
        }
        if (eocd < 0) return [];

        const cdSize = u32(eocd + 12);
        const cdOff = u32(eocd + 16);

        let ptr = cdOff;
        const files = [];
        const dec = new TextDecoder();

        const canInflate = (typeof DecompressionStream !== 'undefined');

        while (ptr < cdOff + cdSize) {
            if (u32(ptr) !== 0x02014b50) break;

            const method = u16(ptr + 10);
            const cSize = u32(ptr + 20);
            const uSize = u32(ptr + 24);
            const nLen = u16(ptr + 28);
            const xLen = u16(ptr + 30);
            const cLen = u16(ptr + 32);
            const lho = u32(ptr + 42);

            const name = dec.decode(zipU8.subarray(ptr + 46, ptr + 46 + nLen));

            // Local header
            if (u32(lho) !== 0x04034b50) { ptr += 46 + nLen + xLen + cLen; continue; }
            const lnLen = u16(lho + 26);
            const lxLen = u16(lho + 28);
            const dataOff = lho + 30 + lnLen + lxLen;

            const comp = zipU8.subarray(dataOff, dataOff + cSize);

            let out;
            if (method === 0) {
                out = comp;
            } else if (method === 8 && canInflate) {
                // deflate (raw)
                try {
                    const ds = new DecompressionStream('deflate-raw');
                    const stream = new Blob([comp]).stream().pipeThrough(ds);
                    const ab = await new Response(stream).arrayBuffer();
                    out = new Uint8Array(ab);
                } catch (e) {
                    if (typeof setStatus === 'function') setStatus('Import failed (ZIP deflate unsupported).');
                    out = null;
                }
            } else {
                if (typeof setStatus === 'function') setStatus('Import failed (ZIP compression not supported).');
                out = null;
            }

            if (out && (uSize === 0 || out.length === uSize)) {
                files.push({ name, data: out });
            }

            ptr += 46 + nLen + xLen + cLen;
        }

        return files;
    }

    // Profile notification system
    let notificationTimeout = null;

    function createNotificationElement() {
        let notif = document.getElementById(NOTIFICATION_ID);
        if (notif) return notif;

        notif = document.createElement('div');
        notif.id = NOTIFICATION_ID;
        notif.style.cssText = `
            position: fixed;
            top: 20px;
            left: 20px;
            background: rgba(0, 0, 0, 0.85);
            color: #fff;
            padding: 12px 24px;
            border-radius: 30px;
            font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
            font-size: 16px;
            font-weight: 900;
            z-index: 2147483647;
            display: none;
            align-items: center;
            gap: 12px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.5);
            border: 2px solid #2a6fdb;
            backdrop-filter: blur(5px);
            pointer-events: none;
            transform: translateZ(0);
            letter-spacing: 0.5px;
        `;

        const icon = document.createElement('span');
        icon.textContent = '🎬';
        icon.style.cssText = `
            font-size: 20px;
            filter: drop-shadow(0 0 5px #2a6fdb);
        `;

        const text = document.createElement('span');
        text.id = 'gvf-notification-text';
        text.textContent = 'Profile: Default';

        notif.appendChild(icon);
        notif.appendChild(text);
        document.body.appendChild(notif);

        return notif;
    }

    function clearNotificationTextNode(node) {
        if (!node) return;
        while (node.firstChild) node.removeChild(node.firstChild);
    }

    function prettySettingName(key) {
        const map = {
            gvf_enabled: 'Enabled',
            gvf_moody: 'Dark Moody',
            gvf_teal: 'Teal Orange',
            gvf_vib: 'Vibrant Saturation',
            gvf_icons: 'Icons',
            gvf_sl: 'Sharpen Level',
            gvf_sr: 'Sharpen Radius',
            gvf_bl: 'Black Level',
            gvf_wl: 'White Level',
            gvf_dn: 'Denoise',
            gvf_edge: 'Edge Detection',
            gvf_hdr: 'HDR',
            gvf_profile: 'Profile',
            gvf_g_hud: 'Grading HUD',
            gvf_i_hud: 'IO HUD',
            gvf_s_hud: 'Scopes HUD',
            gvf_render_mode: 'Render Mode',
            gvf_u_contrast: 'Contrast',
            gvf_u_black: 'Black',
            gvf_u_white: 'White',
            gvf_u_highlights: 'Highlights',
            gvf_u_shadows: 'Shadows',
            gvf_u_saturation: 'Saturation',
            gvf_u_vibrance: 'Vibrance',
            gvf_u_sharpen: 'Sharpen',
            gvf_u_gamma: 'Gamma',
            gvf_u_grain: 'Grain',
            gvf_u_hue: 'Hue',
            gvf_u_r_gain: 'Red Gain',
            gvf_u_g_gain: 'Green Gain',
            gvf_u_b_gain: 'Blue Gain',
            gvf_auto_on: 'Auto-Scene-Match',
            gvf_auto_strength: 'Auto Strength',
            gvf_auto_lock_wb: 'Auto Lock WB',
            gvf_notify: 'Notify',
            gvf_logs: 'Logs',
            gvf_debug: 'Debug',
            gvf_cb_filter: 'Color Blind Filter',
            enabled: 'Enabled',
            darkMoody: 'Dark Moody',
            tealOrange: 'Teal Orange',
            vibrantSat: 'Vibrant Saturation',
            sl: 'Sharpen Level',
            sr: 'Sharpen Radius',
            bl: 'Black Level',
            wl: 'White Level',
            dn: 'Denoise',
            edge: 'Edge Detection',
            hdr: 'HDR',
            profile: 'Profile',
            renderMode: 'Render Mode',
            autoOn: 'Auto-Scene-Match',
            autoStrength: 'Auto Strength',
            autoLockWB: 'Auto Lock WB',
            notify: 'Notify',
            logs: 'Logs',
            debug: 'Debug',
            cbFilter: 'Color Blind Filter'
        };
        if (Object.prototype.hasOwnProperty.call(map, key)) return map[key];
        return String(key || 'Setting')
            .replace(/^gvf_/, '')
            .replace(/_/g, ' ')
            .replace(/\b\w/g, c => c.toUpperCase());
    }

    function resolveReasonValue(reason, currentSettings) {
        const key = String(reason || '').trim();
        if (!key) return undefined;

        const gmToInternal = {
            gvf_enabled: 'enabled',
            gvf_moody: 'darkMoody',
            gvf_teal: 'tealOrange',
            gvf_vib: 'vibrantSat',
            gvf_icons: 'iconsShown',
            gvf_sl: 'sl',
            gvf_sr: 'sr',
            gvf_bl: 'bl',
            gvf_wl: 'wl',
            gvf_dn: 'dn',
            gvf_edge: 'edge',
            gvf_hdr: 'hdr',
            gvf_profile: 'profile',
            gvf_g_hud: 'gradingHudShown',
            gvf_i_hud: 'ioHudShown',
            gvf_s_hud: 'scopesHudShown',
            gvf_render_mode: 'renderMode',
            gvf_u_contrast: 'u_contrast',
            gvf_u_black: 'u_black',
            gvf_u_white: 'u_white',
            gvf_u_highlights: 'u_highlights',
            gvf_u_shadows: 'u_shadows',
            gvf_u_saturation: 'u_sat',
            gvf_u_vibrance: 'u_vib',
            gvf_u_sharpen: 'u_sharp',
            gvf_u_gamma: 'u_gamma',
            gvf_u_grain: 'u_grain',
            gvf_u_hue: 'u_hue',
            gvf_u_r_gain: 'u_r_gain',
            gvf_u_g_gain: 'u_g_gain',
            gvf_u_b_gain: 'u_b_gain',
            gvf_auto_on: 'autoOn',
            gvf_auto_strength: 'autoStrength',
            gvf_auto_lock_wb: 'autoLockWB',
            gvf_notify: 'notify',
            gvf_logs: 'logs',
            gvf_debug: 'debug',
            gvf_cb_filter: 'cbFilter'
        };

        const directKey = gmToInternal[key] || key;
        if (currentSettings && Object.prototype.hasOwnProperty.call(currentSettings, directKey)) {
            return currentSettings[directKey];
        }

        switch (directKey) {
            case 'enabled': return enabled;
            case 'darkMoody': return darkMoody;
            case 'tealOrange': return tealOrange;
            case 'vibrantSat': return vibrantSat;
            case 'autoOn': return autoOn;
            case 'autoLockWB': return autoLockWB;
            case 'notify': return notify;
            case 'logs': return logs;
            case 'debug': return debug;
            default: return undefined;
        }
    }

    function showScreenNotification(message, options = null) {
        const notif = createNotificationElement();
        const textEl = document.getElementById('gvf-notification-text');

        if (textEl) {
            clearNotificationTextNode(textEl);

            if (options && typeof options === 'object') {
                const titleLine = document.createElement('div');
                titleLine.textContent = String(options.title || message || '').trim() || 'Saved';
                textEl.appendChild(titleLine);

                const detail = String(options.detail || '').trim();
                if (detail) {
                    const detailLine = document.createElement('div');
                    detailLine.textContent = detail;
                    detailLine.style.marginTop = '4px';
                    detailLine.style.fontSize = '14px';
                    detailLine.style.fontWeight = '900';
                    if (options.detailColor) detailLine.style.color = String(options.detailColor);
                    textEl.appendChild(detailLine);
                }
            } else {
                textEl.textContent = String(message || '').trim() || 'Saved';
            }
        }

        if (notificationTimeout) {
            clearTimeout(notificationTimeout);
        }

        notif.style.display = 'flex';

        notificationTimeout = setTimeout(() => {
            notif.style.display = 'none';
            notificationTimeout = null;
        }, 3000);
    }

    function showProfileNotification(profileName) {
        showScreenNotification(`Profile: ${profileName}`);
    }

    function showToggleNotification(label, isEnabled, detail = '') {
        showScreenNotification('', {
            title: String(label || 'Toggle').trim() || 'Toggle',
            detail: detail || ((isEnabled ? 'Enabled' : 'Disabled')),
            detailColor: isEnabled ? '#4cff6a' : '#ff4c4c'
        });
    }

    function showValueNotification(label, valueText, color = '#88ccff') {
        showScreenNotification('', {
            title: String(label || 'Value').trim() || 'Value',
            detail: String(valueText || '').trim(),
            detailColor: color
        });
    }

    function showProfileCycleNotification(profileName) {
        showScreenNotification('', {
            title: 'Profile Cycle',
            detail: String(profileName || 'Default').trim() || 'Default',
            detailColor: '#88ccff'
        });
    }

    function showAutoSaveNotification(profileName, reason, currentSettings) {
        const readableName = prettySettingName(reason);
        const resolvedValue = resolveReasonValue(reason, currentSettings);
        const reasonKey = String(reason || '').trim();
        const isHdrReason = (reasonKey === 'hdr' || reasonKey === K.HDR);
        const isRenderModeReason = (reasonKey === 'renderMode' || reasonKey === K.RENDER_MODE);
        let detailText = readableName;
        let detailColor = '#ffffff';

        if (typeof resolvedValue === 'boolean') {
            if (resolvedValue) {
                detailText = `${readableName} enabled`;
                detailColor = '#4cff6a';
            } else {
                detailText = `${readableName} disabled`;
                detailColor = '#ff4c4c';
            }
        } else if (isHdrReason) {
            const hdrValue = Number(resolvedValue);
            if (Number.isFinite(hdrValue) && Math.abs(hdrValue) > 0.0001) {
                detailText = `${readableName} enabled (${hdrValue.toFixed(2)})`;
                detailColor = '#4cff6a';
            } else {
                detailText = `${readableName} disabled`;
                detailColor = '#ff4c4c';
            }
        } else if (isRenderModeReason) {
            const modeValue = String(resolvedValue || '').toLowerCase();
            if (modeValue === 'gpu') {
                detailText = `${readableName}: GPU / WebGL2 Canvas Pipeline`;
                detailColor = '#4cff6a';
            } else {
                detailText = `${readableName}: SVG`;
                detailColor = '#88ccff';
            }
        }

        showScreenNotification('', {
            title: `Profile "${profileName}" saved`,
            detail: detailText,
            detailColor: detailColor
        });
    }

    let _autoSaveProfileTimer = null;
    let _autoSaveProfileReason = '';

    function writeCurrentSettingsIntoActiveProfile(saveToStorage = false) {
        if (!activeUserProfile) return null;

        const currentSettings = stripUiOnlySettings(getCurrentSettings());
        const prevSettings = stripUiOnlySettings(activeUserProfile && activeUserProfile.settings ? activeUserProfile.settings : {});
        const changed = !settingsEqualNormalized(prevSettings, currentSettings);

        const nextProfile = {
            ...activeUserProfile,
            settings: JSON.parse(JSON.stringify(currentSettings)),
            updatedAt: changed ? Date.now() : Number(activeUserProfile && activeUserProfile.updatedAt ? activeUserProfile.updatedAt : Date.now())
        };

        const idx = Array.isArray(userProfiles)
            ? userProfiles.findIndex(p => p && p.id === nextProfile.id)
            : -1;

        if (idx >= 0) {
            userProfiles[idx] = nextProfile;
            activeUserProfile = userProfiles[idx];
        } else {
            userProfiles = normalizeUserProfilesForStorage([...(Array.isArray(userProfiles) ? userProfiles : []), nextProfile]);
            activeUserProfile = userProfiles.find(p => p.id === nextProfile.id) || nextProfile;
        }

        if (saveToStorage && changed) {
            saveUserProfiles();
        }

        return changed ? activeUserProfile : null;
    }

    function updateCurrentProfileSettings(force = false) {
        void force;
        if (!activeUserProfile || _isSwitchingUserProfile) return false;

        const changed = !!writeCurrentSettingsIntoActiveProfile(true);
        return changed;
    }

    function scheduleAutoSaveCurrentProfile(reason = '') {
        void reason;
        if (_autoSaveProfileTimer) {
            clearTimeout(_autoSaveProfileTimer);
            _autoSaveProfileTimer = null;
        }
        _autoSaveProfileReason = '';
        return false;
    }

    function getCurrentSettings() {
        return {
            enabled: enabled,
            notify: notify,
            darkMoody: darkMoody,
            tealOrange: tealOrange,
            vibrantSat: vibrantSat,
            sl: sl,
            sr: sr,
            bl: bl,
            wl: wl,
            dn: dn,
            edge: edge,
            hdr: hdr,
            profile: profile,
            renderMode: renderMode,
            lutProfile: activeLutProfileKey,
            autoOn: autoOn,
            autoStrength: autoStrength,
            autoLockWB: autoLockWB,
            u_contrast: u_contrast,
            u_black: u_black,
            u_white: u_white,
            u_highlights: u_highlights,
            u_shadows: u_shadows,
            u_sat: u_sat,
            u_vib: u_vib,
            u_sharp: u_sharp,
            u_gamma: u_gamma,
            u_grain: u_grain,
            u_hue: u_hue,
            u_r_gain: u_r_gain,
            u_g_gain: u_g_gain,
            u_b_gain: u_b_gain,
            debug: debug,
            logs: logs,
            cbFilter: cbFilter
        };
    }

    function applyUserProfileSettings(settings) {
        _suspendSync = true;
        _inSync = true;
        _isApplyingUserProfileSettings = true;
        suppressValueSync(700);

        try {
            enabled = settings.enabled ?? enabled;
            notify = settings.notify ?? notify;
            darkMoody = settings.darkMoody ?? darkMoody;
            tealOrange = settings.tealOrange ?? tealOrange;
            vibrantSat = settings.vibrantSat ?? vibrantSat;

            sl = settings.sl ?? sl;
            sr = settings.sr ?? sr;
            bl = settings.bl ?? bl;
            wl = settings.wl ?? wl;
            dn = settings.dn ?? dn;
            edge = settings.edge ?? edge;

            hdr = settings.hdr ?? hdr;
            profile = settings.profile ?? profile;
            renderMode = settings.renderMode ?? renderMode;

            // Restore LUT profile for this user profile (if present)
            if (Object.prototype.hasOwnProperty.call(settings, 'lutProfile')) {
                const lpRaw = String(settings.lutProfile || 'none').trim() || 'none';
                try { setActiveLutProfile(lpRaw, undefined, { skipProfileSave: true, skipVisualApply: true }); } catch (_) { }
            }

            autoOn = settings.autoOn ?? autoOn;
            autoStrength = settings.autoStrength ?? autoStrength;
            autoLockWB = settings.autoLockWB ?? autoLockWB;

            u_contrast = settings.u_contrast ?? u_contrast;
            u_black = settings.u_black ?? u_black;
            u_white = settings.u_white ?? u_white;
            u_highlights = settings.u_highlights ?? u_highlights;
            u_shadows = settings.u_shadows ?? u_shadows;
            u_sat = settings.u_sat ?? u_sat;
            u_vib = settings.u_vib ?? u_vib;
            u_sharp = settings.u_sharp ?? u_sharp;
            u_gamma = settings.u_gamma ?? u_gamma;
            u_grain = settings.u_grain ?? u_grain;
            u_hue = settings.u_hue ?? u_hue;

            u_r_gain = settings.u_r_gain ?? u_r_gain;
            u_g_gain = settings.u_g_gain ?? u_g_gain;
            u_b_gain = settings.u_b_gain ?? u_b_gain;

            cbFilter = settings.cbFilter ?? cbFilter;

            // Save in GM
            gmSet(K.enabled, enabled);
            gmSet(K.NOTIFY, notify);
            gmSet(K.moody, darkMoody);
            gmSet(K.teal, tealOrange);
            gmSet(K.vib, vibrantSat);
            gmSet(K.icons, iconsShown);

            gmSet(K.SL, sl);
            gmSet(K.SR, sr);
            gmSet(K.BL, bl);
            gmSet(K.WL, wl);
            gmSet(K.DN, dn);
            gmSet(K.EDGE, edge);

            gmSet(K.HDR, hdr);
            if (hdr !== 0) gmSet(K.HDR_LAST, hdr);

            gmSet(K.PROF, profile);
            gmSet(K.RENDER_MODE, renderMode);
            gmSet(K.NOTIFY, notify);

            gmSet(K.AUTO_ON, autoOn);
            gmSet(K.AUTO_STRENGTH, autoStrength);
            gmSet(K.AUTO_LOCK_WB, autoLockWB);

            gmSet(K.U_CONTRAST, u_contrast);
            gmSet(K.U_BLACK, u_black);
            gmSet(K.U_WHITE, u_white);
            gmSet(K.U_HIGHLIGHTS, u_highlights);
            gmSet(K.U_SHADOWS, u_shadows);
            gmSet(K.U_SAT, u_sat);
            gmSet(K.U_VIB, u_vib);
            gmSet(K.U_SHARP, u_sharp);
            gmSet(K.U_GAMMA, u_gamma);
            gmSet(K.U_GRAIN, u_grain);
            gmSet(K.U_HUE, u_hue);

            gmSet(K.U_R_GAIN, u_r_gain);
            gmSet(K.U_G_GAIN, u_g_gain);
            gmSet(K.U_B_GAIN, u_b_gain);

            gmSet(K.LOGS, logs);
            gmSet(K.DEBUG, debug);
            gmSet(K.CB_FILTER, cbFilter);

            // Apply filter
            if (renderMode === 'gpu') {
                applyGpuFilter();
            } else {
                regenerateSvgImmediately();
            }

            setAutoOn(autoOn, { silent: true });
            scheduleOverlayUpdate();

            log('Profile settings applied');
        } finally {
            _isApplyingUserProfileSettings = false;
            _inSync = false;
            _suspendSync = false;
        }
    }

    // -------------------------
    // INSTANT SVG REGENERATION
    // -------------------------
    let _svgNeedsRegeneration = false;

    function regenerateSvgImmediately() {
        if (_svgNeedsRegeneration) return;
        _svgNeedsRegeneration = true;
        try {
            ensureSvgFilter(true);
            applyFilter({ skipSvgIfPossible: false });
        } finally {
            _svgNeedsRegeneration = false;
        }
    }

    /**
     * Renders a video frame onto destCanvas with the given profile settings applied,
     * WITHOUT touching live global variables or the live SVG filter in the DOM.
     * Uses a temporary hidden SVG filter that is inserted and immediately removed.
     * @param {HTMLCanvasElement} destCanvas  - target canvas (should be 1280x720)
     * @param {HTMLCanvasElement} srcCanvas   - pre-captured raw frame canvas
     * @param {object} settings               - profile.settings object
     */
    function renderFrameWithSettings(destCanvas, srcCanvas, settings) {
        if (!destCanvas || !srcCanvas) return;
        const s = settings || {};
        const LW = destCanvas.width, LH = destCanvas.height;

        // --- Build temporary SVG filter from settings without changing globals ---
        const tmpSvgId  = 'gvf-preview-tmp-svg-' + Math.random().toString(36).slice(2);
        const tmpFiltId = 'gvf-preview-tmp-filt';

        // Compute all the values locally from settings (mirrors ensureSvgFilter logic)
        const _clamp  = (n,a,b) => Math.min(b, Math.max(a, n));
        const _round  = (n,s) => Math.round(n/s)*s;
        const _snap0  = (n,e) => Math.abs(n) <= e ? 0 : n;
        const _normSL = () => _snap0(_round(_clamp(Number(s.sl)||0,-2,2),0.01),0.005);
        const _normSR = () => _snap0(_round(_clamp(Number(s.sr)||0,-2,2),0.01),0.005);
        const _normBL = () => _snap0(_round(_clamp(Number(s.bl)||0,-2,2),0.01),0.005);
        const _normWL = () => _snap0(_round(_clamp(Number(s.wl)||0,-2,2),0.01),0.005);
        const _normDN = () => _snap0(_round(_clamp(Number(s.dn)||0,-1.5,1.5),0.01),0.005);
        const _normHDR= () => _snap0(_round(_clamp(Number(s.hdr)||0,-1,2),0.01),0.005);
        const _normED = () => _snap0(_round(_clamp(Number(s.edge)||0,0,1),0.01),0.005);
        const _normU  = (v) => _round(_clamp(Number(v)||0,-10,10),0.1);
        const _normRGB= (v) => _clamp(Math.round(Number(v)||128),0,255);

        const SL  = Number(_normSL().toFixed(1));
        const SR  = Number(_normSR().toFixed(1));
        const R   = Number(Math.max(0.1, Math.abs(_normSR())).toFixed(1));
        const A   = Number(Math.max(0, _normSL()).toFixed(3));
        const BS  = Number(Math.max(0, -_normSL()).toFixed(3));
        const BL  = Number(_normBL().toFixed(1));
        const WL  = Number(_normWL().toFixed(1));
        const DN  = Number(_normDN().toFixed(1));
        const HDR = Number(_normHDR().toFixed(2));
        const EDGE= Number(_normED().toFixed(2));
        const P   = String(s.profile || 'off');
        const CB  = String(s.cbFilter || 'none');
        const _bOff = _clamp(BL,-2,2)*0.04;
        const _wAdj = _clamp(WL,-2,2)*0.06;

        const moody = !!s.darkMoody, teal = !!s.tealOrange, vib = !!s.vibrantSat;

        // Pick combo filter id
        const comboSuffix = (moody?'m':'')+(teal?'t':'')+(vib?'v':'');
        const tmpComboId = tmpFiltId + (comboSuffix ? '_' + comboSuffix : '');

        // Build temp SVG with the needed combo filter
        const tmpSvg = document.createElementNS(svgNS, 'svg');
        tmpSvg.id = tmpSvgId;
        tmpSvg.setAttribute('width','0'); tmpSvg.setAttribute('height','0');
        tmpSvg.style.cssText = 'position:absolute;left:-99999px;top:-99999px;pointer-events:none;';

        try {
            buildFilter(tmpSvg, tmpComboId, { moody, teal, vib }, R, A, BS, _bOff, _wAdj, DN, EDGE, HDR, P);
        } catch(_) {}

        (document.body || document.documentElement).appendChild(tmpSvg);

        // Build CSS filter string
        const baseTone = (s.enabled !== false) ? ' brightness(1.02) contrast(1.05) saturate(1.21)' : '';
        let profTone = '';
        if (P==='film')    profTone = ' brightness(1.01) contrast(1.08) saturate(1.08)';
        if (P==='anime')   profTone = ' brightness(1.03) contrast(1.10) saturate(1.16)';
        if (P==='gaming')  profTone = ' brightness(1.01) contrast(1.12) saturate(1.06)';
        if (P==='eyecare') profTone = ' brightness(1.05) contrast(0.96) saturate(0.88) hue-rotate(-12deg)';
        let userTone = '';
        if (P==='user') {
            const uc=_normU(s.u_contrast),us=_normU(s.u_sat),uv=_normU(s.u_vib),uh=_normU(s.u_hue);
            const ub=_normU(s.u_black),uw=_normU(s.u_white),ush=_normU(s.u_shadows),uhi=_normU(s.u_highlights),ug=_normU(s.u_gamma);
            const c   = _clamp(1.0+uc*0.04,0.6,1.6);
            const sat = _clamp(1.0+us*0.05,0.4,1.8);
            const vb  = _clamp(1.0+uv*0.02,0.7,1.35);
            const hue = _clamp(uh*3.0,-30,30);
            const blk = _clamp(ub*0.012,-0.12,0.12);
            const wht = _clamp(uw*0.012,-0.12,0.12);
            const sh  = _clamp(ush*0.010,-0.10,0.10);
            const hi  = _clamp(uhi*0.010,-0.10,0.10);
            const br  = _clamp(1.0+(-blk+wht+sh+hi)*0.6,0.7,1.35);
            const g   = _clamp(1.0+ug*0.025,0.6,1.6);
            const gBr = _clamp(1.0+(1.0-g)*0.18,0.85,1.2);
            const gCt = _clamp(1.0+(g-1.0)*0.10,0.9,1.15);
            userTone = ` brightness(${(br*gBr).toFixed(3)}) contrast(${(c*gCt).toFixed(3)}) saturate(${(sat*vb).toFixed(3)}) hue-rotate(${hue.toFixed(1)}deg)`;
        }

        const filterStr = `url("#${tmpComboId}")${baseTone}${profTone}${userTone}`;

        // Render srcCanvas → destCanvas with filter
        try {
            const ctx = destCanvas.getContext('2d', { alpha: false });
            if (ctx) {
                ctx.save();
                ctx.filter = filterStr;
                ctx.drawImage(srcCanvas, 0, 0, LW, LH);
                ctx.restore();
            }
        } catch(_) {} finally {
            // Always remove temp SVG immediately
            try { tmpSvg.remove(); } catch(_) {}
        }
    }


    /**
     * Determines the currently active filter string for screenshots/recordings
     */
    function getCurrentFilterString() {
        try {
            // First, try to extract the current CSS filter from the style element.
            const style = document.getElementById(STYLE_ID);
            if (style && style.textContent) {
                const match = style.textContent.match(/filter:\s*([^!;]+)/);
                if (match && match[1]) {
                    return match[1].trim();
                }
            }

            // Fallback: Create the filter string based on the current settings
            if (renderMode === 'gpu') {
                return getGpuFilterString();
            } else {
                // For SVG mode: Return the URL filter
                const comboId = pickComboId();
                return `url("#${comboId}")${getBaseToneString()}${getProfileToneString()}${getUserToneString()}`;
            }
        } catch (e) {
            logW('Error determining the filter string:', e);
            return 'none';
        }
    }

    function getBaseToneString() {
        return enabled ? ' brightness(1.02) contrast(1.05) saturate(1.21)' : '';
    }

    function getProfileToneString() {
        if (profile === 'film') return ' brightness(1.01) contrast(1.08) saturate(1.08)';
        if (profile === 'anime') return ' brightness(1.03) contrast(1.10) saturate(1.16)';
        if (profile === 'gaming') return ' brightness(1.01) contrast(1.12) saturate(1.06)';
        if (profile === 'eyecare') return ' brightness(1.05) contrast(0.96) saturate(0.88) hue-rotate(-12deg)';
        return '';
    }

    function getUserToneString() {
        if (profile !== 'user') return '';

        const c = clamp(1.0 + (uDelta(u_contrast) * 0.04), 0.60, 1.60);
        const sat = clamp(1.0 + (uDelta(u_sat) * 0.05), 0.40, 1.80);
        const vib = clamp(1.0 + (uDelta(u_vib) * 0.02), 0.70, 1.35);
        const hue = clamp(uDelta(u_hue) * 3.0, -30, 30);

        const blk = clamp(uDelta(u_black) * 0.012, -0.12, 0.12);
        const wht = clamp(uDelta(u_white) * 0.012, -0.12, 0.12);
        const sh = clamp(uDelta(u_shadows) * 0.010, -0.10, 0.10);
        const hi = clamp(uDelta(u_highlights) * 0.010, -0.10, 0.10);

        const br = clamp(1.0 + (-blk + wht + sh + hi) * 0.6, 0.70, 1.35);

        const g = clamp(1.0 + (uDelta(u_gamma) * 0.025), 0.60, 1.60);
        const gBr = clamp(1.0 + (1.0 - g) * 0.18, 0.85, 1.20);
        const gCt = clamp(1.0 + (g - 1.0) * 0.10, 0.90, 1.15);

        return ` brightness(${(br * gBr).toFixed(3)}) contrast(${(c * gCt).toFixed(3)}) saturate(${(sat * vib).toFixed(3)}) hue-rotate(${hue.toFixed(1)}deg)`;
    }


    // -------------------------
    // Screenshot / Recording helpers
    // -------------------------
    function dlBlob(blob, filename) {
        try {
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            a.remove();
            setTimeout(() => URL.revokeObjectURL(url), 1500);
        } catch (_) { }
    }

    function tsName(prefix, ext) {
        const d = new Date();
        const pad = (n) => String(n).padStart(2, '0');
        return `${prefix}_${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}_${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}.${ext}`;
    }

    function getActiveVideoForCapture() {
        try {
            const v = (typeof choosePrimaryVideo === 'function') ? choosePrimaryVideo() : null;
            if (v) return v;
        } catch (_) { }
        return document.querySelector('video');
    }

    function getAppliedCssFilterString(video) {
        try {
            const cs = window.getComputedStyle(video);
            let f = String(cs.filter || '').trim();
            if (!f || f === 'none') return '';
            f = f.replace(/url\([^)]+\)/g, '').replace(/\s+/g, ' ').trim();
            if (!f || f === 'none') return '';
            return f;
        } catch (_) {
            return '';
        }
    }

    function canBakeToCanvas(video) {
        try {
            const w = Math.max(2, video.videoWidth || 0);
            const h = Math.max(2, video.videoHeight || 0);
            if (!w || !h) return { ok: false, reason: 'Video not ready.' };

            const c = document.createElement('canvas');
            c.width = 2; c.height = 2;
            const ctx = c.getContext('2d');
            ctx.drawImage(video, 0, 0, 2, 2);
            ctx.getImageData(0, 0, 1, 1);
            return { ok: true, reason: '' };
        } catch (_) {
            return { ok: false, reason: 'Blocked (DRM/cross-origin).' };
        }
    }

    // -------------------------
    // Firefox audio tap
    // -------------------------
    const AUDIO_TAPS = new WeakMap();

    function ensureAudioTap(video) {
        try {
            if (!video) return null;

            const existing = AUDIO_TAPS.get(video);
            if (existing && existing.dest && existing.dest.stream) {
                const tracks = existing.dest.stream.getAudioTracks ? existing.dest.stream.getAudioTracks() : [];
                if (tracks && tracks.length) return { tracks, note: 'webaudio' };
            }

            const AC = window.AudioContext || window.webkitAudioContext;
            if (!AC) return null;

            const ctx = new AC({ latencyHint: 'interactive' });
            const src = ctx.createMediaElementSource(video);
            const gain = ctx.createGain();
            gain.gain.value = 1.0;

            const dest = ctx.createMediaStreamDestination();

            src.connect(gain);
            gain.connect(ctx.destination);
            gain.connect(dest);

            const tap = { ctx, src, gain, dest };
            AUDIO_TAPS.set(video, tap);

            const tracks = dest.stream.getAudioTracks ? dest.stream.getAudioTracks() : [];
            if (!tracks || !tracks.length) return null;

            tracks.forEach(t => { try { t.__gvfNoStop = true; } catch (_) { } });
            return { tracks, note: 'webaudio' };
        } catch (_) {
            return null;
        }
    }

    async function resumeAudioContextsFor(video) {
        try {
            const tap = AUDIO_TAPS.get(video);
            if (tap && tap.ctx && tap.ctx.state === 'suspended') {
                await tap.ctx.resume();
            }
        } catch (_) { }
    }

    // ---------- Canvas pipeline for recording ----------
    const REC_PIPE = {
        active: false,
        v: null,
        canvas: null,
        ctx: null,
        raf: 0,
        stream: null,
        lastDraw: 0,
        fps: 60,
        audioTracks: [],
        stopFn: null
    };

    function stopCanvasRecorderPipeline() {
        try { if (REC_PIPE.raf) cancelAnimationFrame(REC_PIPE.raf); } catch (_) { }
        REC_PIPE.raf = 0;

        try {
            REC_PIPE.audioTracks.forEach(t => {
                try { if (t && !t.__gvfNoStop) t.stop(); } catch (_) { }
            });
        } catch (_) { }
        REC_PIPE.audioTracks = [];

        try {
            if (REC_PIPE.stream) {
                REC_PIPE.stream.getTracks().forEach(t => {
                    try { if (t && !t.__gvfNoStop) t.stop(); } catch (_) { }
                });
            }
        } catch (_) { }

        REC_PIPE.active = false;
        REC_PIPE.v = null;
        REC_PIPE.stream = null;
        REC_PIPE.canvas = null;
        REC_PIPE.ctx = null;
        REC_PIPE.lastDraw = 0;
        REC_PIPE.stopFn = null;
    }

    function startCanvasRecorderPipeline(video, statusEl) {
        const w = Math.max(2, video.videoWidth || 0);
        const h = Math.max(2, video.videoHeight || 0);
        if (!w || !h) return null;

        const c = document.createElement('canvas');
        c.width = w;
        c.height = h;

        const ctx = c.getContext('2d', { alpha: false, desynchronized: true });
        if (!ctx) return null;

        ctx.imageSmoothingEnabled = true;
        try { ctx.imageSmoothingQuality = 'high'; } catch (_) { }

        // FIX: Use the correct filter string for recording
        const filterString = getCurrentFilterString();
        log('Using filter for recording:', filterString);

        const draw = (t) => {
            if (!REC_PIPE.active) return;

            const dt = t - (REC_PIPE.lastDraw || 0);
            const minDt = 1000 / Math.max(10, REC_PIPE.fps);
            if (dt < (minDt * 0.55)) {
                REC_PIPE.raf = requestAnimationFrame(draw);
                return;
            }
            REC_PIPE.lastDraw = t;

            try {
                ctx.save();
                ctx.filter = filterString || 'none';
                ctx.drawImage(video, 0, 0, w, h);
                ctx.restore();
            } catch (e) {
                if (statusEl) statusEl.textContent = 'Recording stopped: blocked (DRM/cross-origin).';
                // FIX: Evaluate REC.stopRequested
                REC.stopRequested = true;
                if (REC.mr && REC.mr.state === 'recording') {
                    try { REC.mr.stop(); } catch (_) { }
                }
                return;
            }

            REC_PIPE.raf = requestAnimationFrame(draw);
        };

        let stream = null;
        try { stream = c.captureStream(REC_PIPE.fps); } catch (_) { return null; }

        let audioTracks = [];
        let audioNote = '';
        try {
            if (isFirefox()) {
                const tap = ensureAudioTap(video);
                if (tap && tap.tracks && tap.tracks.length) {
                    audioTracks = tap.tracks.slice();
                    audioNote = 'Audio: WebAudio tap';
                }
            }
            if (!audioTracks.length) {
                const vs = (video.captureStream && video.captureStream()) || (video.mozCaptureStream && video.mozCaptureStream());
                if (vs) {
                    const at = vs.getAudioTracks ? vs.getAudioTracks() : [];
                    if (at && at.length) {
                        audioTracks = at.slice();
                        audioNote = 'Audio: captureStream';
                    }
                }
            }
        } catch (_) { }

        try {
            (audioTracks || []).forEach(at => {
                try {
                    stream.addTrack(at);
                    REC_PIPE.audioTracks.push(at);
                } catch (_) { }
            });
        } catch (_) { }

        REC_PIPE.active = true;
        REC_PIPE.v = video;
        REC_PIPE.canvas = c;
        REC_PIPE.ctx = ctx;
        REC_PIPE.stream = stream;
        REC_PIPE.lastDraw = 0;

        REC_PIPE.raf = requestAnimationFrame(draw);

        if (statusEl && audioTracks.length && audioNote) {
            if (statusEl.textContent && statusEl.textContent.startsWith('Tip:')) {
                statusEl.textContent = audioNote;
            }
        }

        return stream;
    }

    // ---------- Robust recorder ----------
    const REC = {
        active: false,
        stopRequested: false,
        mr: null,
        chunks: [],
        v: null,
        mime: '',
        ext: 'webm',
        startTime: 0,
        timerInterval: null,
        currentVideo: null  // Track current video for HUD positioning
    };

    function pickRecorderMime(hasAudio) {
        const mp4Audio = [
            'video/mp4;codecs=avc1.4D401F,mp4a.40.2',
            'video/mp4;codecs=avc1.4D401F,mp4a.40.2',
            'video/mp4'
        ];
        const webmAudio = [
            'video/webm;codecs=vp9,opus',
            'video/webm;codecs=vp8,opus',
            'video/webm;codecs=vp9',
            'video/webm;codecs=vp8',
            'video/webm'
        ];
        const mp4NoAudio = [
            'video/mp4;codecs=avc1.4D401F',
            'video/mp4;codecs=avc1.4D401F',
            'video/mp4'
        ];
        const webmNoAudio = [
            'video/webm;codecs=vp9',
            'video/webm;codecs=vp8',
            'video/webm'
        ];
        const cands = hasAudio ? [...mp4Audio, ...webmAudio] : [...mp4NoAudio, ...webmNoAudio];
        for (const m of cands) {
            try { if (window.MediaRecorder && MediaRecorder.isTypeSupported && MediaRecorder.isTypeSupported(m)) return m; } catch (_) { }
        }
        return '';
    }

    function getExtFromMime(mime) {
        const m = String(mime || '').toLowerCase();
        if (m.includes('video/mp4')) return 'mp4';
        return 'webm';
    }

    function safeBlobTypeFromRecorder(mr, fallback) {
        try {
            const mt = (mr && mr.mimeType) ? String(mr.mimeType) : '';
            if (mt) return mt;
        } catch (_) { }
        return fallback || 'video/webm';
    }

    // Recording HUD functions
    function createRecordingHUD() {
        let hud = document.getElementById(RECORDING_HUD_ID);
        if (hud) return hud;

        hud = document.createElement('div');
        hud.id = RECORDING_HUD_ID;
        hud.style.cssText = `
            position: absolute;
            top: 10px;
            left: 10px;
            background: rgba(0, 0, 0, 0.8);
            color: #ff4444;
            padding: 6px 12px;
            border-radius: 20px;
            font-family: monospace;
            font-size: 14px;
            font-weight: bold;
            z-index: 2147483647;
            display: none;
            align-items: center;
            gap: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.5);
            border: 1px solid rgba(255, 68, 68, 0.6);
            backdrop-filter: blur(4px);
            pointer-events: none;
            transform: translateZ(0);
            letter-spacing: 0.5px;
        `;

        const redDot = document.createElement('span');
        redDot.style.cssText = `
            width: 12px;
            height: 12px;
            background: #ff4444;
            border-radius: 50%;
            display: inline-block;
            animation: gvf-record-pulse 1.2s ease-in-out infinite;
            box-shadow: 0 0 10px rgba(255, 68, 68, 0.8);
        `;

        const timeDisplay = document.createElement('span');
        timeDisplay.id = 'gvf-record-time';
        timeDisplay.textContent = '00:00';
        timeDisplay.style.cssText = `
            text-shadow: 0 0 5px rgba(255, 68, 68, 0.5);
        `;

        hud.appendChild(redDot);
        hud.appendChild(timeDisplay);

        // Add animation style if not already present
        if (!document.getElementById('gvf-record-style')) {
            const style = document.createElement('style');
            style.id = 'gvf-record-style';
            style.textContent = `
                @keyframes gvf-record-pulse {
                    0% { opacity: 1; transform: scale(1); }
                    50% { opacity: 0.6; transform: scale(1.2); }
                    100% { opacity: 1; transform: scale(1); }
                }
            `;
            document.head.appendChild(style);
        }

        return hud;
    }

    function positionRecordingHUD(video) {
        const hud = document.getElementById(RECORDING_HUD_ID);
        if (!hud || !video) return;


        if (hud.parentNode !== video.parentNode) {
            if (video.parentNode) {
                video.parentNode.appendChild(hud);
            }
        }


        hud.style.position = 'absolute';
        hud.style.top = '10px';
        hud.style.left = '10px';
        hud.style.right = 'auto';
        hud.style.bottom = 'auto';
        hud.style.transform = 'none';
    }

    function updateRecordingTimer() {
        if (!REC.active) return;

        const hud = document.getElementById(RECORDING_HUD_ID);
        if (!hud) return;

        const timeDisplay = document.getElementById('gvf-record-time');
        if (!timeDisplay) return;

        const elapsed = Math.floor((Date.now() - REC.startTime) / 1000);
        const minutes = Math.floor(elapsed / 60);
        const seconds = elapsed % 60;
        timeDisplay.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;


        if (REC.currentVideo && REC.currentVideo.parentNode) {
            positionRecordingHUD(REC.currentVideo);
        }
    }

    function startRecordingTimer(video) {
        REC.startTime = Date.now();
        REC.currentVideo = video;


        const hud = createRecordingHUD();


        if (video.parentNode) {
            video.parentNode.appendChild(hud);
            positionRecordingHUD(video);
        }

        hud.style.display = 'flex';

        if (REC.timerInterval) clearInterval(REC.timerInterval);
        REC.timerInterval = setInterval(updateRecordingTimer, 100);

        log('Recording HUD started');
    }

    function stopRecordingTimer() {
        if (REC.timerInterval) {
            clearInterval(REC.timerInterval);
            REC.timerInterval = null;
        }

        const hud = document.getElementById(RECORDING_HUD_ID);
        if (hud) {
            hud.style.display = 'none';

            if (hud.parentNode) {
                hud.parentNode.removeChild(hud);
            }
        }
        REC.currentVideo = null;
        log('Recording HUD stopped');
    }

    async function takeVideoScreenshot(statusEl) {
        const v = getActiveVideoForCapture();
        if (!v) { if (statusEl) statusEl.textContent = 'No video found.'; return; }

        const w = Math.max(2, v.videoWidth || 0);
        const h = Math.max(2, v.videoHeight || 0);
        if (!w || !h) { if (statusEl) statusEl.textContent = 'Video not ready.'; return; }

        const chk = canBakeToCanvas(v);
        if (!chk.ok) { if (statusEl) statusEl.textContent = `Screenshot blocked: ${chk.reason}`; return; }

        const c = document.createElement('canvas');
        c.width = w;
        c.height = h;
        const ctx = c.getContext('2d', { alpha: false, desynchronized: true });
        if (!ctx) { if (statusEl) statusEl.textContent = 'Canvas unavailable.'; return; }

        ctx.imageSmoothingEnabled = true;
        try { ctx.imageSmoothingQuality = 'high'; } catch (_) { }

        // FIX: Use the correct filter string for screenshots
        const filterString = getCurrentFilterString();
        log('Using filter for screenshot:', filterString);

        try {
            ctx.save();
            ctx.filter = filterString || 'none';
            ctx.drawImage(v, 0, 0, w, h);
            ctx.restore();
            ctx.getImageData(0, 0, 1, 1);
        } catch (_) {
            if (statusEl) statusEl.textContent = 'Screenshot blocked (cross-origin/DRM).';
            return;
        }

        c.toBlob((blob) => {
            if (!blob) { if (statusEl) statusEl.textContent = 'Screenshot failed.'; return; }
            const name = tsName('gvf_screenshot', 'png');
            dlBlob(blob, name);
            if (statusEl) statusEl.textContent = `Screenshot saved: ${name}`;
        }, 'image/png');
    }

    async function toggleVideoRecord(statusEl, btnEl) {
        if (REC.active) {
            try {
                REC.stopRequested = true;

                if (btnEl) {
                    btnEl.textContent = 'Stopping...';
                    btnEl.disabled = true;
                    btnEl.style.opacity = '0.6';
                    btnEl.style.cursor = 'not-allowed';
                }
                if (statusEl) statusEl.textContent = 'Finalizing recording...';

                if (REC.mr && REC.mr.state === 'recording') {
                    try { REC.mr.requestData(); } catch (_) { }
                    setTimeout(() => {
                        try { REC.mr.stop(); } catch (_) { }
                    }, 700);
                } else {
                    try { REC.mr && REC.mr.stop(); } catch (_) { }
                }

                stopRecordingTimer();
            } catch (_) { }
            return;
        }

        const v = getActiveVideoForCapture();
        if (!v) { if (statusEl) statusEl.textContent = 'No video found.'; return; }

        const chk = canBakeToCanvas(v);
        if (!chk.ok) {
            if (statusEl) statusEl.textContent = `Recording disabled: ${chk.reason}`;
            if (btnEl) {
                btnEl.disabled = true;
                btnEl.textContent = 'DRM blocked';
                btnEl.style.opacity = '0.55';
                btnEl.style.cursor = 'not-allowed';
            }
            return;
        }

        if (!window.MediaRecorder) {
            if (statusEl) statusEl.textContent = 'MediaRecorder not supported.';
            return;
        }

        try { if (isFirefox()) await resumeAudioContextsFor(v); } catch (_) { }

        const filteredStream = startCanvasRecorderPipeline(v, statusEl);
        if (!filteredStream) {
            if (statusEl) statusEl.textContent = 'Recording not supported (canvas capture failed).';
            return;
        }

        const hasAudio = (() => {
            try { return filteredStream.getAudioTracks && filteredStream.getAudioTracks().length > 0; } catch (_) { }
            return false;
        })();

        const mime = pickRecorderMime(hasAudio);
        if (!mime) {
            stopCanvasRecorderPipeline();
            if (statusEl) statusEl.textContent = 'No supported recording format (mp4/webm).';
            return;
        }

        const ext = getExtFromMime(mime);

        REC.active = true;
        REC.stopRequested = false;
        REC.v = v;
        REC.mime = mime;
        REC.ext = ext;
        REC.chunks = [];

        if (btnEl) {
            btnEl.disabled = false;
            btnEl.textContent = 'Stop Record';
            btnEl.style.opacity = '1';
            btnEl.style.cursor = 'pointer';
        }

        if (statusEl) {
            if (hasAudio) statusEl.textContent = `Recording... (${ext.toUpperCase()})`;
            else statusEl.textContent = `Recording... (${ext.toUpperCase()} (no audio)) — site may block audio capture.`;
        }

        startRecordingTimer(v);

        let mr;
        try {
            const opts = {
                mimeType: mime,
                videoBitsPerSecond: 6_000_000,
                audioBitsPerSecond: 96_000
            };
            mr = new MediaRecorder(filteredStream, opts);
        } catch (_) {
            stopCanvasRecorderPipeline();
            REC.active = false;
            stopRecordingTimer();
            if (btnEl) btnEl.textContent = 'Record';
            if (statusEl) statusEl.textContent = 'Recorder init failed.';
            return;
        }

        REC.mr = mr;

        mr.ondataavailable = (ev) => {
            if (ev && ev.data && ev.data.size > 0) REC.chunks.push(ev.data);
        };

        mr.onerror = () => {
            try { mr.stop(); } catch (_) { }
        };

        mr.onstop = () => {
            setTimeout(() => {
                try {
                    const type = safeBlobTypeFromRecorder(mr, (REC.ext === 'mp4' ? 'video/mp4' : 'video/webm'));
                    const blob = new Blob(REC.chunks, { type });

                    if (!blob || blob.size < 50_000) {
                        if (statusEl) statusEl.textContent = 'Save failed (empty/too small). DRM/cross-origin or tab slept.';
                    } else {
                        const name = tsName('gvf_record', REC.ext);
                        dlBlob(blob, name);

                        if (statusEl) {
                            const note = (REC.ext === 'webm')
                                ? 'Saved (WebM). If Windows player refuses: open with VLC.'
                                : 'Saved (MP4).';
                            statusEl.textContent = `Saved: ${name} — ${note}`;
                        }
                    }
                } catch (e) {
                    if (statusEl) statusEl.textContent = 'Save failed.';
                }

                stopCanvasRecorderPipeline();

                REC.active = false;
                REC.mr = null;
                REC.chunks = [];
                REC.v = null;
                REC.mime = '';
                REC.ext = 'webm';
                REC.stopRequested = false;

                if (btnEl) {
                    btnEl.disabled = false;
                    btnEl.style.opacity = '1';
                    btnEl.style.cursor = 'pointer';
                    btnEl.textContent = 'Record';
                }
            }, 250);
        };

        try { mr.start(); } catch (_) {
            stopCanvasRecorderPipeline();
            stopRecordingTimer();
            if (statusEl) statusEl.textContent = 'Recorder start failed.';
            try { mr.stop(); } catch (__) { }
        }
    }

    // -------------------------
    // DEBUG / LOGGING
    // -------------------------
    const LOG = {
        on: !!logs,
        tag: '[GVF]',
        lastTickMs: 0,
        tickEveryMs: 1000,
        lastToneMs: 0,
        toneEveryMs: 800
    };

    function log(...a) { if (!LOG.on) return; try { console.log(LOG.tag, ...a); } catch (_) { } }
    function logW(...a) { if (!LOG.on) return; try { console.warn(LOG.tag, ...a); } catch (_) { } }
    function logToggle(name, state, extra) { log(`${name}:`, state ? 'ON' : 'OFF', extra || ''); }

    // Debug Toggle Function
    function toggleDebug() {
        debug = !debug;
        logs = debug; // Sync logs with debug
        gmSet(K.DEBUG, debug);
        gmSet(K.LOGS, logs);

        LOG.on = logs;

        logToggle('Debug Mode', debug);
        logToggle('Console Logs', logs);

        // Update Auto-Dot immediately
        setAutoDotState(autoOn ? (debug ? 'idle' : 'off') : 'off');
        scheduleOverlayUpdate();

        // Short confirmation in console
        if (debug) {
            console.log('%c[GVF] Debug Mode ACTIVATED - Visual debug dots visible', 'color: #00ff00; font-weight: bold');
        } else {
            console.log('%c[GVF] Debug Mode DEACTIVATED - Visual debug dots hidden', 'color: #ff6666; font-weight: bold');
        }
    }

    // -------------------------
    // Global state
    // -------------------------
    let enabled = !!gmGet(K.enabled, true);
    let darkMoody = !!gmGet(K.moody, true);
    let tealOrange = !!gmGet(K.teal, false);
    let vibrantSat = !!gmGet(K.vib, false);
    let iconsShown = !!gmGet(K.icons, false);

    const isFirefoxBrowser = isFirefox();

    if (isFirefoxBrowser) {
        var sl = Number(gmGet(K.SL, 1.3));
        var sr = Number(gmGet(K.SR, -1.1));
        var bl = Number(gmGet(K.BL, 0.3));
        var wl = Number(gmGet(K.WL, 0.2));
        var dn = Number(gmGet(K.DN, 0.6));
        var profile = String(gmGet(K.PROF, 'off')).toLowerCase();
    } else {
        var sl = Number(gmGet(K.SL, 1.0));
        var sr = Number(gmGet(K.SR, 0.5));
        var bl = Number(gmGet(K.BL, -1.2));
        var wl = Number(gmGet(K.WL, 0.2));
        var dn = Number(gmGet(K.DN, -0.6));
        var profile = String(gmGet(K.PROF, 'user')).toLowerCase();
    }

    let hdr = Number(gmGet(K.HDR, 0.0));
    let edge = Number(gmGet(K.EDGE, 0.0));

    if (!['off', 'film', 'anime', 'gaming', 'eyecare', 'user'].includes(profile)) profile = 'off';

    let renderMode = String(gmGet(K.RENDER_MODE, 'svg')).toLowerCase();
    if (!['svg', 'gpu'].includes(renderMode)) renderMode = 'svg';

    let gradingHudShown = !!gmGet(K.G_HUD, false);
    let ioHudShown = !!gmGet(K.I_HUD, false);
    let scopesHudShown = !!gmGet(K.S_HUD, false);

    let u_contrast = Number(gmGet(K.U_CONTRAST, 0.0));
    let u_black = Number(gmGet(K.U_BLACK, 0.0));
    let u_white = Number(gmGet(K.U_WHITE, 0.0));
    let u_highlights = Number(gmGet(K.U_HIGHLIGHTS, 0.0));
    let u_shadows = Number(gmGet(K.U_SHADOWS, 0.0));
    let u_sat = Number(gmGet(K.U_SAT, 0.0));
    let u_vib = Number(gmGet(K.U_VIB, 0.0));
    let u_sharp = Number(gmGet(K.U_SHARP, 0.0));
    let u_gamma = Number(gmGet(K.U_GAMMA, 0.0));
    let u_grain = Number(gmGet(K.U_GRAIN, 0.0));
    let u_hue = Number(gmGet(K.U_HUE, 0.0));

    let u_r_gain = Number(gmGet(K.U_R_GAIN, 128));
    let u_g_gain = Number(gmGet(K.U_G_GAIN, 128));
    let u_b_gain = Number(gmGet(K.U_B_GAIN, 128));

    let autoOn = !!gmGet(K.AUTO_ON, true);
    let notify = !!gmGet(K.NOTIFY, true);
    let autoStrength = Number(gmGet(K.AUTO_STRENGTH, 0.65));
    autoStrength = clamp(autoStrength, 0, 1);
    let autoLockWB = !!gmGet(K.AUTO_LOCK_WB, true);

    // Color blindness filter
    let cbFilter = String(gmGet(K.CB_FILTER, 'none')).toLowerCase();
    if (!['none', 'protanopia', 'deuteranopia', 'tritanomaly'].includes(cbFilter)) cbFilter = 'none';

    // Initialize Profile Management
    loadUserProfiles();
    loadLutProfiles();

    const HK = { base: 'b', moody: 'd', teal: 'o', vib: 'v', icons: 'h' };

    function normSL() { return snap0(roundTo(clamp(Number(sl) || 0, -2, 2), 0.01), 0.005); }
    function normSR() { return snap0(roundTo(clamp(Number(sr) || 0, -2, 2), 0.01), 0.005); }
    function normBL() { return snap0(roundTo(clamp(Number(bl) || 0, -2, 2), 0.01), 0.005); }
    function normWL() { return snap0(roundTo(clamp(Number(wl) || 0, -2, 2), 0.01), 0.005); }
    function normDN() { return snap0(roundTo(clamp(Number(dn) || 0, -1.5, 1.5), 0.01), 0.005); }
    function normHDR() { return snap0(roundTo(clamp(Number(hdr) || 0, -1.0, 2.0), 0.01), 0.005); }
    function normEDGE() { return snap0(roundTo(clamp(Number(edge) || 0, 0, 1.0), 0.01), 0.005); }
    function normU(v) { return roundTo(clamp(Number(v) || 0, -10, 10), 0.1); }
    function uDelta(v) { return normU(v); }
    function normRGB(v) { return clamp(Math.round(Number(v) || 128), 0, 255); }
    function rgbGainToFactor(v) { return (normRGB(v) / 128); }

    function getSharpenA() { return Math.max(0, normSL()) * 1.0; }
    function getBlurSigma() { return Math.max(0, -normSL()) * 1.0; }
    function getRadius() { return Math.max(0.1, Math.abs(normSR())); }
    function blackToOffset(v) { return clamp(v, -2, 2) * 0.04; }
    function whiteToHiAdj(v) { return clamp(v, -2, 2) * 0.06; }
    function dnToDenoiseMix(v) { return clamp(v, 0, 1.5) * 0.5; }
    function dnToDenoiseSigma(v) { return clamp(v, 0, 1.5) * 0.8; }
    function dnToGrainAlpha(v) { return clamp(-v, 0, 1.5) * (0.20 / 1.5); }

    const PROF = {
        off: { name: 'Off', color: 'transparent' },
        film: { name: 'Movie', color: '#00b050' },
        anime: { name: 'Anime', color: '#1e6fff' },
        gaming: { name: 'Gaming', color: '#ff2a2a' },
        eyecare: { name: 'EyeCare', color: '#ffaa33' },
        user: { name: 'User', color: '#bfbfbf' }
    };

    const PROFILE_VIDEO_OUTLINE = false;

    // -------------------------
    // Color blindness filter matrices
    // -------------------------
    function getColorBlindnessMatrix(type, strength = 1.0) {

    const k = Math.max(0, Math.min(1, strength));

    // Helper: lerp a matrix towards identity by strength (so k=0 => no change)
    const I = matIdentity4x5();
    const mix = (M) => M.map((v, i) => I[i] + (v - I[i]) * k);

    if (type === 'protanopia') {

        const M = [
            0.9715365562, 0.0000451198, 0.0000221655, 0, 0.0054466531,
            0.4906507166, 0.4721896523, 0.0000465477, 0, 0.0045839181,
            0.4515750655, -0.3950199862, 0.7845041177, 0, 0.0772238079,
            0, 0, 0, 1, 0
        ];
        return mix(M);
    }

    if (type === 'deuteranopia') {

        const M = [
            1.1313510788, -0.2442083804, 0.0000487006, 0, 0.0570148323,
            0.0001729681, 0.9711718674, 0.0000761152, 0, 0.0057285327,
            -0.1474623881, 0.1563820548, 0.9401548784, 0, 0.0166599033,
            0, 0, 0, 1, 0
        ];
        return mix(M);
    }

    if (type === 'tritanomaly' || type === 'tritanopia') {

        const M = [
            0.92, -0.06, 0.00, 0, 0,
            0.04, 1.05, 0.00, 0, 0,
            0.00, -0.02, 1.02, 0, 0,
            0, 0, 0, 1, 0
        ];
        return mix(M);
    }


    return matIdentity4x5();
    }

    // -------------------------
    // 5x5 Color Matrix utils
    // -------------------------
    const LUMA = { r: 0.2126, g: 0.7152, b: 0.0722 };

    function matIdentity4x5() {
        return [
            1, 0, 0, 0, 0,
            0, 1, 0, 0, 0,
            0, 0, 1, 0, 0,
            0, 0, 0, 1, 0
        ];
    }

    function matMul4x5(a, b) {
        const out = new Array(20);
        for (let row = 0; row < 4; row++) {
            for (let col = 0; col < 4; col++) {
                let s = 0;
                for (let k = 0; k < 4; k++) s += a[row * 5 + k] * b[k * 5 + col];
                out[row * 5 + col] = s;
            }
            let o = a[row * 5 + 4];
            for (let k = 0; k < 4; k++) o += a[row * 5 + k] * b[k * 5 + 4];
            out[row * 5 + 4] = o;
        }
        return out;
    }

    function matBrightnessContrast(br, ct) {
        const g = br * ct;
        const off = br * 0.5 * (1 - ct);
        return [
            g, 0, 0, 0, off,
            0, g, 0, 0, off,
            0, 0, g, 0, off,
            0, 0, 0, 1, 0
        ];
    }

    function matSaturation(s) {
        const ir = (1 - s) * LUMA.r;
        const ig = (1 - s) * LUMA.g;
        const ib = (1 - s) * LUMA.b;
        return [
            ir + s, ig, ib, 0, 0,
            ir, ig + s, ib, 0, 0,
            ir, ig, ib + s, 0, 0,
            0, 0, 0, 1, 0
        ];
    }

    function matHueRotate(deg) {
        const rad = (deg * Math.PI) / 180;
        const cosA = Math.cos(rad);
        const sinA = Math.sin(rad);
        const lr = LUMA.r, lg = LUMA.g, lb = LUMA.b;

        const a00 = lr + cosA * (1 - lr) + sinA * (-lr);
        const a01 = lg + cosA * (-lg) + sinA * (-lg);
        const a02 = lb + cosA * (-lb) + sinA * (1 - lb);
        const a10 = lr + cosA * (-lr) + sinA * (0.143);
        const a11 = lg + cosA * (1 - lg) + sinA * (0.140);
        const a12 = lb + cosA * (-lb) + sinA * (-0.283);
        const a20 = lr + cosA * (-lr) + sinA * (-(1 - lr));
        const a21 = lg + cosA * (-lg) + sinA * (lg);
        const a22 = lb + cosA * (1 - lb) + sinA * (lb);

        return [
            a00, a01, a02, 0, 0,
            a10, a11, a12, 0, 0,
            a20, a21, a22, 0, 0,
            0, 0, 0, 1, 0
        ];
    }

    function matRGBGain(rGain, gGain, bGain) {
        return [
            rGain, 0, 0, 0, 0,
            0, gGain, 0, 0, 0,
            0, 0, bGain, 0, 0,
            0, 0, 0, 1, 0
        ];
    }

    function matToSvgValues(m) {
        return m.map(x => (Math.abs(x) < 1e-10 ? '0' : Number(x).toFixed(6))).join(' ');
    }

    let autoMatrixStr = matToSvgValues(matIdentity4x5());
    let _autoLastMatrixStr = autoMatrixStr;

    function updateAutoMatrixInSvg(valuesStr) {
        try {
            const svg = document.getElementById(SVG_ID);
            if (!svg) return;
            const nodes = svg.querySelectorAll('feColorMatrix[data-gvf-auto="1"]');
            if (!nodes || !nodes.length) return;
            nodes.forEach(n => {
                try {
                    if (n) {
                        n.setAttribute('values', valuesStr);
                    }
                } catch (_) { }
            });
        } catch (_) { }
    }

    // -------------------------
    // BRANCHLESS SHADER LOGIC
    // -------------------------
    function branchlessClamp(x, min, max) {
        return Math.min(max, Math.max(min, x));
    }

    function branchlessSign(x) {
        return (x > 0) - (x < 0);
    }

    function branchlessStep(edge, x) {
        return (x >= edge) | 0;
    }

    function branchlessMix(a, b, t) {
        return a + (b - a) * t;
    }

    function branchlessSmoothStep(edge0, edge1, x) {
        const t = Math.min(1, Math.max(0, (x - edge0) / (edge1 - edge0)));
        return t * t * (3 - 2 * t);
    }

    function branchlessGamma(x, gamma) {
        const g = branchlessClamp(gamma, 0.5, 2.0);
        if (g < 1.0) {
            return x * (1.0 + (1.0 - g) * (1.0 - x) * 0.5);
        } else {
            return x * (1.0 - (g - 1.0) * x * 0.3);
        }
    }

    function branchlessRGBGain(r, g, b, rGain, gGain, bGain) {
        return [
            r * rGain / 128.0,
            g * gGain / 128.0,
            b * bGain / 128.0
        ];
    }

    function branchlessSaturation(r, g, b, sat) {
        const luma = LUMA.r * r + LUMA.g * g + LUMA.b * b;
        return [
            branchlessMix(luma, r, sat),
            branchlessMix(luma, g, sat),
            branchlessMix(luma, b, sat)
        ];
    }

    function branchlessContrast(r, g, b, contrast) {
        const factor = (259.0 * (contrast * 255.0 + 255.0)) / (255.0 * (259.0 - contrast * 255.0));
        return [
            branchlessClamp(factor * (r - 128) + 128, 0, 255),
            branchlessClamp(factor * (g - 128) + 128, 0, 255),
            branchlessClamp(factor * (b - 128) + 128, 0, 255)
        ];
    }

    function branchlessHDRToneMap(r, g, b, exposure) {
        const luma = LUMA.r * r + LUMA.g * g + LUMA.b * b;
        const scale = (luma * exposure + 1.0) / (luma + 1.0);
        return [r * scale, g * scale, b * scale];
    }

    function branchlessSharpen(original, blurred, amount) {
        return [
            branchlessClamp(original[0] * (1.0 + amount) - blurred[0] * amount, 0, 255),
            branchlessClamp(original[1] * (1.0 + amount) - blurred[1] * amount, 0, 255),
            branchlessClamp(original[2] * (1.0 + amount) - blurred[2] * amount, 0, 255)
        ];
    }

    function branchlessMotionDetect(curr, prev) {
        let diff = 0;
        for (let i = 0; i < curr.length; i++) {
            diff += Math.abs(curr[i] - prev[i]);
        }
        return diff / curr.length;
    }

    function branchlessAdaptiveFps(motionScore, currentFps, minFps, maxFps) {
        const targetFps = minFps + motionScore * (maxFps - minFps);
        const alpha = 0.2;
        return currentFps * (1 - alpha) + targetFps * alpha;
    }

    // ===================== REAL WEBGL2 CANVAS PIPELINE =====================
    let webglPipeline = null;

    class WebGL2Pipeline {
        constructor() {
            this.canvas = null;
            this.gl = null;
            this.program = null;
            this.videoTexture = null;
            this.video = null;
            this.active = false;
            this.rafId = null;

            // Uniform locations
            this.uResolution = null;
            this.uVideoTex = null;
            this.uParams = null;
            this.uParams2 = null;
            this.uRGBGain = null;
            this.uHueRotate = null;
            this.uProfileMatrix = null;
            this.uAutoMatrix = null;

            // Attribute locations
            this.aPosition = null;
            this.aTexCoord = null;

            // Buffers
            this.vertexBuffer = null;
            this.texCoordBuffer = null;

            // Original video parent and styles
            this.originalParent = null;
            this.originalNextSibling = null;
            this.originalStyle = null;
            this.originalParentPosition = null;
            this.wrapper = null;
            this.firstFrameDrawn = false;

            // Parameter cache
            this.params = {
                contrast: 1.0,
                saturation: 1.0,
                brightness: 1.0,
                sharpen: 0.0,
                gamma: 1.0,
                grain: 0.0,
                hdr: 0.0,
                rGain: 1.0,
                gGain: 1.0,
                bGain: 1.0,
                hue: 0.0,
                cosHue: 1.0,
                sinHue: 0.0,
                vibrance: 1.0,
                black: 0.0,
                white: 1.0
            };

            // HDR startup smoothing / GPU protection
            this.hdrWarmupUntil = 0;
            this.hdrWarmupDurationMs = 2200;
            this.hdrStartDelayUntil = 0;
            this.hdrStartDelayMs = 650;
            this._boundWarmupHandler = null;
            this._boundVisibilityHandler = null;
        }

        markHdrWarmup(durationMs) {
            const now = nowMs();
            const ms = Math.max(700, Number(durationMs) || this.hdrWarmupDurationMs || 2200);
            this.hdrStartDelayUntil = now + Math.max(250, this.hdrStartDelayMs || 650);
            this.hdrWarmupUntil = this.hdrStartDelayUntil + ms;
        }

        getHdrWarmupFactor() {
            const hdrTarget = Math.max(0, Number(normHDR()) || 0);
            if (hdrTarget <= 0.0001) return 1.0;

            const now = nowMs();
            if (this.hdrStartDelayUntil > now) return 0.0;

            const left = this.hdrWarmupUntil - now;
            if (left <= 0) return 1.0;

            const dur = Math.max(700, this.hdrWarmupDurationMs || 2200);
            const progress = clamp(1 - (left / dur), 0, 1);

            // Very soft HDR ramp to avoid start spikes and sustained GPU overload.
            return clamp(progress * progress * 0.9, 0.0, 0.9);
        }

        bindHdrWarmupEvents(video) {
            if (!video) return;
            if (this._boundWarmupHandler) return;

            const warm = () => {
                if ((Number(normHDR()) || 0) > 0.0001) {
                    this.markHdrWarmup();
                }
            };

            this._boundWarmupHandler = warm;
            ['play', 'playing', 'seeking', 'seeked', 'loadeddata', 'canplay'].forEach((evt) => {
                try { video.addEventListener(evt, warm, true); } catch (_) { }
            });

            this._boundVisibilityHandler = () => {
                if (!document.hidden && video && !video.paused && (Number(normHDR()) || 0) > 0.0001) {
                    this.markHdrWarmup(900);
                }
            };
            try { document.addEventListener('visibilitychange', this._boundVisibilityHandler, true); } catch (_) { }
        }

        unbindHdrWarmupEvents(video) {
            if (video && this._boundWarmupHandler) {
                ['play', 'playing', 'seeking', 'seeked', 'loadeddata', 'canplay'].forEach((evt) => {
                    try { video.removeEventListener(evt, this._boundWarmupHandler, true); } catch (_) { }
                });
            }
            if (this._boundVisibilityHandler) {
                try { document.removeEventListener('visibilitychange', this._boundVisibilityHandler, true); } catch (_) { }
            }
            this._boundWarmupHandler = null;
            this._boundVisibilityHandler = null;
        }

        init() {
            try {
                // Create visible canvas that will replace the video
                this.canvas = document.createElement('canvas');
                this.canvas.id = WEBGL_CANVAS_ID;
                this.canvas.style.position = 'absolute';
                this.canvas.style.inset = '0';
                this.canvas.style.width = '100%';
                this.canvas.style.height = '100%';
                this.canvas.style.objectFit = 'contain';
                this.canvas.style.transform = 'none';
                this.canvas.style.display = 'block';
                this.canvas.style.pointerEvents = 'none';
                this.canvas.style.zIndex = '2147483646';
                this.canvas.style.opacity = '0';

                // Try WebGL2 first, fallback to WebGL1
                let gl = this.canvas.getContext('webgl2', {
                    alpha: false,
                    antialias: false,
                    preserveDrawingBuffer: false,
                    powerPreference: 'high-performance'
                });

                if (!gl) {
                    gl = this.canvas.getContext('webgl', {
                        alpha: false,
                        antialias: false,
                        preserveDrawingBuffer: false
                    }) || this.canvas.getContext('experimental-webgl', {
                        alpha: false,
                        antialias: false,
                        preserveDrawingBuffer: false
                    });
                }

                                this._isWebGL2 = (typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext);

if (!gl) {
                    logW('WebGL not available');
                    return false;
                }

                this.gl = gl;

                if (!this.setupShaders()) {
                    return false;
                }

                this.setupBuffers();
                this.active = true;
                log('WebGL2 Canvas Pipeline initialized successfully');
                return true;
            } catch (e) {
                logW('WebGL init error:', e);
                return false;
            }
        }

        getVertexShader() {
            const src100 = `#version 100
                attribute vec2 aPosition;
                attribute vec2 aTexCoord;
                varying vec2 vTexCoord;
                void main() {
                    gl_Position = vec4(aPosition, 0.0, 1.0);
                    vTexCoord = aTexCoord;
                }
            `;
            if (!this._isWebGL2) return src100;

            // WebGL2: upgrade GLSL100 -> GLSL300 ES
            return src100
                .replace('#version 100', '#version 300 es')
                .replace(/\battribute\b/g, 'in')
                .replace(/\bvarying\b/g, 'out')
                .replace(/\btexture2D\b/g, 'texture');
        }

        getFragmentShader() {
            const src100 = `#version 100
                precision highp float;
                varying vec2 vTexCoord;
                uniform sampler2D uVideoTex;
                uniform vec2 uResolution;

                uniform vec4 uParams;      // x:contrast, y:saturation, z:brightness, w:sharpen
                uniform vec4 uParams2;      // x:gamma, y:grain, z:vibrance, w:hdr
                uniform vec4 uRGBGain;      // x:rGain, y:gGain, z:bGain, w:unused
                uniform vec2 uHueRotate;    // x:cosHue, y:sinHue
                uniform mat4 uProfileMatrix;
                uniform mat4 uAutoMatrix;
                uniform float uEdge;

                const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);

                float sampleLuma(vec2 uv) {
                    return dot(texture2D(uVideoTex, uv).rgb, LUMA);
                }

                float clampFast(float x, float minVal, float maxVal) {
                    return min(max(x, minVal), maxVal);
                }



                // --- HDR helpers (linear-light + ACES tonemapping) ---
                vec3 srgbToLinear(vec3 c) {
                    c = clamp(c, 0.0, 1.0);
                    vec3 low = c / 12.92;
                    vec3 high = pow((c + 0.055) / 1.055, vec3(2.4));
                    vec3 t = step(vec3(0.04045), c);
                    return mix(low, high, t);
                }

                vec3 linearToSrgb(vec3 c) {
                    c = max(c, vec3(0.0));
                    vec3 low = c * 12.92;
                    vec3 high = 1.055 * pow(c, vec3(1.0/2.4)) - 0.055;
                    vec3 t = step(vec3(0.0031308), c);
                    return clamp(mix(low, high, t), 0.0, 1.0);
                }

                vec3 RRTAndODTFit(vec3 v) {
                    vec3 a = v * (v + 0.0245786) - 0.000090537;
                    vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;
                    return a / b;
                }

                vec3 tonemapACES(vec3 color) {
                    const mat3 ACESInputMat = mat3(
                      0.59719, 0.07600, 0.02840,
                      0.35458, 0.90834, 0.13383,
                      0.04823, 0.01566, 0.83777
                    );
                    const mat3 ACESOutputMat = mat3(
                        1.60475, -0.10208, -0.00327,
                        -0.53108, 1.10813, -0.07276,
                        -0.07367, -0.00605, 1.07602
                    );
                    color = ACESInputMat * color;
                    color = RRTAndODTFit(color);
                    color = ACESOutputMat * color;
                    return clamp(color, 0.0, 1.0);
                }
                vec3 applyHueRotate(vec3 color, float cosHue, float sinHue) {
                    float lr = LUMA.r, lg = LUMA.g, lb = LUMA.b;
                    float a00 = lr + cosHue*(1.0-lr) + sinHue*(-lr);
                    float a01 = lg + cosHue*(-lg) + sinHue*(-lg);
                    float a02 = lb + cosHue*(-lb) + sinHue*(1.0-lb);
                    float a10 = lr + cosHue*(-lr) + sinHue*(0.143);
                    float a11 = lg + cosHue*(1.0-lg) + sinHue*(0.140);
                    float a12 = lb + cosHue*(-lb) + sinHue*(-0.283);
                    float a20 = lr + cosHue*(-lr) + sinHue*(-(1.0-lr));
                    float a21 = lg + cosHue*(-lg) + sinHue*(lg);
                    float a22 = lb + cosHue*(1.0-lb) + sinHue*(lb);
                    return vec3(
                        a00*color.r + a01*color.g + a02*color.b,
                        a10*color.r + a11*color.g + a12*color.b,
                        a20*color.r + a21*color.g + a22*color.b
                    );
                }

                vec3 applyColorMatrix(vec3 color, mat4 m) {
                    return vec3(
                        m[0][0]*color.r + m[1][0]*color.g + m[2][0]*color.b + m[3][0],
                        m[0][1]*color.r + m[1][1]*color.g + m[2][1]*color.b + m[3][1],
                        m[0][2]*color.r + m[1][2]*color.g + m[2][2]*color.b + m[3][2]
                    );
                }

                void main() {
                    vec4 texColor = texture2D(uVideoTex, vTexCoord);
                    vec3 color = texColor.rgb;

                    // RGB Gain
                    color.r *= uRGBGain.x;
                    color.g *= uRGBGain.y;
                    color.b *= uRGBGain.z;

                    // Profile Matrix
                    color = applyColorMatrix(color, uProfileMatrix);

                    // Auto Matrix
                    color = applyColorMatrix(color, uAutoMatrix);

                    // Hue Rotate
                    color = applyHueRotate(color, uHueRotate.x, uHueRotate.y);

                    // Vibrance
                    float luma = dot(color, LUMA);
                    vec3 delta = color - luma;
                    color = luma + delta * uParams2.z;

                    // Saturation
                    color = luma + uParams.y * (color - luma);

                    // Contrast & Brightness
                    color = (color - 0.5) * uParams.x + 0.5;
                    color *= uParams.z;

                    // Gamma
                    float gInv = 1.0 / clampFast(uParams2.x, 0.5, 2.0);
                    color.r = pow(color.r, gInv);
                    color.g = pow(color.g, gInv);
                    color.b = pow(color.b, gInv);

                    // HDR (WebGL HDR-like: linear-light + exposure lift + ACES tonemapping)
                    float hdr = clampFast(uParams2.w, 0.0, 1.0);
                    if (hdr > 0.0001) {
                        // Keep the low-end response soft, but make exposure changes visibly affect the image.
                        vec3 lin = srgbToLinear(color);
                        float sceneLuma = dot(lin, LUMA);
                        float hdrCurve = pow(hdr, 1.85);
                        float exposureStops = hdrCurve * 1.10;
                        float exposure = pow(2.0, exposureStops);
                        float shadowMask = 1.0 - clampFast(sceneLuma * 2.25, 0.0, 1.0);
                        float shadowLift = 1.0 + hdrCurve * 0.85 * shadowMask;
                        lin *= exposure * shadowLift;
                        lin = tonemapACES(lin);
                        color = linearToSrgb(lin);
                        float postLift = 1.0 + hdrCurve * 0.14;
                        color = clamp(color * postLift, 0.0, 1.0);
                    }
                    // Grain
                    float noise = fract(sin(vTexCoord.x * 12.9898 + vTexCoord.y * 78.233) * 43758.5453);
                    noise = (noise - 0.5) * uParams2.y;
                    color += vec3(noise);

                    // Sharpen
                    if (uParams.w > 0.0) {
                        float lumaSharp = dot(color, LUMA);
                        vec3 sharpColor = color + (color - lumaSharp) * uParams.w;
                        color = vec3(
                            clampFast(sharpColor.r, 0.0, 1.0),
                            clampFast(sharpColor.g, 0.0, 1.0),
                            clampFast(sharpColor.b, 0.0, 1.0)
                        );
                    }

                    // Real edge detection: Sobel on source luma, then darken only true edges.
                    // Use a non-linear strength curve so tiny slider values stay subtle and controllable.
                    if (uEdge > 0.0001) {
                        float edgeStrength = pow(clamp(uEdge, 0.0, 1.0), 2.2);
                        vec2 px = vec2(1.0 / max(uResolution.x, 1.0), 1.0 / max(uResolution.y, 1.0));
                        float tl = sampleLuma(vTexCoord + px * vec2(-1.0, -1.0));
                        float  t = sampleLuma(vTexCoord + px * vec2( 0.0, -1.0));
                        float tr = sampleLuma(vTexCoord + px * vec2( 1.0, -1.0));
                        float  l = sampleLuma(vTexCoord + px * vec2(-1.0,  0.0));
                        float  r = sampleLuma(vTexCoord + px * vec2( 1.0,  0.0));
                        float bl = sampleLuma(vTexCoord + px * vec2(-1.0,  1.0));
                        float  b = sampleLuma(vTexCoord + px * vec2( 0.0,  1.0));
                        float br = sampleLuma(vTexCoord + px * vec2( 1.0,  1.0));

                        float gx = -tl + tr - 2.0*l + 2.0*r - bl + br;
                        float gy = -tl - 2.0*t - tr + bl + 2.0*b + br;
                        float edgeMag = length(vec2(gx, gy));
                        float edgeMask = smoothstep(0.18, 0.60, edgeMag) * edgeStrength;
                        float darken = 1.0 - edgeMask * 0.92;
                        color *= darken;
                    }

                    gl_FragColor = vec4(clampFast(color.r, 0.0, 1.0),
                                        clampFast(color.g, 0.0, 1.0),
                                        clampFast(color.b, 0.0, 1.0),
                                        texColor.a);
                }
            `;
            if (!this._isWebGL2) return src100;

            // WebGL2: upgrade GLSL100 -> GLSL300 ES
            // - varying -> in
            // - gl_FragColor -> outColor
            // - texture2D -> texture
            let s = src100
                .replace('#version 100', '#version 300 es')
                .replace(/\bvarying\b/g, 'in')
                .replace(/\btexture2D\b/g, 'texture')
                .replace(/\bgl_FragColor\b/g, 'outColor');

            // Ensure fragment output exists
            if (!/\bout\s+vec4\s+outColor\s*;/.test(s)) {
                s = s.replace(/precision\s+highp\s+float\s*;\s*/m, (m) => m + '\n                out vec4 outColor;\n');
            }
            return s;
        }

        setupShaders() {
            const gl = this.gl;

            const vertexShader = gl.createShader(gl.VERTEX_SHADER);
            gl.shaderSource(vertexShader, this.getVertexShader());
            gl.compileShader(vertexShader);

            if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
                logW('Vertex shader compile error:', gl.getShaderInfoLog(vertexShader));
                return false;
            }

            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
            gl.shaderSource(fragmentShader, this.getFragmentShader());
            gl.compileShader(fragmentShader);

            if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
                logW('Fragment shader compile error:', gl.getShaderInfoLog(fragmentShader));
                return false;
            }

            this.program = gl.createProgram();
            gl.attachShader(this.program, vertexShader);
            gl.attachShader(this.program, fragmentShader);
            gl.linkProgram(this.program);

            if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
                logW('Program link error:', gl.getProgramInfoLog(this.program));
                return false;
            }

            gl.useProgram(this.program);

            this.uResolution = gl.getUniformLocation(this.program, 'uResolution');
            this.uVideoTex = gl.getUniformLocation(this.program, 'uVideoTex');
            this.uParams = gl.getUniformLocation(this.program, 'uParams');
            this.uParams2 = gl.getUniformLocation(this.program, 'uParams2');
            this.uRGBGain = gl.getUniformLocation(this.program, 'uRGBGain');
            this.uHueRotate = gl.getUniformLocation(this.program, 'uHueRotate');
            this.uProfileMatrix = gl.getUniformLocation(this.program, 'uProfileMatrix');
            this.uAutoMatrix = gl.getUniformLocation(this.program, 'uAutoMatrix');
            this.uEdge = gl.getUniformLocation(this.program, 'uEdge');

            this.aPosition = gl.getAttribLocation(this.program, 'aPosition');
            this.aTexCoord = gl.getAttribLocation(this.program, 'aTexCoord');

            gl.uniform1i(this.uVideoTex, 0);

            return true;
        }

        setupBuffers() {
            const gl = this.gl;

            const vertices = new Float32Array([
                -1.0, -1.0,
                 1.0, -1.0,
                -1.0,  1.0,
                 1.0,  1.0
            ]);

            this.vertexBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

            // Use standard top-left texture coordinates and flip exactly once on upload.
            // The previous mixed approach caused the GPU image to look wrong again.
            const texCoords = new Float32Array([
                0.0, 0.0,
                1.0, 0.0,
                0.0, 1.0,
                1.0, 1.0
            ]);

            this.texCoordBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
        }

        attachToVideo(video) {
            if (!this.active && !this.init()) {
                return false;
            }

            if (this.video && this.video !== video) {
                this.shutdown();
                if (!this.init()) return false;
            }

            this.video = video;
            this.firstFrameDrawn = false;
            this.bindHdrWarmupEvents(video);
            this.markHdrWarmup();

            this.originalParent = video.parentNode;
            this.originalNextSibling = video.nextSibling;
            this.originalStyle = video.style.cssText;

            if (!this.originalParent) return false;

            const parentStyle = window.getComputedStyle(this.originalParent);
            this.originalParentPosition = this.originalParent.style.position || '';
            if (parentStyle.position === 'static') {
                this.originalParent.style.position = 'relative';
            }

            if (!this.videoTexture) {
                const gl = this.gl;
                this.videoTexture = gl.createTexture();
                gl.bindTexture(gl.TEXTURE_2D, this.videoTexture);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
                gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
                gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
            }

            if (!this.wrapper || !this.wrapper.isConnected) {
                const wrapper = document.createElement('div');
                wrapper.setAttribute(WEBGL_WRAPPER_ATTR, '1');
                wrapper.style.position = 'absolute';
                wrapper.style.inset = '0';
                wrapper.style.pointerEvents = 'none';
                wrapper.style.zIndex = '2147483646';
                wrapper.style.overflow = 'hidden';
                this.wrapper = wrapper;
            }

            if (this.canvas.parentNode !== this.wrapper) {
                this.wrapper.appendChild(this.canvas);
            }
            if (this.wrapper.parentNode !== this.originalParent) {
                this.originalParent.appendChild(this.wrapper);
            }

            this.canvas.width = video.videoWidth || 640;
            this.canvas.height = video.videoHeight || 360;
            this.canvas.style.opacity = '0';
            video.style.opacity = video.style.opacity || '';
            video.style.pointerEvents = video.style.pointerEvents || '';
            this.startRenderLoop();
            this.render();
            return true;
        }

        updateParams() {
            let contrast = 1.0 + (u_contrast * 0.04);
            let saturation = 1.0 + (u_sat * 0.05);
            let brightness = 1.0 + (u_black * -0.012) + (u_white * 0.012);

            let rGain = u_r_gain / 128.0;
            let gGain = u_g_gain / 128.0;
            let bGain = u_b_gain / 128.0;

            if (enabled) {
                contrast *= 1.05;
                saturation *= 1.21;
                brightness *= 1.02;
            }

            if (profile === 'film') {
                contrast *= 1.08;
                saturation *= 1.08;
            } else if (profile === 'anime') {
                contrast *= 1.10;
                saturation *= 1.16;
                brightness *= 1.03;
            } else if (profile === 'gaming') {
                contrast *= 1.12;
                saturation *= 1.06;
            } else if (profile === 'eyecare') {
                saturation *= 0.85;
                brightness *= 1.06;
            }

            if (darkMoody) {
                saturation *= 0.92;
                brightness *= 0.96;
            }

            if (vibrantSat) {
                saturation *= 1.35;
            }

            let sharpen = Math.max(0, normSL() * 0.3);
            let grain = Math.max(0, -normDN() * 0.2);
            let gamma = 1.0 + u_gamma * 0.025;
            let vibrance = 1.0 + u_vib * 0.02;
            let hdrVal = normHDR();
            let edgeVal = normEDGE();

            let hue = u_hue * 3;
            if (tealOrange) {
                hue += -5;
            }
            let hueRad = hue * Math.PI / 180;

            const hdrWarmupFactor = this.getHdrWarmupFactor();
            const activeVisibleVideos = this.getActiveRenderableVideoCount();
            let effectiveHdr = clamp(hdrVal, -1.0, 2.0);
            if (effectiveHdr > 0) {
                // Clamp HDR intensity in GPU mode so the shader does not run at the most expensive path.
                effectiveHdr = Math.min(effectiveHdr, activeVisibleVideos >= 2 ? 0.42 : 0.65);
                effectiveHdr *= hdrWarmupFactor;
            }

            this.params = {
                contrast: clamp(contrast, 0.5, 2.0),
                saturation: clamp(saturation, 0.0, 3.0),
                brightness: clamp(brightness, 0.5, 2.0),
                sharpen: clamp(sharpen, 0.0, 2.0),
                gamma: clamp(gamma, 0.5, 2.0),
                grain: clamp(grain, 0.0, 0.5),
                vibrance: clamp(vibrance, 0.0, 2.0),
                hdr: effectiveHdr,
                edge: clamp(edgeVal, 0.0, 1.0),
                rGain: clamp(rGain, 0.0, 2.0),
                gGain: clamp(gGain, 0.0, 2.0),
                bGain: clamp(bGain, 0.0, 2.0),
                hue: hue,
                cosHue: Math.cos(hueRad),
                sinHue: Math.sin(hueRad)
            };

            if (LOG.on && (performance.now() - LOG.lastTickMs) > 5000) {
                log('RGB Gain:', this.params.rGain.toFixed(2), this.params.gGain.toFixed(2), this.params.bGain.toFixed(2));
            }
        }

        getActiveRenderableVideoCount() {
            try {
                return Array.from(document.querySelectorAll('video')).filter(v => isVideoRenderable(v)).length;
            } catch (_) {
                return 1;
            }
        }

        getRenderThrottleMs() {
            const hdrActive = normHDR() > 0.0001;
            const activeVisibleVideos = this.getActiveRenderableVideoCount();
            const hdrWarmupFactor = this.getHdrWarmupFactor();
            let throttle = RENDER_THROTTLE;

            if (activeVisibleVideos >= 2) throttle = Math.max(throttle, 60);

            if (hdrActive) {
                throttle = Math.max(throttle, 95);
                if (activeVisibleVideos >= 2) throttle = Math.max(throttle, 145);
                if (hdrWarmupFactor < 0.999) throttle = Math.max(throttle, activeVisibleVideos >= 2 ? 185 : 150);
                if (this.hdrStartDelayUntil > nowMs()) throttle = Math.max(throttle, activeVisibleVideos >= 2 ? 210 : 170);
            }

            return throttle;
        }

        getRenderScale(srcWidth, srcHeight) {
            const hdrActive = normHDR() > 0.0001;
            const activeVisibleVideos = this.getActiveRenderableVideoCount();
            const pixelCount = Math.max(1, (srcWidth || 0) * (srcHeight || 0));
            const hdrWarmupFactor = this.getHdrWarmupFactor();

            let scale = 1.0;

            if (hdrActive) scale = Math.min(scale, 0.56);
            if (pixelCount >= (2560 * 1440)) scale = Math.min(scale, hdrActive ? 0.38 : 0.82);
            else if (pixelCount >= (1920 * 1080)) scale = Math.min(scale, hdrActive ? 0.46 : 0.9);
            else if (pixelCount >= (1280 * 720)) scale = Math.min(scale, hdrActive ? 0.54 : 0.95);

            if (activeVisibleVideos >= 2) scale = Math.min(scale, hdrActive ? 0.34 : 0.8);

            if (hdrActive) {
                if (this.hdrStartDelayUntil > nowMs()) {
                    scale = Math.min(scale, activeVisibleVideos >= 2 ? 0.28 : 0.34);
                } else if (hdrWarmupFactor < 0.999) {
                    const startupScale = activeVisibleVideos >= 2 ? 0.30 : 0.36;
                    scale = Math.min(scale, startupScale + (0.14 * hdrWarmupFactor));
                }
            }

            return clamp(scale, 0.28, 1.0);
        }

        shouldRenderNow() {
            if (!this.active || !this.video) return false;
            if (document.hidden) return false;
            if (!isVideoRenderable(this.video)) return false;
            return true;
        }

        render() {
            if (!this.active || !this.gl || !this.video) return;

            const gl = this.gl;
            const video = this.video;

            if (video.readyState < 2 || video.videoWidth === 0 || video.videoHeight === 0) {
                return;
            }

            try {
                const width = video.videoWidth;
                const height = video.videoHeight;
                const renderScale = this.getRenderScale(width, height);
                const targetWidth = Math.max(2, Math.round(width * renderScale));
                const targetHeight = Math.max(2, Math.round(height * renderScale));

                if (this.canvas.width !== targetWidth || this.canvas.height !== targetHeight) {
                    this.canvas.width = targetWidth;
                    this.canvas.height = targetHeight;
                    gl.viewport(0, 0, targetWidth, targetHeight);
                    if (this.uResolution) {
                        gl.uniform2f(this.uResolution, targetWidth, targetHeight);
                    }
                }

                this.updateParams();

                gl.useProgram(this.program);

                gl.uniform4f(this.uParams,
                    this.params.contrast,
                    this.params.saturation,
                    this.params.brightness,
                    this.params.sharpen
                );

                gl.uniform4f(this.uParams2,
                    this.params.gamma,
                    this.params.grain,
                    this.params.vibrance,
                    this.params.hdr
                );

                gl.uniform4f(this.uRGBGain,
                    this.params.rGain,
                    this.params.gGain,
                    this.params.bGain,
                    1.0
                );

                if (this.uEdge !== null) {
                    gl.uniform1f(this.uEdge, this.params.edge);
                }

                gl.uniform2f(this.uHueRotate,
                    this.params.cosHue,
                    this.params.sinHue
                );

                // Profile matrix (Identity for now)
                let profMatrix = new Float32Array(16);
                profMatrix[0] = 1; profMatrix[5] = 1; profMatrix[10] = 1; profMatrix[15] = 1;
                gl.uniformMatrix4fv(this.uProfileMatrix, false, profMatrix);

                // Auto matrix (Identity for now)
                let autoMatrix = new Float32Array(16);
                autoMatrix[0] = 1; autoMatrix[5] = 1; autoMatrix[10] = 1; autoMatrix[15] = 1;
                gl.uniformMatrix4fv(this.uAutoMatrix, false, autoMatrix);

                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(gl.TEXTURE_2D, this.videoTexture);
                // HTML5 video frames need a single Y-flip on upload in the GPU path.
                // Setting this to false here inverted the live image again.
                gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
                gl.clearColor(0.0, 0.0, 0.0, 0.0);
                gl.clear(gl.COLOR_BUFFER_BIT);
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);

                gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
                gl.enableVertexAttribArray(this.aPosition);
                gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, 0, 0);

                gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
                gl.enableVertexAttribArray(this.aTexCoord);
                gl.vertexAttribPointer(this.aTexCoord, 2, gl.FLOAT, false, 0, 0);

                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
                if (!this.firstFrameDrawn) {
                    this.firstFrameDrawn = true;
                    this.canvas.style.opacity = '1';
                }

            } catch (e) {
                logW('WebGL render error:', e);
            }
        }

        startRenderLoop() {
            this.stopRenderLoop();

            const canRVFC = this.video && typeof this.video.requestVideoFrameCallback === 'function';

            if (canRVFC) {
                const onFrame = (now) => {
                    if (!this.active || !this.video) { this.rafId = null; return; }
                    const throttle = this.getRenderThrottleMs();
                    if (this.shouldRenderNow() && (now - lastRenderTime >= throttle)) {
                        lastRenderTime = now;
                        this.render();
                    }
                    this.rafId = this.video.requestVideoFrameCallback(onFrame);
                };
                this.rafId = this.video.requestVideoFrameCallback(onFrame);
                return;
            }

            const loop = (timestamp) => {
                if (!this.active || !this.video) { this.rafId = null; return; }
                const throttle = this.getRenderThrottleMs();
                if (this.shouldRenderNow() && (timestamp - lastRenderTime >= throttle)) {
                    lastRenderTime = timestamp;
                    this.render();
                }
                this.rafId = requestAnimationFrame(loop);
            };

            this.rafId = requestAnimationFrame(loop);
        }

        stopRenderLoop() {
            if (!this.rafId) return;
            try {
                // Could be an rAF id or a requestVideoFrameCallback id
                if (this.video && typeof this.video.cancelVideoFrameCallback === 'function') {
                    try { this.video.cancelVideoFrameCallback(this.rafId); } catch (_) { }
                }
                cancelAnimationFrame(this.rafId);
            } catch (_) { }
            this.rafId = null;
        }

        shutdown() {
            this.active = false;
            this.stopRenderLoop();
            this.unbindHdrWarmupEvents(this.video);

            if (this.video) {
                this.video.style.cssText = this.originalStyle || '';
            }
            if (this.originalParent) {
                this.originalParent.style.position = this.originalParentPosition || '';
            }
            if (this.canvas && this.canvas.parentNode) {
                this.canvas.parentNode.removeChild(this.canvas);
            }
            if (this.wrapper && this.wrapper.parentNode) {
                this.wrapper.parentNode.removeChild(this.wrapper);
            }

            if (this.gl && this.program) {
                const gl = this.gl;
                gl.deleteProgram(this.program);
                if (this.videoTexture) gl.deleteTexture(this.videoTexture);
                if (this.vertexBuffer) gl.deleteBuffer(this.vertexBuffer);
                if (this.texCoordBuffer) gl.deleteBuffer(this.texCoordBuffer);
            }

            this.canvas = null;
            this.gl = null;
            this.program = null;
            this.videoTexture = null;
            this.vertexBuffer = null;
            this.texCoordBuffer = null;
            this.wrapper = null;
            this.video = null;
            this.firstFrameDrawn = false;
        }
    }

    // GPU Mode Manager
    function activateWebGLMode() {
        const video = getGpuPrimaryVideo();
        if (!video) return;
        if (!webglPipeline) {
            webglPipeline = new WebGL2Pipeline();
        }
        if (webglPipeline.video && webglPipeline.video !== video) {
            webglPipeline.shutdown();
            webglPipeline = new WebGL2Pipeline();
        }
        document.querySelectorAll('video').forEach(v => { delete v.__gvf_webgl_attached; });
        video.__gvf_webgl_attached = true;
        webglPipeline.attachToVideo(video);
    }

    function deactivateWebGLMode() {
        if (webglPipeline) {
            webglPipeline.shutdown();
            webglPipeline = null;
        }
        document.querySelectorAll('video').forEach(video => {
            delete video.__gvf_webgl_attached;
        });
    }

    // -------------------------
    // GPU PIPELINE MODE - Fallback
    // -------------------------
    function getGpuFilterString() {
        if (webglPipeline && webglPipeline.active) {
            return 'none';
        }

        const filters = [];

        if (enabled) {
            filters.push('brightness(1.02)');
            filters.push('contrast(1.05)');
            filters.push('saturate(1.21)');
        }

        const slVal = normSL();
        if (slVal > 0) {
            const sharpAmount = Math.min(2, slVal * 0.3);
            filters.push(`contrast(${1 + sharpAmount})`);
        } else if (slVal < 0) {
            const blurAmount = Math.abs(slVal) * 0.5;
            filters.push(`blur(${blurAmount.toFixed(1)}px)`);
        }

        const blVal = normBL();
        if (blVal !== 0) {
            const blackAdj = 1 + (blVal * 0.03);
            filters.push(`brightness(${blackAdj.toFixed(2)})`);
        }

        const wlVal = normWL();
        if (wlVal !== 0) {
            const whiteAdj = 1 + (wlVal * 0.04);
            filters.push(`contrast(${whiteAdj.toFixed(2)})`);
        }

        const hdrVal = normHDR();
        if (hdrVal > 0) {
            const hdrContrast = 1 + (hdrVal * 0.15);
            const hdrSaturate = 1 + (hdrVal * 0.1);
            filters.push(`contrast(${hdrContrast.toFixed(2)})`);
            filters.push(`saturate(${hdrSaturate.toFixed(2)})`);
        } else if (hdrVal < 0) {
            const softContrast = 1 + (hdrVal * 0.1);
            filters.push(`contrast(${softContrast.toFixed(2)})`);
        }

        if (darkMoody) {
            filters.push('brightness(0.96)');
            filters.push('saturate(0.92)');
        }

        if (tealOrange) {
            filters.push('sepia(0.15)');
            filters.push('hue-rotate(-5deg)');
            filters.push('saturate(1.1)');
        }

        if (vibrantSat) {
            filters.push('saturate(1.35)');
        }

        if (profile === 'film') {
            filters.push('brightness(1.01) contrast(1.08) saturate(1.08)');
        } else if (profile === 'anime') {
            filters.push('brightness(1.03) contrast(1.10) saturate(1.16)');
            // Soft CSS filters for GPU mode (avoid artifacts)
            filters.push('contrast(1.15) brightness(0.98)');
        } else if (profile === 'gaming') {
            filters.push('brightness(1.01) contrast(1.12) saturate(1.06)');
        } else if (profile === 'eyecare') {
            filters.push('brightness(1.06) contrast(0.94) saturate(0.85) hue-rotate(-18deg) sepia(0.25)');
        }

        if (profile === 'user') {
            if (u_contrast !== 0) {
                const c = 1 + (u_contrast * 0.04);
                filters.push(`contrast(${c.toFixed(2)})`);
            }
            if (u_sat !== 0) {
                const sat = 1 + (u_sat * 0.05);
                filters.push(`saturate(${sat.toFixed(2)})`);
            }
            if (u_vib !== 0) {
                const vib = 1 + (u_vib * 0.02);
                filters.push(`saturate(${vib.toFixed(2)})`);
            }
            if (u_hue !== 0) {
                const hue = u_hue * 3;
                filters.push(`hue-rotate(${hue.toFixed(1)}deg)`);
            }
            if (u_black !== 0 || u_white !== 0) {
                const blk = u_black * 0.012;
                const wht = u_white * 0.012;
                const br = 1 + (-blk + wht) * 0.6;
                filters.push(`brightness(${br.toFixed(2)})`);
            }
            if (u_gamma !== 0) {
                const g = 1 + (u_gamma * 0.025);
                filters.push(`brightness(${g.toFixed(2)})`);
            }
        }

        // Color blindness filter for GPU mode
        if (cbFilter !== 'none') {
            if (cbFilter === 'protanopia') {
                filters.push('contrast(1.05) saturate(0.9)');
            } else if (cbFilter === 'deuteranopia') {
                filters.push('contrast(1.05) saturate(0.9) hue-rotate(5deg)');
            } else if (cbFilter === 'tritanomaly') {
                filters.push('contrast(1.02) saturate(0.95) hue-rotate(-5deg)');
            }
        }

        if (autoOn && AUTO.cur) {
            if (AUTO.cur.br !== 1.0) filters.push(`brightness(${AUTO.cur.br.toFixed(2)})`);
            if (AUTO.cur.ct !== 1.0) filters.push(`contrast(${AUTO.cur.ct.toFixed(2)})`);
        }

        const uniqueFilters = [...new Set(filters.filter(f => f && f.length > 0))];
        return uniqueFilters.length > 0 ? uniqueFilters.join(' ') : 'none';
    }

    // -------------------------
    // Auto Scene Match (UNCHANGED)
    // -------------------------
    let _autoLastStyleStamp = 0;
    const AUTO_LEVELS = [2, 4, 6, 8, 10];
    const ADAPTIVE_FPS = {
        MIN: 2,
        MAX: 10,
        current: 2,
        lastAdjust: 0,
        history: [],
        historySize: 5
    };
    const AUTO = {
        baseFps: 2,
        boostMs: 800,
        minBoostIdx: 3,
        minBoostEarlyMs: 700,
        minBoostEarlyIdx: 4,
        minArea: 64 * 64,
        canvasW: 96,
        canvasH: 54,
        running: false,
        tBoostUntil: 0,
        tBoostStart: 0,
        lastSig: null,
        cur: { br: 1.0, ct: 1.0, sat: 1.0, hue: 0.0 },
        tgt: { br: 1.0, ct: 1.0, sat: 1.0, hue: 0.0 },

        scoreEma: 0,
        scoreAlpha: 0.16,

        lastLuma: null,
        motionEma: 0,
        motionAlpha: 0.30,
        motionThresh: 0.0075,
        motionMinFrames: 5,
        motionFrames: 0,

        lastAppliedMs: 0,

        statsEma: null,
        statsAlpha: 0.12,
        lastStatsMs: 0,

        blink: false,

        drmBlocked: false,
        blockUntilMs: 0,
        lastGoodMatrixStr: autoMatrixStr,

        lastFrameTime: 0,
        frameIntervals: [],
        maxFrameIntervals: 10
    };

    function calculateAdaptiveFps(changeScore) {
        ADAPTIVE_FPS.history.push(changeScore);
        if (ADAPTIVE_FPS.history.length > ADAPTIVE_FPS.historySize) {
            ADAPTIVE_FPS.history.shift();
        }

        const avgChange = ADAPTIVE_FPS.history.reduce((a, b) => a + b, 0) / ADAPTIVE_FPS.history.length;

        const t1 = Math.min(avgChange, 0.1) / 0.1;
        const t2 = Math.max(0, Math.min(avgChange - 0.1, 0.2)) / 0.2;
        const t3 = Math.max(0, Math.min(avgChange - 0.3, 0.7)) / 0.7;

        const targetFps =
            (avgChange < 0.1 ? 2 + t1 * 2 : 0) +
            (avgChange >= 0.1 && avgChange < 0.3 ? 4 + t2 * 3 : 0) +
            (avgChange >= 0.3 ? 7 + t3 * 3 : 0);

        const clamped = Math.max(ADAPTIVE_FPS.MIN, Math.min(targetFps, ADAPTIVE_FPS.MAX));
        const rounded = Math.round(clamped * 2) / 2;

        const fpsDiff = rounded - ADAPTIVE_FPS.current;
        const step = Math.max(-1, Math.min(fpsDiff, 1));
        ADAPTIVE_FPS.current += step;

        return ADAPTIVE_FPS.current;
    }

    const overlaysAutoDot = new WeakMap();
    let autoDotMode = 'off';

    function mkAutoDotOverlay() {
        const d = document.createElement('div');
        d.className = 'gvf-auto-dot';
        d.style.cssText = `
      position: fixed;
      width: 8px;
      height: 8px;
      border-radius: 999px;
      z-index: 2147483647;
      pointer-events: none;
      opacity: 0.95;
      display: none;
      transform: translateZ(0);
      box-shadow: 0 0 0 1px rgba(0,0,0,0.75), 0 0 10px rgba(0,255,0,0.18);
      background: #0b3d17;
    `;
        (document.body || document.documentElement).appendChild(d);
        return d;
    }

    function setAutoDotState(mode) {
        if (!debug) return;
        autoDotMode = mode || 'off';
        scheduleOverlayUpdate();
    }

    function applyAutoDotStyle(dotEl) {
        if (!dotEl) return;

        if (!autoOn || autoDotMode === 'off' || !debug) {
            dotEl.style.display = 'none';
            return;
        }

        dotEl.style.display = 'block';

        const t = nowMs();
        const staleMs = 10000;
        const isStale = (AUTO.lastAppliedMs > 0) && ((t - AUTO.lastAppliedMs) >= staleMs);
        if (isStale) {
            dotEl.style.background = '#ff2a2a';
            dotEl.style.boxShadow = '0 0 0 1px rgba(0,0,0,0.80), 0 0 16px rgba(255,42,42,0.55)';
            return;
        }

        if (autoDotMode === 'idle') {
            dotEl.style.background = '#0b3d17';
            dotEl.style.boxShadow = '0 0 0 1px rgba(0,0,0,0.75), 0 0 10px rgba(0,255,0,0.12)';
            return;
        }

        if (autoDotMode === 'workBright') {
            dotEl.style.background = '#38ff64';
            dotEl.style.boxShadow = '0 0 0 1px rgba(0,0,0,0.75), 0 0 14px rgba(56,255,100,0.45)';
            return;
        }

        if (autoDotMode === 'workDark') {
            dotEl.style.background = '#0f7a2b';
            dotEl.style.boxShadow = '0 0 0 1px rgba(0,0,0,0.75), 0 0 12px rgba(56,255,100,0.22)';
            return;
        }
    }

    function isActuallyVisible(v) {
        try {
            const cs = window.getComputedStyle(v);
            if (!cs) return true;
            if (cs.display === 'none') return false;
            if (cs.visibility === 'hidden') return false;
            if (Number(cs.opacity || '1') <= 0) return false;
            return true;
        } catch (_) { return true; }
    }

    function getVideoRect(v) {
        try {
            const r = v.getBoundingClientRect();
            if (r && r.width > 0 && r.height > 0) return r;
        } catch (_) { }
        const w = (v.offsetWidth || 0);
        const h = (v.offsetHeight || 0);
        return { top: 0, left: 0, right: w, bottom: h, width: w, height: h };
    }

    function isPlayableCandidate(v) {
        if (!v) return false;
        const hasDecoded = (v.videoWidth > 0 && v.videoHeight > 0);
        const hasTime = (Number.isFinite(v.currentTime) && v.currentTime > 0) || (Number.isFinite(v.duration) && v.duration > 0);
        const hasData = hasDecoded || hasTime || (v.readyState >= 1);
        if (!hasData) return false;
        if (v.ended) return false;
        if (!isActuallyVisible(v)) return false;
        const r = getVideoRect(v);
        if (!r || r.width < 80 || r.height < 60) return false;
        const area = r.width * r.height;
        if (area < AUTO.minArea) return false;
        return true;
    }

    function choosePrimaryVideo() {
        let best = null;
        let bestScore = 0;

        const vids = Array.from(document.querySelectorAll('video'));
        for (const v of vids) {
            try {
                if (!isPlayableCandidate(v)) continue;

                const r = getVideoRect(v);
                const area = r.width * r.height;

                const inView = !(r.bottom < 0 || r.right < 0 || r.top > (window.innerHeight || 0) || r.left > (window.innerWidth || 0));
                const playing = (!v.paused && !v.seeking);

                const score = area * (inView ? 1.25 : 0.90) * (playing ? 1.20 : 1.00);
                if (score > bestScore) { best = v; bestScore = score; }
            } catch (_) { }
        }
        return best;
    }

    function computeFrameStats(imgData) {
        const d = imgData.data;

        let sumR = 0, sumG = 0, sumB = 0;
        let sumY = 0, sumY2 = 0;
        let sumCh = 0;

        const stepPx = 2;
        const w = imgData.width;
        const h = imgData.height;
        const stride = w * 4;

        let count = 0;
        for (let y = 0; y < h; y += stepPx) {
            let idx = y * stride;
            for (let x = 0; x < w; x += stepPx) {
                const i = idx + x * 4;
                const r = d[i] / 255;
                const g = d[i + 1] / 255;
                const b = d[i + 2] / 255;

                const Y = LUMA.r * r + LUMA.g * g + LUMA.b * b;

                sumR += r; sumG += g; sumB += b;
                sumY += Y; sumY2 += Y * Y;

                const mx = Math.max(r, g, b);
                const mn = Math.min(r, g, b);
                sumCh += (mx - mn);

                count++;
            }
        }

        const inv = 1 / Math.max(1, count);
        const mR = sumR * inv;
        const mG = sumG * inv;
        const mB = sumB * inv;
        const mY = sumY * inv;
        const vY = Math.max(0, (sumY2 * inv) - (mY * mY));
        const sdY = Math.sqrt(vY);
        const mCh = sumCh * inv;

        return { mR, mG, mB, mY, sdY, mCh };
    }

    function computeMotionFromImage(imgData) {
        const d = imgData.data;
        const stepPx = 2;
        const w = imgData.width;
        const h = imgData.height;
        const stride = w * 4;

        const sw = Math.ceil(w / stepPx);
        const sh = Math.ceil(h / stepPx);
        const n = sw * sh;
        const cur = new Uint8Array(n);

        let k = 0;
        for (let y = 0; y < h; y += stepPx) {
            let idx = y * stride;
            for (let x = 0; x < w; x += stepPx) {
                const i = idx + x * 4;
                const r = d[i];
                const g = d[i + 1];
                const b = d[i + 2];
                const y8 = (r * 54 + g * 183 + b * 19) >> 8;
                cur[k++] = y8;
            }
        }

        const prev = AUTO.lastLuma;
        AUTO.lastLuma = cur;

        if (!prev || prev.length !== cur.length) return 1.0;

        return branchlessMotionDetect(cur, prev);
    }

    function detectCut(sig, lastSig) {
        if (!lastSig) return false;
        const dY = Math.abs(sig.mY - lastSig.mY);
        const dCh = Math.abs(sig.mCh - lastSig.mCh);
        const dRB = Math.abs((sig.mR - sig.mB) - (lastSig.mR - lastSig.mB));
        const dGB = Math.abs((sig.mG - sig.mB) - (lastSig.mG - lastSig.mB));

        const score = (dY * 1.1) + (dCh * 0.9) + (dRB * 0.7) + (dGB * 0.7);
        sig.__cutScore = score;
        return score > 0.14;
    }

    function wrapHueDeg(deg) {
        let d = deg;
        while (d > 180) d -= 360;
        while (d < -180) d += 360;
        return d;
    }

    function approach(cur, tgt, a, dead=0.002) {
        const d = tgt - cur;
        if (Math.abs(d) < dead) return tgt;
        return cur + d * a;
    }

    function updateStatsAveraging(sig) {
        const a = clamp(AUTO.statsAlpha, 0.05, 0.95);
        if (!AUTO.statsEma) {
            AUTO.statsEma = { ...sig };
            return AUTO.statsEma;
        }
        const e = AUTO.statsEma;
        e.mR = e.mR * (1 - a) + sig.mR * a;
        e.mG = e.mG * (1 - a) + sig.mG * a;
        e.mB = e.mB * (1 - a) + sig.mB * a;
        e.mY = e.mY * (1 - a) + sig.mY * a;
        e.sdY = e.sdY * (1 - a) + sig.sdY * a;
        e.mCh = e.mCh * (1 - a) + sig.mCh * a;
        e.__cutScore = sig.__cutScore;
        return e;
    }

    function updateAutoTargetsFromStats(sig) {
        const s = clamp(autoStrength, 0, 1);

        const targetY = 0.50;
        const errY = clamp(targetY - sig.mY, -0.22, 0.22);
        const br = clamp(1.0 + errY * 0.85, 0.78, 1.22);

        const targetSd = 0.23;
        const errSd = clamp(targetSd - sig.sdY, -0.18, 0.18);
        const ct = clamp(1.0 + (-errSd) * 0.85, 0.82, 1.30);

        const targetCh = 0.12;
        const errCh = clamp(targetCh - sig.mCh, -0.20, 0.20);
        const sat = clamp(1.0 + (-errCh) * 0.90, 0.80, 1.45);

        let hue = 0.0;
        if (autoLockWB) {
            const rb = clamp(sig.mR - sig.mB, -0.18, 0.18);
            hue = clamp((-rb) * 28.0, -10.0, 10.0);
        }

        AUTO.tgt.br = clamp(1.0 + (br - 1.0) * s, 0.78, 1.22);
        AUTO.tgt.ct = clamp(1.0 + (ct - 1.0) * s, 0.82, 1.30);
        AUTO.tgt.sat = clamp(1.0 + (sat - 1.0) * s, 0.80, 1.45);
        AUTO.tgt.hue = clamp(0.0 + (hue - 0.0) * s, -12.0, 12.0);
    }

    function updateAutoSmoothing(isCut) {
        const a = isCut ? 0.16 : 0.05;
        AUTO.cur.br  = approach(AUTO.cur.br,  AUTO.tgt.br,  a, 0.003);
        AUTO.cur.ct  = approach(AUTO.cur.ct,  AUTO.tgt.ct,  a, 0.003);
        AUTO.cur.sat = approach(AUTO.cur.sat, AUTO.tgt.sat, a, 0.004);
        AUTO.cur.hue = approach(AUTO.cur.hue, AUTO.tgt.hue, a, 0.06);
        AUTO.cur.hue = wrapHueDeg(AUTO.cur.hue);
    }

    function buildAutoMatrixValues() {
        if (!autoOn) return matIdentity4x5();

        const br = clamp(AUTO.cur.br, 0.78, 1.22);
        const ct = clamp(AUTO.cur.ct, 0.82, 1.30);
        const sat = clamp(AUTO.cur.sat, 0.80, 1.45);
        const hue = clamp(AUTO.cur.hue, -12, 12);

        let m = matIdentity4x5();

        m = matMul4x5(matHueRotate(hue), m);
        m = matMul4x5(matSaturation(sat), m);
        m = matMul4x5(matBrightnessContrast(br, ct), m);

        return m;
    }

    function setAutoMatrixAndApply() {
        const m = buildAutoMatrixValues();
        const valuesStr = matToSvgValues(m);

        if (valuesStr === _autoLastMatrixStr) return;
        autoMatrixStr = valuesStr;
        _autoLastMatrixStr = valuesStr;

        AUTO.lastGoodMatrixStr = _autoLastMatrixStr;
        AUTO.lastAppliedMs = nowMs();

        const t = nowMs();
        if ((t - _autoLastStyleStamp) < 150) return;
        _autoLastStyleStamp = t;

        if (LOG.on && (t - LOG.lastToneMs) >= LOG.toneEveryMs) {
            LOG.lastToneMs = t;
            log('AutoMatrix updated:', autoMatrixStr);
        }

        updateAutoMatrixInSvg(autoMatrixStr);
        applyFilter({ skipSvgIfPossible: true });
    }

    function primeAutoOnVideoActivity() {
        try {
            const resetAuto = () => {
                if (!autoOn) return;
                AUTO.lastSig = null;
                AUTO.lastLuma = null;
                AUTO.motionEma = 0;
                AUTO.motionFrames = 0;
                AUTO.scoreEma = 0;
                AUTO.statsEma = null;
                AUTO.tBoostStart = nowMs();
                AUTO.tBoostUntil = AUTO.tBoostStart + AUTO.boostMs;
                AUTO.drmBlocked = false;
                AUTO.blockUntilMs = 0;
                AUTO.blink = false; // FIX: Reset blink
                ADAPTIVE_FPS.current = ADAPTIVE_FPS.MIN;
                ADAPTIVE_FPS.history = [];
            };

            document.addEventListener('play', resetAuto, true);
            document.addEventListener('playing', resetAuto, true);
            document.addEventListener('loadeddata', () => {
                if (!autoOn) return;
                AUTO.lastSig = null;
                AUTO.lastLuma = null;
                AUTO.motionEma = 0;
                AUTO.motionFrames = 0;
                AUTO.scoreEma = 0;
                AUTO.statsEma = null;
                AUTO.drmBlocked = false;
                AUTO.blockUntilMs = 0;
                AUTO.blink = false;
                ADAPTIVE_FPS.current = ADAPTIVE_FPS.MIN;
                ADAPTIVE_FPS.history = [];
            }, true);
        } catch (_) { }
    }

    function scoreToIdx(score) {
        if (score < 0.020) return 0;
        if (score < 0.045) return 1;
        if (score < 0.075) return 2;
        if (score < 0.115) return 3;
        return 4;
    }

    function pickAutoFps(nowT, cutScore) {
        const a = clamp(AUTO.scoreAlpha, 0.05, 0.95);
        AUTO.scoreEma = (AUTO.scoreEma * (1 - a)) + (cutScore * a);

        const adaptiveFps = calculateAdaptiveFps(cutScore);

        if (nowT < AUTO.tBoostUntil) {
            const age = nowT - (AUTO.tBoostStart || nowT);
            const early = age >= 0 && age < AUTO.minBoostEarlyMs;
            const boostFps = early ? 10 : 8;
            return Math.max(adaptiveFps, boostFps);
        }

        return adaptiveFps;
    }

    function ensureAutoLoop() {
        if (AUTO.running) return;
        AUTO.running = true;

        const c = document.createElement('canvas');
        c.width = AUTO.canvasW;
        c.height = AUTO.canvasH;

        let ctx = null;
        try { ctx = c.getContext('2d', { willReadFrequently: true }); }
        catch (_) { try { ctx = c.getContext('2d'); } catch (__) { } }

        const scheduleNext = (fps) => {
            const ms = Math.max(80, Math.round(1000 / Math.max(1, fps)));
            setTimeout(loop, ms);
        };

        const loop = () => {
            if (!AUTO.running) return;

            if (!autoOn) {
                AUTO.lastSig = null;
                AUTO.lastLuma = null;
                AUTO.scoreEma = 0;
                AUTO.motionEma = 0;
                AUTO.motionFrames = 0;
                AUTO.statsEma = null;
                AUTO.drmBlocked = false;
                AUTO.blockUntilMs = 0;
                AUTO.lastAppliedMs = 0;
                AUTO.blink = false;
                setAutoDotState('off');
                scheduleNext(ADAPTIVE_FPS.MIN);
                return;
            }

            const tNow = nowMs();
            if (AUTO.drmBlocked && tNow < (AUTO.blockUntilMs || 0)) {
                setAutoDotState('idle');
                scheduleNext(ADAPTIVE_FPS.MIN);
                return;
            }

            const v = choosePrimaryVideo();
            if (!v || !ctx) {
                AUTO.lastSig = null;
                AUTO.lastLuma = null;
                AUTO.motionEma = 0;
                AUTO.motionFrames = 0;
                AUTO.statsEma = null;
                setAutoDotState('idle');

                const t = nowMs();
                if (LOG.on && (t - LOG.lastTickMs) >= LOG.tickEveryMs) {
                    LOG.lastTickMs = t;
                    log('Auto(A) running: no playable video found.');
                }
                scheduleNext(ADAPTIVE_FPS.MIN);
                return;
            }

            if (v.paused || v.seeking) {
                AUTO.motionFrames = 0;
                setAutoDotState('idle');
                scheduleNext(ADAPTIVE_FPS.MIN);
                return;
            }

            try {
                ctx.drawImage(v, 0, 0, AUTO.canvasW, AUTO.canvasH);
                const img = ctx.getImageData(0, 0, AUTO.canvasW, AUTO.canvasH);

                if (AUTO.drmBlocked) {
                    AUTO.drmBlocked = false;
                    AUTO.blockUntilMs = 0;
                }

                const motion = computeMotionFromImage(img);
                const ma = clamp(AUTO.motionAlpha, 0.05, 0.95);
                AUTO.motionEma = (AUTO.motionEma * (1 - ma)) + (motion * ma);

                const hasMotionNow = (AUTO.motionEma >= AUTO.motionThresh);
                AUTO.motionFrames = hasMotionNow ? (AUTO.motionFrames + 1) : 0;

                const sigRaw = computeFrameStats(img);
                const isCut = detectCut(sigRaw, AUTO.lastSig);
                AUTO.lastSig = sigRaw;

                const sig = updateStatsAveraging(sigRaw);

                if (isCut) {
                    AUTO.tBoostStart = nowMs();
                    AUTO.tBoostUntil = AUTO.tBoostStart + AUTO.boostMs;
                }

                const t = nowMs();
                const rawScore = clamp(sigRaw.__cutScore || 0, 0, 1);

                const hasMotion = (AUTO.motionFrames >= AUTO.motionMinFrames);
                const allowUpdate = isCut || hasMotion;

                let fps = ADAPTIVE_FPS.current;
                if (allowUpdate) {
                    fps = pickAutoFps(t, rawScore);
                }

                if (allowUpdate) {
                    updateAutoTargetsFromStats(sig);
                    updateAutoSmoothing(isCut);
                    setAutoMatrixAndApply();

                    AUTO.blink = !AUTO.blink;
                    setAutoDotState(AUTO.blink ? 'workBright' : 'workDark');
                } else {
                    setAutoDotState('idle');
                }

                if (LOG.on && (t - LOG.lastTickMs) >= LOG.tickEveryMs) {
                    LOG.lastTickMs = t;
                    log(
                        `Auto(A) tick @${fps.toFixed(1)}fps`,
                        `adaptive=${ADAPTIVE_FPS.current.toFixed(1)}fps`,
                        `update=${allowUpdate ? 'YES' : 'NO'}`,
                        `motion=${motion.toFixed(4)} ema=${AUTO.motionEma.toFixed(4)} thr=${AUTO.motionThresh.toFixed(3)} frames=${AUTO.motionFrames}/${AUTO.motionMinFrames}`,
                        `raw=${rawScore.toFixed(3)} emaScore=${AUTO.scoreEma.toFixed(3)}`,
                        `avgY=${(sig.mY || 0).toFixed(3)} avgSd=${(sig.sdY || 0).toFixed(3)} avgCh=${(sig.mCh || 0).toFixed(3)}`
                    );
                }

                scheduleNext(fps);
            } catch (e) {
                AUTO.drmBlocked = true;

                const t = nowMs();
                const nextWait = (AUTO.blockUntilMs && (t - AUTO.blockUntilMs) < 2000) ? 5000 : 2000;
                AUTO.blockUntilMs = t + nextWait;

                AUTO.lastSig = null;
                AUTO.lastLuma = null;
                AUTO.motionEma = 0;
                AUTO.motionFrames = 0;
                AUTO.statsEma = null;
                AUTO.blink = false; // FIX: Reset blink

                const keep = AUTO.lastGoodMatrixStr || _autoLastMatrixStr || autoMatrixStr || matToSvgValues(matIdentity4x5());
                autoMatrixStr = keep;
                _autoLastMatrixStr = keep;
                updateAutoMatrixInSvg(keep);

                setAutoDotState('idle');

                if (LOG.on && (t - LOG.lastTickMs) >= LOG.tickEveryMs) {
                    LOG.lastTickMs = t;
                    logW('Auto(A) DRM/cross-origin: pixels blocked. Using last AutoMatrix (static) + backoff.', e && e.message ? e.message : e);
                }

                scheduleNext(ADAPTIVE_FPS.MIN);
            }
        };

        log(`Auto analyzer loop created with ADAPTIVE FPS (2-10fps). levels=${AUTO_LEVELS.join(',')} canvas=${AUTO.canvasW}x${AUTO.canvasH} motionThresh=${AUTO.motionThresh}`);
        scheduleNext(ADAPTIVE_FPS.MIN);
    }

    function setAutoOn(on, opts = {}) {
        const silent = !!opts.silent;
        const next = !!on;

        if (next === autoOn && AUTO.running) {
            if (!silent) {
                scheduleOverlayUpdate();
                setAutoDotState(next ? 'idle' : 'off');
            }
            return;
        }

        autoOn = next;
        if (!_inSync) gmSet(K.AUTO_ON, autoOn);

        logToggle('Auto Scene Match (Ctrl+Alt+A)', autoOn, `(strength=${autoStrength.toFixed(2)}, lockWB=${autoLockWB ? 'yes' : 'no'}, adaptive FPS 2-10)`);
        if (!silent) showToggleNotification('Auto-Scene-Match', autoOn);

        if (!autoOn) {
            AUTO.lastSig = null;
            AUTO.lastLuma = null;
            AUTO.motionEma = 0;
            AUTO.motionFrames = 0;
            AUTO.scoreEma = 0;
            AUTO.statsEma = null;
            AUTO.tBoostUntil = 0;
            AUTO.tBoostStart = 0;
            AUTO.tgt = { br: 1.0, ct: 1.0, sat: 1.0, hue: 0.0 };
            AUTO.drmBlocked = false;
            AUTO.blockUntilMs = 0;
            AUTO.lastAppliedMs = 0;
            AUTO.blink = false;

            autoMatrixStr = matToSvgValues(matIdentity4x5());
            _autoLastMatrixStr = autoMatrixStr;
            AUTO.lastGoodMatrixStr = autoMatrixStr;
            updateAutoMatrixInSvg(autoMatrixStr);

            setAutoDotState('off');

            if (!silent) {
                applyFilter({ skipSvgIfPossible: true });
                scheduleOverlayUpdate();
            }
            return;
        }

        AUTO.lastAppliedMs = 0;
        ADAPTIVE_FPS.current = ADAPTIVE_FPS.MIN;
        ADAPTIVE_FPS.history = [];

        setAutoDotState('idle');
        ensureAutoLoop();

        if (!silent) {
            applyFilter({ skipSvgIfPossible: false });
            setAutoMatrixAndApply();
            scheduleOverlayUpdate();
        } else {
            autoMatrixStr = matToSvgValues(buildAutoMatrixValues());
            _autoLastMatrixStr = autoMatrixStr;
            AUTO.lastGoodMatrixStr = autoMatrixStr;
            updateAutoMatrixInSvg(autoMatrixStr);
        }
    }

    // -------------------------
    // Config Menu (User Profile Management)
    // -------------------------
    let configMenuVisible = false;

    function createConfigMenu() {
        // Remove existing menu, if any
        let existingMenu = document.getElementById(CONFIG_MENU_ID);
        if (existingMenu) {
            existingMenu.remove();
        }

        const menu = document.createElement('div');
        menu.id = CONFIG_MENU_ID;
        menu.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 820px;
            max-width: 98vw;
            max-height: 88vh;
            background: rgba(20, 20, 20, 0.98);
            backdrop-filter: blur(10px);
            border: 2px solid #2a6fdb;
            border-radius: 16px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255,255,255,0.1) inset;
            color: #eaeaea;
            font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
            z-index: 2147483647;
            display: none;
            flex-direction: column;
            padding: 20px;
            user-select: none;
            pointer-events: auto;
        `;
        stopEventsOn(menu);

        // Header
        const header = document.createElement('div');
        header.style.cssText = `
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 2px solid #2a6fdb;
        `;

        const title = document.createElement('div');
        title.style.cssText = `
            font-size: 20px;
            font-weight: 900;
            color: #fff;
            text-shadow: 0 0 10px #2a6fdb;
        `;
        title.textContent = '👤 User Profile Manager';

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '✕';
        closeBtn.style.cssText = `
            background: rgba(255, 255, 255, 0.1);
            border: none;
            color: #fff;
            font-size: 20px;
            cursor: pointer;
            width: 36px;
            height: 36px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s;
            border: 1px solid rgba(255,255,255,0.2);
        `;
        closeBtn.addEventListener('mouseenter', () => {
            closeBtn.style.background = 'rgba(255, 68, 68, 0.3)';
            closeBtn.style.borderColor = '#ff4444';
        });
        closeBtn.addEventListener('mouseleave', () => {
            closeBtn.style.background = 'rgba(255, 255, 255, 0.1)';
            closeBtn.style.borderColor = 'rgba(255,255,255,0.2)';
        });
        closeBtn.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            toggleConfigMenu();
        });

        header.appendChild(title);
        header.appendChild(closeBtn);
        menu.appendChild(header);

        makeFloatingManagerDraggable(menu, header, K.USER_PROFILE_MANAGER_POS);

        // Show active profile
        const activeInfo = document.createElement('div');
        activeInfo.id = 'gvf-active-profile-info';
        activeInfo.style.cssText = `
            background: rgba(42, 111, 219, 0.2);
            border: 1px solid #2a6fdb;
            border-radius: 8px;
            padding: 10px;
            margin-bottom: 15px;
            font-size: 13px;
            display: flex;
            align-items: center;
            gap: 8px;
        `;

        function setActiveProfileInfo(el, profileName) {
            if (!el) return;
            while (el.firstChild) el.removeChild(el.firstChild);

            const name = profileName || 'Default';
            el.append('🔵 Active profile: ');

            const strong = document.createElement('strong');
            strong.textContent = name;
            el.appendChild(strong);
        }

        setActiveProfileInfo(activeInfo, activeUserProfile?.name);
        menu.appendChild(activeInfo);

        // Profile List Container
        const listContainer = document.createElement('div');
        listContainer.style.cssText = `
            flex: 1;
            overflow-y: auto;
            margin-bottom: 20px;
            max-height: 300px;
            background: rgba(0,0,0,0.3);
            border-radius: 8px;
            padding: 5px;
        `;

        const profileList = document.createElement('div');
        profileList.id = 'gvf-profile-list';
        profileList.style.cssText = `
            display: flex;
            flex-direction: column;
            gap: 8px;
        `;

        listContainer.appendChild(profileList);
        menu.appendChild(listContainer);

        // Input for new profile
        const inputContainer = document.createElement('div');
        inputContainer.style.cssText = `
            display: flex;
            gap: 8px;
            margin-bottom: 16px;
        `;

        const newProfileInput = document.createElement('input');
        newProfileInput.type = 'text';
        newProfileInput.placeholder = 'New profile name...';
        newProfileInput.id = 'gvf-new-profile-name';
        newProfileInput.style.cssText = `
            flex: 1;
            background: rgba(0, 0, 0, 0.5);
            border: 1px solid rgba(255, 255, 255, 0.2);
            border-radius: 8px;
            padding: 10px 12px;
            color: #fff;
            font-size: 14px;
            outline: none;
            transition: border 0.2s;
        `;
        newProfileInput.addEventListener('focus', () => {
            newProfileInput.style.borderColor = '#2a6fdb';
        });
        newProfileInput.addEventListener('blur', () => {
            newProfileInput.style.borderColor = 'rgba(255, 255, 255, 0.2)';
        });

        const addBtn = document.createElement('button');
        addBtn.textContent = '+ Add';
        addBtn.style.cssText = `
            background: #2a6fdb;
            border: none;
            color: #fff;
            padding: 10px 16px;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 900;
            cursor: pointer;
            transition: all 0.2s;
            border: 1px solid transparent;
        `;
        addBtn.addEventListener('mouseenter', () => {
            addBtn.style.background = '#3a7feb';
            addBtn.style.transform = 'scale(1.02)';
        });
        addBtn.addEventListener('mouseleave', () => {
            addBtn.style.background = '#2a6fdb';
            addBtn.style.transform = 'scale(1)';
        });
        addBtn.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            const name = newProfileInput.value.trim();
            if (name) {
                createNewUserProfile(name);
                updateProfileList();
                newProfileInput.value = '';

                // Update active info
                const activeInfo = document.getElementById('gvf-active-profile-info');
                if (activeInfo) {
                    setActiveProfileInfo(activeInfo, activeUserProfile?.name);
                }
            } else {
                alert('Please enter a name!');
            }
        });

        inputContainer.appendChild(newProfileInput);
        inputContainer.appendChild(addBtn);
        menu.appendChild(inputContainer);

        // Buttons
        const buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = `
            display: flex;
            gap: 8px;
            justify-content: flex-end;
            margin-top: 10px;
        `;

        const saveCurrentBtn = document.createElement('button');
        saveCurrentBtn.textContent = '💾 Save current profile';
        saveCurrentBtn.style.cssText = `
            background: rgba(255, 255, 255, 0.1);
            border: 1px solid rgba(255, 255, 255, 0.2);
            color: #fff;
            padding: 10px 16px;
            border-radius: 8px;
            font-size: 13px;
            font-weight: 900;
            cursor: pointer;
            transition: all 0.2s;
            flex: 1;
        `;
        saveCurrentBtn.addEventListener('mouseenter', () => {
            saveCurrentBtn.style.background = 'rgba(255, 255, 255, 0.2)';
            saveCurrentBtn.style.borderColor = '#2a6fdb';
        });
        saveCurrentBtn.addEventListener('mouseleave', () => {
            saveCurrentBtn.style.background = 'rgba(255, 255, 255, 0.1)';
            saveCurrentBtn.style.borderColor = 'rgba(255,255,255,0.2)';
        });
        saveCurrentBtn.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            if (!activeUserProfile && Array.isArray(userProfiles) && userProfiles.length) {
                activeUserProfile = userProfiles[0];
            }
            updateCurrentProfileSettings(true);
            saveUserProfiles();
            updateProfileList();
            showScreenNotification('', {
                title: `Profile "${String(activeUserProfile?.name || 'Default')}" saved`,
                detail: 'User Profile Manager',
                detailColor: '#4cff6a'
            });

            // Brief feedback
            saveCurrentBtn.textContent = '✓ Saved!';
            setTimeout(() => {
                saveCurrentBtn.textContent = '💾 Save current profile';
            }, 1000);
        });


        buttonContainer.appendChild(saveCurrentBtn);

        // Import / Export (profiles as ZIP of per-profile JSON)
        const importExportRow = document.createElement('div');
        importExportRow.style.cssText = `
            display: flex;
            gap: 8px;
            justify-content: space-between;
            margin-top: 8px;
        `;

        const exportProfilesBtn = document.createElement('button');
        exportProfilesBtn.textContent = '📦 Export profiles (ZIP)';
        exportProfilesBtn.style.cssText = `
            background: rgba(42, 111, 219, 0.25);
            border: 1px solid rgba(42, 111, 219, 0.6);
            color: #fff;
            padding: 10px 12px;
            border-radius: 8px;
            font-size: 13px;
            font-weight: 900;
            cursor: pointer;
            transition: all 0.2s;
            flex: 1;
        `;

        const importProfilesBtn = document.createElement('button');
        importProfilesBtn.textContent = '📥 Import profiles (ZIP/JSON)';
        importProfilesBtn.style.cssText = `
            background: rgba(255, 255, 255, 0.08);
            border: 1px solid rgba(255, 255, 255, 0.2);
            color: #fff;
            padding: 10px 12px;
            border-radius: 8px;
            font-size: 13px;
            font-weight: 900;
            cursor: pointer;
            transition: all 0.2s;
            flex: 1;
        `;

        const profileFileInput = document.createElement('input');
        profileFileInput.type = 'file';
        profileFileInput.accept = '.zip,application/zip,.json,application/json';
        profileFileInput.style.display = 'none';
        stopEventsOn(profileFileInput);

        const setTmpStatus = (msg) => {
            // Reuse existing "activeInfo" line as a small status area without adding new UI noise.
            const el = document.getElementById('gvf-active-profile-info');
            if (!el) return;
            el.title = String(msg || '');
        };

        exportProfilesBtn.addEventListener('mouseenter', () => {
            exportProfilesBtn.style.background = 'rgba(42, 111, 219, 0.35)';
            exportProfilesBtn.style.borderColor = '#2a6fdb';
        });
        exportProfilesBtn.addEventListener('mouseleave', () => {
            exportProfilesBtn.style.background = 'rgba(42, 111, 219, 0.25)';
            exportProfilesBtn.style.borderColor = 'rgba(42, 111, 219, 0.6)';
        });

        importProfilesBtn.addEventListener('mouseenter', () => {
            importProfilesBtn.style.background = 'rgba(255, 255, 255, 0.14)';
            importProfilesBtn.style.borderColor = '#2a6fdb';
        });
        importProfilesBtn.addEventListener('mouseleave', () => {
            importProfilesBtn.style.background = 'rgba(255, 255, 255, 0.08)';
            importProfilesBtn.style.borderColor = 'rgba(255, 255, 255, 0.2)';
        });

        exportProfilesBtn.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();

            try {
                const zipBlob = exportAllUserProfilesAsZip();
                if (!zipBlob) {
                    logW('No profiles to export.');
                    setTmpStatus('No profiles to export.');
                    return;
                }

                const zipName = _zipName('gvf_user_profiles');
                downloadBlob(zipBlob, zipName);

                log('Exported profiles ZIP:', zipName);
                setTmpStatus('Profiles exported as ZIP.');
            } catch (err) {
                logW('Profile export failed:', err);
                setTmpStatus('Profile export failed.');
            }
        });

        importProfilesBtn.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            try { profileFileInput.value = ''; } catch (_) { }
            profileFileInput.click();
        });

        profileFileInput.addEventListener('change', async () => {
            const f = profileFileInput.files && profileFileInput.files[0];
            if (!f) return;
            setTmpStatus('Importing profiles...');
            const ok = await importProfilesFromZipOrJsonFile(f, null);
            setTmpStatus(ok ? 'Profiles imported.' : 'Profile import failed.');
            try { profileFileInput.value = ''; } catch (_) { }
        });

        importExportRow.appendChild(exportProfilesBtn);
        importExportRow.appendChild(importProfilesBtn);

        menu.appendChild(importExportRow);
        menu.appendChild(profileFileInput);
        menu.appendChild(buttonContainer);

        // Add to body
        if (document.body) {
            document.body.appendChild(menu);
            applyManagerPosition(menu, K.USER_PROFILE_MANAGER_POS);
            log('Config menu created and added to the body');
        }

        return menu;
    }

    // -------------------------
    // Shared Slideshow Lightbox
    // entries: Array<{ bigCanvas, bigReady, label }>
    // startIndex: which entry to show first
    // accentColor: CSS color string for title glow
    // -------------------------
    function openSlideshow(entries, startIndex, accentColor) {
        if (!entries || !entries.length) return;
        accentColor = accentColor || '#4a9eff';
        let idx = Math.max(0, Math.min(startIndex || 0, entries.length - 1));

        const overlay = document.createElement('div');
        overlay.style.cssText = `
            position:fixed;inset:0;z-index:2147483647;
            background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);
            display:flex;flex-direction:column;align-items:center;justify-content:center;
            gap:12px;user-select:none;
        `;

        // Title
        const lbTitle = document.createElement('div');
        lbTitle.style.cssText = `color:#fff;font-size:17px;font-weight:900;
            text-shadow:0 0 14px ${accentColor};pointer-events:none;text-align:center;
            max-width:80vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`;

        // Counter  e.g. "3 / 7"
        const lbCounter = document.createElement('div');
        lbCounter.style.cssText = `color:#aaa;font-size:12px;font-weight:700;
            pointer-events:none;letter-spacing:0.05em;`;

        // Image area
        const imgWrap = document.createElement('div');
        imgWrap.style.cssText = `position:relative;display:flex;align-items:center;
            justify-content:center;max-width:90vw;max-height:65vh;`;

        // Canvas slot — we swap canvas references into this wrapper
        const canvasSlot = document.createElement('div');
        canvasSlot.style.cssText = `display:flex;align-items:center;justify-content:center;`;
        imgWrap.appendChild(canvasSlot);

        // Prev / Next nav buttons
        const mkNavBtn = (label) => {
            const b = document.createElement('button');
            b.type = 'button'; b.textContent = label;
            b.style.cssText = `
                position:absolute;top:50%;transform:translateY(-50%);
                background:rgba(0,0,0,0.55);border:1px solid rgba(255,255,255,0.25);
                color:#fff;font-size:22px;font-weight:900;
                width:44px;height:44px;border-radius:50%;
                cursor:pointer;display:flex;align-items:center;justify-content:center;
                transition:background 0.15s;z-index:2;
            `;
            b.addEventListener('mouseenter', () => { b.style.background = `rgba(0,0,0,0.82)`; });
            b.addEventListener('mouseleave', () => { b.style.background = `rgba(0,0,0,0.55)`; });
            b.addEventListener('click', (e) => { e.stopPropagation(); });
            return b;
        };
        const prevBtn = mkNavBtn('‹');
        prevBtn.style.left = '-54px';
        const nextBtn = mkNavBtn('›');
        nextBtn.style.right = '-54px';
        imgWrap.appendChild(prevBtn);
        imgWrap.appendChild(nextBtn);

        // Close button
        const lbClose = document.createElement('button');
        lbClose.type = 'button'; lbClose.textContent = '✕';
        lbClose.style.cssText = `
            position:absolute;top:16px;right:16px;
            background:rgba(255,255,255,0.12);border:1px solid rgba(255,255,255,0.25);
            color:#fff;font-size:18px;font-weight:900;width:36px;height:36px;
            border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;
        `;
        lbClose.addEventListener('click', (ev) => { ev.stopPropagation(); overlay.remove(); });
        stopEventsOn(lbClose);

        // Dot indicators
        const dotsWrap = document.createElement('div');
        dotsWrap.style.cssText = `display:flex;gap:6px;align-items:center;`;
        const dots = entries.map((_, i) => {
            const d = document.createElement('div');
            d.style.cssText = `width:8px;height:8px;border-radius:50%;cursor:pointer;
                background:rgba(255,255,255,0.25);transition:background 0.2s,transform 0.2s;`;
            d.addEventListener('click', (e) => { e.stopPropagation(); goTo(i); });
            dotsWrap.appendChild(d);
            return d;
        });

        function renderEntry() {
            const entry = entries[idx];
            lbTitle.textContent = entry.label || '';
            lbCounter.textContent = `${idx + 1} / ${entries.length}`;
            prevBtn.style.visibility = entries.length > 1 ? 'visible' : 'hidden';
            nextBtn.style.visibility = entries.length > 1 ? 'visible' : 'hidden';

            // Update dots
            dots.forEach((d, i) => {
                d.style.background = i === idx ? accentColor : 'rgba(255,255,255,0.25)';
                d.style.transform = i === idx ? 'scale(1.3)' : 'scale(1)';
            });

            // Swap canvas content
            while (canvasSlot.firstChild) canvasSlot.removeChild(canvasSlot.firstChild);
            if (entry.bigReady && entry.bigCanvas) {
                entry.bigCanvas.style.cssText = 'display:block;width:auto;height:auto;max-width:90vw;max-height:65vh;border-radius:10px;';
                entry.bigCanvas.addEventListener('click', (e) => e.stopPropagation());
                canvasSlot.appendChild(entry.bigCanvas);
            } else {
                const msg = document.createElement('div');
                msg.textContent = 'Preview loading…';
                msg.style.cssText = 'color:#fff;opacity:0.7;font-size:14px;';
                canvasSlot.appendChild(msg);
                // Poll until ready
                const poll = setInterval(() => {
                    if (entries[idx] === entry && entry.bigReady && entry.bigCanvas) {
                        clearInterval(poll);
                        renderEntry();
                    }
                }, 150);
            }
        }

        function goTo(i) {
            idx = ((i % entries.length) + entries.length) % entries.length;
            renderEntry();
        }

        prevBtn.addEventListener('click', () => goTo(idx - 1));
        nextBtn.addEventListener('click', () => goTo(idx + 1));

        // Swipe support
        let touchStartX = 0;
        overlay.addEventListener('touchstart', (e) => { touchStartX = e.touches[0].clientX; }, { passive: true });
        overlay.addEventListener('touchend', (e) => {
            const dx = e.changedTouches[0].clientX - touchStartX;
            if (Math.abs(dx) > 40) goTo(dx < 0 ? idx + 1 : idx - 1);
        }, { passive: true });

        // Arrow key support
        const onKey = (e) => {
            if (e.key === 'ArrowLeft') { e.stopPropagation(); goTo(idx - 1); }
            if (e.key === 'ArrowRight') { e.stopPropagation(); goTo(idx + 1); }
            if (e.key === 'Escape') { overlay.remove(); document.removeEventListener('keydown', onKey, true); }
        };
        document.addEventListener('keydown', onKey, true);
        overlay.addEventListener('click', () => { overlay.remove(); document.removeEventListener('keydown', onKey, true); });

        overlay.appendChild(lbTitle);
        overlay.appendChild(lbCounter);
        overlay.appendChild(imgWrap);
        if (entries.length > 1) overlay.appendChild(dotsWrap);
        overlay.appendChild(lbClose);
        stopEventsOn(overlay);
        // Re-allow click-to-close on the overlay background
        overlay.addEventListener('click', () => { overlay.remove(); document.removeEventListener('keydown', onKey, true); });
        (document.body || document.documentElement).appendChild(overlay);
        renderEntry();
    }

    function updateProfileList() {
        const list = document.getElementById('gvf-profile-list');
        if (!list) { logW('Profile list not found'); return; }

        while (list.firstChild) list.removeChild(list.firstChild);

        const LW = 1280, LH = 720, PW = 160, PH = 90;

        // Capture raw video frame ONCE — shared base for all profile previews
        let rawCanvas = null;
        (() => {
            try {
                const c = document.createElement('canvas');
                c.width = LW; c.height = LH;
                const ctx = c.getContext('2d', { alpha: false, willReadFrequently: true });
                if (!ctx) return;
                let drew = false;
                let video = getHudPrimaryVideo();
                if (!video) { const all = Array.from(document.querySelectorAll('video')); video = all.find(v => v.readyState >= 2 && v.videoWidth > 0) || null; }
                if (video && video.readyState >= 2 && video.videoWidth > 0) {
                    try { ctx.drawImage(video, 0, 0, LW, LH); ctx.getImageData(0, 0, 1, 1); drew = true; } catch(_) {}
                }
                if (!drew) {
                    const grad = ctx.createLinearGradient(0, 0, LW, LH);
                    grad.addColorStop(0,'#1a3a5c'); grad.addColorStop(0.25,'#c85032');
                    grad.addColorStop(0.5,'#f0c040'); grad.addColorStop(0.75,'#3ab56a'); grad.addColorStop(1,'#8040c0');
                    ctx.fillStyle = grad; ctx.fillRect(0, 0, LW, LH);
                }
                rawCanvas = c;
            } catch(_) {}
        })();

        // Snapshot/restore helpers
        const snapshotGlobals = () => ({
            enabled, darkMoody, tealOrange, vibrantSat, notify,
            sl, sr, bl, wl, dn, edge, hdr, profile, renderMode,
            autoOn, autoStrength, autoLockWB,
            u_contrast, u_black, u_white, u_highlights, u_shadows,
            u_sat, u_vib, u_sharp, u_gamma, u_grain, u_hue,
            u_r_gain, u_g_gain, u_b_gain, cbFilter,
            activeLutProfileKey: String(activeLutProfileKey)
        });

        // Apply profile settings temporarily, render onto destCanvas, restore
        const renderProfilePreview = (profileSettings, destCanvas) => {
            if (!rawCanvas) return;
            try { renderFrameWithSettings(destCanvas, rawCanvas, profileSettings); } catch(_) {}
        };

        const renderQueue = [];
        const slideshowEntries = []; // filled per-profile for slideshow

        userProfiles.forEach(userProf => {
            const isActive = activeUserProfile && activeUserProfile.id === userProf.id;

            const item = document.createElement('div');
            item.style.cssText = `display:flex;align-items:center;gap:12px;padding:10px 12px;
                background:${isActive ? 'rgba(42,111,219,0.3)' : 'rgba(255,255,255,0.05)'};
                border:2px solid ${isActive ? '#2a6fdb' : 'rgba(255,255,255,0.1)'};
                border-radius:8px;margin:2px 0;`;

            // Thumbnail
            const previewWrap = document.createElement('div');
            previewWrap.style.cssText = `flex-shrink:0;border-radius:6px;overflow:hidden;
                border:1px solid rgba(42,111,219,0.45);width:${PW}px;height:${PH}px;background:#111;cursor:zoom-in;`;
            const thumbCanvas = document.createElement('canvas');
            thumbCanvas.width = PW; thumbCanvas.height = PH;
            thumbCanvas.style.cssText = `display:block;width:${PW}px;height:${PH}px;`;
            previewWrap.appendChild(thumbCanvas);

            // Big canvas for lightbox
            const bigCanvas = document.createElement('canvas');
            bigCanvas.width = LW; bigCanvas.height = LH;
            bigCanvas.style.cssText = 'display:block;width:auto;height:auto;max-width:90vw;max-height:65vh;border-radius:10px;';
            let bigReady = false;

            const entry = { bigCanvas, get bigReady() { return bigReady; }, label: '👤 ' + String(userProf.name) + (isActive ? '  · active' : '') };
            slideshowEntries.push(entry);
            renderQueue.push({ settings: userProf.settings || {}, bigCanvas, thumbCanvas, onDone: () => { bigReady = true; } });

            // Slideshow lightbox
            previewWrap.addEventListener('click', (e) => {
                e.stopPropagation();
                openSlideshow(slideshowEntries, slideshowEntries.indexOf(entry), '#2a6fdb');
            });
            stopEventsOn(previewWrap);

            // Info
            const info = document.createElement('div');
            info.style.cssText = `display:flex;flex-direction:column;gap:4px;flex:1;min-width:0;overflow:hidden;`;
            const nameSpan = document.createElement('span');
            nameSpan.style.cssText = `font-weight:${isActive ? '900' : '600'};color:${isActive ? '#fff' : '#ccc'};
                font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`;
            nameSpan.textContent = userProf.name + (isActive ? ' (active)' : '');
            const dateSpan = document.createElement('span');
            dateSpan.style.cssText = `font-size:11px;color:#888;`;
            dateSpan.textContent = 'Created: ' + new Date(userProf.createdAt).toLocaleDateString('en-US');
            info.appendChild(nameSpan); info.appendChild(dateSpan);

            // Actions
            const actions = document.createElement('div');
            actions.style.cssText = `display:flex;gap:8px;flex-shrink:0;`;

            const mkActionBtn = (text, bg, border, color) => {
                const b = document.createElement('button');
                b.type = 'button'; b.textContent = text;
                b.style.cssText = `background:${bg};border:1px solid ${border};color:${color};
                    padding:6px 12px;border-radius:6px;font-size:12px;font-weight:900;cursor:pointer;`;
                stopEventsOn(b); return b;
            };

            if (!isActive) {
                const activateBtn = mkActionBtn('Activate','rgba(42,111,219,0.3)','#2a6fdb','#fff');
                activateBtn.addEventListener('mouseenter', () => { activateBtn.style.background='rgba(42,111,219,0.5)'; });
                activateBtn.addEventListener('mouseleave', () => { activateBtn.style.background='rgba(42,111,219,0.3)'; });
                activateBtn.addEventListener('click', (e) => {
                    e.preventDefault(); e.stopPropagation();
                    switchToUserProfile(userProf.id); updateProfileList();
                    const ai = document.getElementById('gvf-active-profile-info');
                    if (ai) setActiveProfileInfo(ai, activeUserProfile?.name);
                });
                actions.appendChild(activateBtn);
            }

            // Edit button — floating window, uses same rawCanvas
            const editBtn = mkActionBtn('Edit','rgba(255,255,255,0.08)','rgba(255,255,255,0.3)','#fff');
            editBtn.addEventListener('click', (e) => {
                e.preventDefault(); e.stopPropagation();
                const existingEd = document.getElementById('gvf-userProf-edit-window');
                if (existingEd) existingEd.remove();

                const EW = 680;
                const win = document.createElement('div');
                win.id = 'gvf-userProf-edit-window';
                win.style.cssText = `position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);
                    width:${EW}px;max-width:96vw;max-height:90vh;
                    background:rgba(18,18,18,0.98);backdrop-filter:blur(12px);
                    border:2px solid #2a6fdb;border-radius:16px;box-shadow:0 20px 60px rgba(0,0,0,0.85);
                    color:#eaeaea;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
                    z-index:2147483647;display:flex;flex-direction:column;padding:20px;gap:12px;
                    user-select:none;pointer-events:auto;overflow-y:auto;`;
                stopEventsOn(win);

                const edHeader = document.createElement('div');
                edHeader.style.cssText = `display:flex;justify-content:space-between;align-items:center;
                    padding-bottom:10px;border-bottom:2px solid #2a6fdb;flex-shrink:0;cursor:move;`;
                const edTitle = document.createElement('div');
                edTitle.textContent = '✏️ Edit — ' + String(userProf.name);
                edTitle.style.cssText = `font-size:17px;font-weight:900;color:#fff;text-shadow:0 0 10px rgba(42,111,219,0.6);`;
                const edClose = document.createElement('button');
                edClose.type = 'button'; edClose.textContent = '✕';
                edClose.style.cssText = `background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.2);
                    color:#fff;font-size:18px;font-weight:900;width:34px;height:34px;
                    border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;`;
                edClose.addEventListener('click', () => win.remove());
                stopEventsOn(edClose);
                edHeader.appendChild(edTitle); edHeader.appendChild(edClose);
                win.appendChild(edHeader);
                makeFloatingManagerDraggable(win, edHeader, null);

                // Preview canvas — rendered once on open, updated only on Apply Preview
                const epW = EW - 44, epH = Math.round(epW * 9/16);
                const previewC = document.createElement('canvas');
                previewC.width = LW; previewC.height = LH;
                previewC.style.cssText = `display:block;width:${epW}px;height:${epH}px;
                    border-radius:8px;border:1px solid rgba(42,111,219,0.4);flex-shrink:0;`;
                const previewLabel = document.createElement('div');
                previewLabel.style.cssText = `font-size:11px;color:#888;text-align:center;margin-top:-4px;`;
                previewLabel.textContent = 'Preview — click Apply Preview to update';
                win.appendChild(previewC); win.appendChild(previewLabel);

                // Render using rawCanvas (already captured, no new video grab, no global changes)
                const edRenderPreview = (settingsObj) => {
                    try {
                        if (rawCanvas) {
                            renderFrameWithSettings(previewC, rawCanvas, settingsObj);
                            previewLabel.textContent = 'Preview — filter applied ✓';
                        } else {
                            // gradient fallback
                            const offC = document.createElement('canvas'); offC.width=LW; offC.height=LH;
                            const oCtx = offC.getContext('2d',{alpha:false});
                            if (oCtx) {
                                const grad = oCtx.createLinearGradient(0,0,LW,LH);
                                grad.addColorStop(0,'#1a3a5c'); grad.addColorStop(0.5,'#f0c040'); grad.addColorStop(1,'#8040c0');
                                oCtx.fillStyle=grad; oCtx.fillRect(0,0,LW,LH);
                            }
                            renderFrameWithSettings(previewC, offC, settingsObj);
                            previewLabel.textContent = 'Preview — no video, gradient used';
                        }
                    } catch(err) { previewLabel.textContent = 'Preview error: ' + err.message; }
                };

                // Render once on open
                setTimeout(() => edRenderPreview(userProf.settings || {}), 0);

                const textarea = document.createElement('textarea');
                textarea.style.cssText = `width:100%;min-height:240px;resize:vertical;flex-shrink:0;
                    background:rgba(0,0,0,0.55);border:1px solid rgba(255,255,255,0.18);
                    border-radius:8px;padding:10px 12px;color:#e8e8e8;
                    font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;
                    line-height:1.4;outline:none;`;
                stopEventsOn(textarea);
                try { textarea.value = JSON.stringify(userProf, null, 2); } catch(_) { textarea.value = '{}'; }
                win.appendChild(textarea);

                const btnRow = document.createElement('div');
                btnRow.style.cssText = 'display:flex;gap:8px;align-items:center;flex-shrink:0;';
                const applyPreviewBtn = mkActionBtn('👁 Apply Preview','rgba(255,138,0,0.2)','#ff8a00','#ffd7a6');
                const saveJsonBtn    = mkActionBtn('💾 Save','rgba(42,111,219,0.35)','#2a6fdb','#fff');
                const cancelBtn      = mkActionBtn('Cancel','rgba(255,255,255,0.08)','rgba(255,255,255,0.2)','#ccc');
                const errMsg = document.createElement('div');
                errMsg.style.cssText = 'font-size:11px;color:#ff6b6b;flex:1;';

                const parseAndValidate = () => {
                    let parsed;
                    try {
                        parsed = JSON.parse(textarea.value);
                    } catch(e) {
                        throw new Error('JSON syntax error: ' + e.message);
                    }
                    if (!parsed || typeof parsed !== 'object') throw new Error('JSON must be an object');
                    parsed.id = userProf.id;
                    if (!parsed.name || typeof parsed.name !== 'string' || !parsed.name.trim()) {
                        parsed.name = userProf.name || 'Profile';
                    }
                    // If settings missing, fall back to original profile settings
                    if (!parsed.settings || typeof parsed.settings !== 'object') {
                        parsed.settings = userProf.settings || {};
                    }
                    try {
                        parsed.settings = buildImportedUserProfileSettings(parsed.settings);
                    } catch(e) {
                        throw new Error('Settings merge failed: ' + e.message);
                    }
                    return parsed;
                };

                applyPreviewBtn.addEventListener('click', (ev) => {
                    ev.preventDefault(); ev.stopPropagation();
                    try { errMsg.textContent = ''; edRenderPreview(parseAndValidate().settings); }
                    catch(err) { errMsg.textContent = 'JSON error: ' + err.message; }
                });
                saveJsonBtn.addEventListener('click', (ev) => {
                    ev.preventDefault(); ev.stopPropagation();
                    errMsg.textContent = '';
                    let parsed;
                    try { parsed = parseAndValidate(); } catch(err) { errMsg.textContent = 'Parse error: ' + err.message; return; }
                    try {
                        parsed.updatedAt = Date.now();
                        parsed.createdAt = userProf.createdAt || Date.now();
                        if (!parsed.name || !String(parsed.name).trim()) parsed.name = userProf.name || 'Profile';
                        // Search by id (string compare) — robust against array replacement
                        const profileId = String(userProf.id || '');
                        let idx = userProfiles.findIndex(p => p && String(p.id) === profileId);
                        if (idx < 0) {
                            // Fallback: search by name
                            idx = userProfiles.findIndex(p => p && String(p.name) === String(userProf.name));
                        }
                        if (idx >= 0) {
                            userProfiles[idx] = parsed;
                        } else {
                            // Profile not found — add it
                            userProfiles.push(parsed);
                        }
                        saveUserProfiles();
                        win.remove();
                        updateProfileList();
                        const ai = document.getElementById('gvf-active-profile-info');
                        if (ai) setActiveProfileInfo(ai, activeUserProfile?.name);
                    } catch(err) { errMsg.textContent = 'Save error: ' + err.message; }
                });
                cancelBtn.addEventListener('click', (ev) => { ev.preventDefault(); ev.stopPropagation(); win.remove(); });
                btnRow.appendChild(applyPreviewBtn); btnRow.appendChild(saveJsonBtn);
                btnRow.appendChild(cancelBtn); btnRow.appendChild(errMsg);
                win.appendChild(btnRow);
                (document.body || document.documentElement).appendChild(win);
            });
            actions.appendChild(editBtn);

            if (userProf.id !== 'default') {
                const deleteBtn = mkActionBtn('✕ Delete','rgba(255,68,68,0.2)','#ff4444','#ff8888');
                deleteBtn.addEventListener('mouseenter', () => { deleteBtn.style.background='rgba(255,68,68,0.4)'; deleteBtn.style.color='#fff'; });
                deleteBtn.addEventListener('mouseleave', () => { deleteBtn.style.background='rgba(255,68,68,0.2)'; deleteBtn.style.color='#ff8888'; });
                deleteBtn.addEventListener('click', (e) => {
                    e.preventDefault(); e.stopPropagation();
                    if (confirm(`Really delete userProf "${userProf.name}"?`)) {
                        deleteUserProfile(userProf.id); updateProfileList();
                        const ai = document.getElementById('gvf-active-profile-info');
                        if (ai) setActiveProfileInfo(ai, activeUserProfile?.name);
                    }
                });
                actions.appendChild(deleteBtn);
            }

            item.appendChild(previewWrap); item.appendChild(info); item.appendChild(actions);
            list.appendChild(item);
        });

        // Render previews sequentially — one per setTimeout tick, no re-render after
        const processQueue = (i) => {
            if (i >= renderQueue.length) return;
            const { settings, bigCanvas, thumbCanvas, onDone } = renderQueue[i];
            setTimeout(() => {
                try {
                    renderProfilePreview(settings, bigCanvas);
                    onDone();
                    const tCtx = thumbCanvas.getContext('2d', { alpha: false });
                    if (tCtx) {
                        tCtx.imageSmoothingEnabled = true;
                        try { tCtx.imageSmoothingQuality = 'high'; } catch(_) {}
                        tCtx.drawImage(bigCanvas, 0, 0, PW, PH);
                    }
                } catch(_) {}
                processQueue(i + 1);
            }, 0);
        };
        processQueue(0);
    }

    function toggleConfigMenu() {
        log('toggleConfigMenu called, currently:', configMenuVisible);

        configMenuVisible = !configMenuVisible;
        const menu = document.getElementById(CONFIG_MENU_ID);

        if (!menu) {
            log('Menu does not exist, creating new one...');
            const newMenu = createConfigMenu();
            if (configMenuVisible) {
                setTimeout(() => {
                    updateProfileList();
                    newMenu.style.display = 'flex';
                }, 10);
            }
            return;
        }

        if (configMenuVisible) {
            log('Showing menu');
            updateProfileList();
            menu.style.display = 'flex';
        } else {
            log('Hiding menu');
            menu.style.display = 'none';
        }
    }

    // LUT Config Menu (LUT Profile Manager)
    // -------------------------
    let lutConfigMenuVisible = false;

    /**
     * Applies a 4x5 feColorMatrix (row-major, 20 floats) to ImageData in-place.
     * Runs on a downscaled buffer for speed; caller handles up/downscaling.
     */
    function applyLut4x5ToImageData(imageData, matrix4x5) {
        if (!imageData || !Array.isArray(matrix4x5) || matrix4x5.length !== 20) return;
        const d = imageData.data;
        const m = matrix4x5.map(Number);
        const m0=m[0],m1=m[1],m2=m[2],m4=m[4]*255;
        const m5=m[5],m6=m[6],m7=m[7],m9=m[9]*255;
        const m10=m[10],m11=m[11],m12=m[12],m14=m[14]*255;
        for (let i = 0; i < d.length; i += 4) {
            const r=d[i], g=d[i+1], b=d[i+2];
            d[i]   = m0*r+m1*g+m2*b+m4   < 0 ? 0 : m0*r+m1*g+m2*b+m4   > 255 ? 255 : m0*r+m1*g+m2*b+m4;
            d[i+1] = m5*r+m6*g+m7*b+m9   < 0 ? 0 : m5*r+m6*g+m7*b+m9   > 255 ? 255 : m5*r+m6*g+m7*b+m9;
            d[i+2] = m10*r+m11*g+m12*b+m14 < 0 ? 0 : m10*r+m11*g+m12*b+m14 > 255 ? 255 : m10*r+m11*g+m12*b+m14;
        }
    }

    /**
     * Captures one shared raw ImageData at 320x180 from the current video frame
     * (or gradient fallback). Used as the base for all LUT previews this session.
     */
    function captureLutPreviewFrame() {
        const W = 1280, H = 720;
        const c = document.createElement('canvas');
        c.width = W; c.height = H;
        const ctx = c.getContext('2d', { alpha: false, willReadFrequently: true });
        if (!ctx) return null;
        let drew = false;
        const video = getHudPrimaryVideo();
        if (video && video.readyState >= 2 && video.videoWidth > 0) {
            try {
                ctx.drawImage(video, 0, 0, W, H);
                ctx.getImageData(0, 0, 1, 1);
                drew = true;
            } catch(_) {}
        }
        if (!drew) {
            const grad = ctx.createLinearGradient(0, 0, W, H);
            grad.addColorStop(0, '#1a3a5c'); grad.addColorStop(0.25, '#c85032');
            grad.addColorStop(0.5, '#f0c040'); grad.addColorStop(0.75, '#3ab56a');
            grad.addColorStop(1, '#8040c0');
            ctx.fillStyle = grad; ctx.fillRect(0, 0, W, H);
        }
        try { return ctx.getImageData(0, 0, W, H); } catch(_) { return null; }
    }

    /**
     * Applies LUT to ImageData in async chunks to avoid blocking the main thread.
     * Calls onDone() when finished.
     */
    function applyLut4x5Async(imageData, matrix4x5, onDone) {
        const d = imageData.data;
        const m = matrix4x5.map(Number);
        const m0=m[0],m1=m[1],m2=m[2],m4=m[4]*255;
        const m5=m[5],m6=m[6],m7=m[7],m9=m[9]*255;
        const m10=m[10],m11=m[11],m12=m[12],m14=m[14]*255;
        const total = d.length;
        const CHUNK = 1280 * 60 * 4; // 60 rows per chunk
        let offset = 0;
        function processChunk() {
            const end = Math.min(offset + CHUNK, total);
            for (let i = offset; i < end; i += 4) {
                const r=d[i], g=d[i+1], b=d[i+2];
                d[i]   = m0*r+m1*g+m2*b+m4   < 0 ? 0 : m0*r+m1*g+m2*b+m4   > 255 ? 255 : m0*r+m1*g+m2*b+m4;
                d[i+1] = m5*r+m6*g+m7*b+m9   < 0 ? 0 : m5*r+m6*g+m7*b+m9   > 255 ? 255 : m5*r+m6*g+m7*b+m9;
                d[i+2] = m10*r+m11*g+m12*b+m14 < 0 ? 0 : m10*r+m11*g+m12*b+m14 > 255 ? 255 : m10*r+m11*g+m12*b+m14;
            }
            offset = end;
            if (offset < total) {
                setTimeout(processChunk, 0);
            } else {
                onDone(imageData);
            }
        }
        setTimeout(processChunk, 0);
    }

    function createLutConfigMenu() {
        let existingMenu = document.getElementById(LUT_CONFIG_MENU_ID);
        if (existingMenu) existingMenu.remove();

        const menu = document.createElement('div');
        menu.id = LUT_CONFIG_MENU_ID;
        menu.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 820px;
            max-width: 98vw;
            max-height: 88vh;
            background: rgba(20, 20, 20, 0.98);
            backdrop-filter: blur(10px);
            border: 2px solid #ff8a00;
            border-radius: 16px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255,255,255,0.1) inset;
            color: #eaeaea;
            font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
            z-index: 2147483647;
            display: none;
            flex-direction: column;
            padding: 20px;
            user-select: none;
            pointer-events: auto;
        `;
        stopEventsOn(menu);

        const header = document.createElement('div');
        header.style.cssText = `
            display:flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 16px;
            padding-bottom: 10px;
            border-bottom: 2px solid #ff8a00;
        `;

        const title = document.createElement('div');
        title.textContent = '🎨 LUT Profile Manager';
        title.style.cssText = `
            font-size: 20px;
            font-weight: 900;
            color: #fff;
            text-shadow: 0 0 10px rgba(255,138,0,0.55);
        `;

        const closeBtn = document.createElement('button');
        closeBtn.type = 'button';
        closeBtn.textContent = '✕';
        closeBtn.style.cssText = `
            background: rgba(255, 255, 255, 0.1);
            border: none;
            color: #fff;
            font-size: 20px;
            cursor: pointer;
            width: 36px;
            height: 36px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s;
            border: 1px solid rgba(255,255,255,0.2);
        `;
        closeBtn.addEventListener('click', (e) => {
            e.preventDefault(); e.stopPropagation();
            toggleLutConfigMenu();
        });
        stopEventsOn(closeBtn);

        header.appendChild(title);
        header.appendChild(closeBtn);
        menu.appendChild(header);

        makeFloatingManagerDraggable(menu, header, K.LUT_PROFILE_MANAGER_POS);

        const activeInfo = document.createElement('div');
        activeInfo.id = 'gvf-active-lut-profile-info';
        activeInfo.style.cssText = `
            background: rgba(255, 138, 0, 0.18);
            border: 1px solid rgba(255, 138, 0, 0.65);
            border-radius: 8px;
            padding: 10px;
            margin-bottom: 12px;
            font-size: 13px;
            display:flex;
            align-items:center;
            gap:8px;
        `;

        const setActiveLutInfo = () => {
            while (activeInfo.firstChild) activeInfo.removeChild(activeInfo.firstChild);
            activeInfo.append('🟠 Active LUT: ');
            const strong = document.createElement('strong');
            strong.textContent = (activeLutProfileKey && activeLutProfileKey !== 'none') ? lutParseKey(activeLutProfileKey).name : 'None';
            activeInfo.appendChild(strong);
        };
        setActiveLutInfo();
        menu.appendChild(activeInfo);

        // Group controls (for better overview)
        const groupCtlRow = document.createElement('div');
        groupCtlRow.style.cssText = 'display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:12px;';

        const groupLabel = document.createElement('div');
        groupLabel.textContent = 'Group';
        groupLabel.style.cssText = 'font-size:12px;font-weight:900;opacity:0.85;min-width:52px;';

        const groupFilter = document.createElement('select');
        groupFilter.id = 'gvf-lut-group-filter';
        groupFilter.style.cssText = `
            flex: 1;
            min-width: 160px;
            background: rgba(30,30,30,0.9);
            color: #eaeaea;
            border: 1px solid rgba(255,255,255,0.2);
            border-radius: 8px;
            padding: 6px 8px;
            font-size: 12px;
            font-weight: 900;
            cursor: pointer;
        `;
        stopEventsOn(groupFilter);

        const mkSmallBtn = (text, bg, fg) => {
            const b = document.createElement('button');
            b.type = 'button';
            b.textContent = text;
            b.style.cssText = `
                cursor:pointer;
                padding: 8px 10px;border-radius: 10px;
                border: 1px solid rgba(255,255,255,0.14);
                background: ${bg};
                color: ${fg};
                font-weight: 900;
                font-size: 12px;
            `;
            stopEventsOn(b);
            return b;
        };

        const addGroupBtn = mkSmallBtn('Add Group', 'rgba(255, 255, 255, 0.10)', '#ffffff');
        const renameGroupBtn = mkSmallBtn('Rename', 'rgba(255, 255, 255, 0.10)', '#ffffff');
        const deleteGroupBtn = mkSmallBtn('Delete', 'rgba(255, 68, 68, 0.20)', '#ffd0d0');

        const getAllGroupNames = () => {
            const set = new Set();
            // explicit groups (supports empty groups)
            for (const g0 of (Array.isArray(lutGroups) ? lutGroups : [])) {
                const g = String(g0 || '').trim();
                if (g) set.add(g);
            }
            // groups referenced by profiles
            for (const p of (Array.isArray(lutProfiles) ? lutProfiles : [])) {
                const g = (p && p.group) ? String(p.group).trim() : '';
                if (g) set.add(g);
            }
            return Array.from(set).sort((a, b) => a.localeCompare(b));
        };

        const rebuildGroupFilter = () => {
            while (groupFilter.firstChild) groupFilter.removeChild(groupFilter.firstChild);

            const optAll = document.createElement('option');
            optAll.value = '__all__';
            optAll.textContent = 'All groups';
            groupFilter.appendChild(optAll);

            const names = getAllGroupNames();
            for (const g of names) {
                const o = document.createElement('option');
                o.value = g;
                o.textContent = g;
                groupFilter.appendChild(o);
            }

            const optUng = document.createElement('option');
            optUng.value = '__ungrouped__';
            optUng.textContent = 'Ungrouped';
            groupFilter.appendChild(optUng);

            // keep selection if still exists
            const cur = String(groupFilter.dataset.gvfValue || '__all__');
            const canKeep = Array.from(groupFilter.options).some(o => String(o.value) === cur);
            groupFilter.value = canKeep ? cur : '__all__';
            groupFilter.dataset.gvfValue = groupFilter.value;
        };

        // will be extended later (also updates the per-profile group selector)
        let _rebuildLutGroupUis = () => { rebuildGroupFilter(); };

        groupFilter.addEventListener('change', () => {
            groupFilter.dataset.gvfValue = String(groupFilter.value || '__all__');
            updateLutProfileList();
        });

        addGroupBtn.addEventListener('click', () => {
            const n = prompt('New group name:');
            if (!n) return;
            const g = String(n).trim();
            if (!g) return;

            if (!Array.isArray(lutGroups)) lutGroups = [];
            if (!lutGroups.some(x => String(x).trim() === g)) {
                lutGroups.push(g);
                saveLutGroups();
            }

            groupFilter.dataset.gvfValue = g;
            _rebuildLutGroupUis();
            groupFilter.value = g;
            updateLutProfileList();

            log('LUT group created:', g);
        });

        renameGroupBtn.addEventListener('click', () => {
            const cur = String(groupFilter.value || '__all__');
            if (cur === '__all__' || cur === '__ungrouped__') {
                alert('Select a concrete group first.');
                return;
            }
            const n = prompt(`Rename group "${cur}" to:`, cur);
            if (!n) return;
            const next = String(n).trim();
            if (!next || next === cur) return;

            for (const p of (Array.isArray(lutProfiles) ? lutProfiles : [])) {
                if (p && String(p.group || '').trim() === cur) p.group = next;
            }

            // rename in explicit group list
            if (!Array.isArray(lutGroups)) lutGroups = [];
            lutGroups = lutGroups.map(x => (String(x || '').trim() === cur ? next : String(x || '').trim())).filter(Boolean);
            saveLutGroups();

            saveLutProfiles();

            groupFilter.dataset.gvfValue = next;
            _rebuildLutGroupUis();
            groupFilter.value = next;

            try { if (typeof refreshLutDropdownFn === 'function') refreshLutDropdownFn(); } catch (_) { }

            updateLutProfileList();
            log('LUT group renamed:', cur, '->', next);
        });

        deleteGroupBtn.addEventListener('click', () => {
            const cur = String(groupFilter.value || '__all__');
            if (cur === '__all__') { alert('Select a concrete group first.'); return; }
            if (cur === '__ungrouped__') { alert('Ungrouped cannot be deleted.'); return; }

            const ok = confirm(`Delete group "${cur}"? (Profiles will become ungrouped)`);
            if (!ok) return;

            for (const p of (Array.isArray(lutProfiles) ? lutProfiles : [])) {
                if (p && String(p.group || '').trim() === cur) delete p.group;
            }

            // remove from explicit group list
            if (!Array.isArray(lutGroups)) lutGroups = [];
            lutGroups = lutGroups.filter(x => String(x || '').trim() !== cur);
            saveLutGroups();

            saveLutProfiles();

            groupFilter.dataset.gvfValue = '__all__';
            _rebuildLutGroupUis();
            groupFilter.value = '__all__';

            try { if (typeof refreshLutDropdownFn === 'function') refreshLutDropdownFn(); } catch (_) { }

            updateLutProfileList();
            log('LUT group deleted:', cur);
        });

        groupCtlRow.appendChild(groupLabel);
        groupCtlRow.appendChild(groupFilter);
        groupCtlRow.appendChild(addGroupBtn);
        groupCtlRow.appendChild(renameGroupBtn);
        groupCtlRow.appendChild(deleteGroupBtn);

        menu.appendChild(groupCtlRow);
        try { _rebuildLutGroupUis(); } catch (_) { }

        const ctlRow = document.createElement('div');
        ctlRow.style.cssText = `display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;`;

        const mkCtlBtn = (text) => {
            const b = document.createElement('button');
            b.type = 'button';
            b.textContent = text;
            b.style.cssText = `
                cursor:pointer;
                padding: 8px 10px;border-radius: 10px;
                border: 1px solid rgba(255, 138, 0, 0.55);
                background: rgba(255, 138, 0, 0.18);
                color: #ffd7a6;
                font-weight: 900;
                font-size: 12px;
            `;
            stopEventsOn(b);
            return b;
        };

        const exportBtn = mkCtlBtn('Export ZIP');
        const importBtn = mkCtlBtn('Import ZIP');

        const importInput = document.createElement('input');
        importInput.type = 'file';
        importInput.accept = '.zip,.json,application/zip,application/json';
        importInput.style.display = 'none';

        importBtn.addEventListener('click', () => importInput.click());


exportBtn.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();

            try {
                const zipBlob = exportAllLutProfilesAsZip();
                if (!zipBlob) {
                    logW('No LUT profiles to export.');
                    return;
                }

                const zipName = _zipName('gvf_lut_profiles');
                downloadBlob(zipBlob, zipName);

                log('Exported LUT profiles ZIP:', zipName);
            } catch (err) {
                logW('LUT export failed:', err);
                alert('LUT export failed. Check console for details.');
            }
        });
importInput.addEventListener('change', async () => {
            const file = importInput.files && importInput.files[0] ? importInput.files[0] : null;
            importInput.value = '';
            if (!file) return;

            try {
                const res = await importLutProfilesFromZipOrJsonFile(file);
                if (!res || !res.ok) {
                    logW('LUT import failed:', res && res.msg ? res.msg : 'unknown');
                    alert(res && res.msg ? res.msg : 'LUT import failed. Check console for details.');
                    return;
                }

                log(res.msg || 'LUT import ok.');
            } catch (e) {
                logW('LUT import failed:', e);
                alert('LUT import failed. Check console for details.');
            }
        });

        const loadExamplesBtn = mkCtlBtn('⬇ Load Examples LUT');
        loadExamplesBtn.title = 'Download and import the bundled LUT example profiles';
        loadExamplesBtn.addEventListener('click', async (e) => {
            e.preventDefault();
            e.stopPropagation();
            loadExamplesBtn.disabled = true;
            loadExamplesBtn.textContent = '⏳ Loading…';
            try {
                // githubusercontent CDN URL – try direct first, then proxy fallbacks
                const rawUrl = 'https://raw.githubusercontent.com/nextscript/Globale-Video-Filter-Overlay/main/LUTsProfiles_v2.0.zip';
                const candidates = [
                    rawUrl,
                    'https://api.allorigins.win/raw?url=' + encodeURIComponent(rawUrl),
                    'https://corsproxy.io/?' + encodeURIComponent(rawUrl),
                    'https://proxy.cors.sh/' + rawUrl,
                ];
                let response = null;
                for (const url of candidates) {
                    try {
                        const r = await fetch(url);
                        if (r.ok) { response = r; break; }
                    } catch (_) { }
                }
                if (!response) throw new Error('All fetch attempts failed (CORS/network)');
                if (!response.ok) throw new Error(`HTTP ${response.status}`);
                const blob = await response.blob();
                const file = new File([blob], 'LUTsProfiles_v2.0.zip', { type: 'application/zip' });
                const res = await importLutProfilesFromZipOrJsonFile(file);
                if (!res || !res.ok) {
                    alert(res && res.msg ? res.msg : 'LUT import failed. Check console for details.');
                } else {
                    log(res.msg || 'Example LUTs imported.');
                    try { showValueNotification('LUT Import', res.msg, '#4cff6a'); } catch (_) { }
                }
            } catch (err) {
                logW('Load Examples LUT failed:', err);
                alert('Load Examples LUT failed: ' + (err && err.message ? err.message : err));
            } finally {
                loadExamplesBtn.disabled = false;
                loadExamplesBtn.textContent = '⬇ Load Examples LUT';
            }
        });

        ctlRow.appendChild(exportBtn);
        ctlRow.appendChild(importBtn);
        ctlRow.appendChild(loadExamplesBtn);
        ctlRow.appendChild(importInput);
        menu.appendChild(ctlRow);

        const listContainer = document.createElement('div');
        listContainer.style.cssText = `
            flex: 1;
            overflow-y: auto;
            margin-bottom: 14px;
            max-height: 320px;
            background: rgba(0,0,0,0.3);
            border-radius: 8px;
            padding: 6px;
        `;

        const lutList = document.createElement('div');
        lutList.id = 'gvf-lut-profile-list';
        lutList.style.cssText = `display:flex;flex-direction:column;gap:8px;`;

        listContainer.appendChild(lutList);
        menu.appendChild(listContainer);

        const form = document.createElement('div');
        form.style.cssText = `
            border-top: 1px solid rgba(255, 138, 0, 0.35);
            padding-top: 12px;
            display:flex;
            flex-direction: column;
            gap: 10px;
        `;

        const nameRow = document.createElement('div');
        nameRow.style.cssText = `display:flex;gap:8px;align-items:center;flex-wrap:wrap;`;

        const nameInput = document.createElement('input');
        nameInput.type = 'text';
        nameInput.placeholder = 'Profile name...';
        nameInput.style.cssText = `
            flex: 1;
            min-width: 220px;
            background: rgba(0,0,0,0.5);
            border: 1px solid rgba(255,255,255,0.2);
            border-radius: 8px;
            padding: 10px 12px;
            color: #fff;
            font-size: 14px;
            outline: none;
        `;
        stopEventsOn(nameInput);


        const groupSelect = document.createElement('select');
        groupSelect.id = 'gvf-lut-group-select';
        groupSelect.title = 'Assign group';
        groupSelect.style.cssText = `
            width: 160px;
            background: rgba(30,30,30,0.9);
            color: #eaeaea;
            border: 1px solid rgba(255,255,255,0.2);
            border-radius: 8px;
            padding: 6px 8px;
            font-size: 12px;
            font-weight: 900;
            cursor: pointer;
        `;
        stopEventsOn(groupSelect);

        const rebuildGroupSelect = () => {
            while (groupSelect.firstChild) groupSelect.removeChild(groupSelect.firstChild);

            const optUng = document.createElement('option');
            optUng.value = '';
            optUng.textContent = 'Ungrouped';
            groupSelect.appendChild(optUng);

            const names = getAllGroupNames();
            for (const g of names) {
                const o = document.createElement('option');
                o.value = g;
                o.textContent = g;
                groupSelect.appendChild(o);
            }
        };

        rebuildGroupSelect();

        // extend group UI rebuilder to also refresh the selector
        _rebuildLutGroupUis = () => { rebuildGroupFilter(); rebuildGroupSelect(); };

const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = 'image/png,.cube,text/plain,application/octet-stream';
        fileInput.style.cssText = `
            flex: 1;
            min-width: 220px;
            color: #eaeaea;
            font-size: 12px;
        `;
        stopEventsOn(fileInput);

        fileInput.addEventListener('change', async () => {
            try {
                const f = fileInput.files && fileInput.files[0] ? fileInput.files[0] : null;
                if (!f) return;

                const lower = String(f.name || '').toLowerCase();
                if (lower.endsWith('.cube')) {
                    const N = 11;
                    const conv = await cubeFileToMatrix4x5(f, { samplesPerAxis: N, linearizeIn: false, delinearizeOut: false });
                    matrixArea.value = matrixCopyNoBrackets(conv.matrix4x5);
                    if (!nameInput.value.trim()) nameInput.value = f.name.replace(/\.[^.]+$/, '');
                    log('CUBE -> 4x5 matrix generated:', f.name, `size=${conv.size}`);
                } else {
                    // PNG: keep current behavior (fill matrix area for convenience)
                    const conv = await pngFileToMatrix4x5(f, { samplesPerAxis: 11, flipY: false, linearizeIn: false, delinearizeOut: false });
                    matrixArea.value = matrixCopyNoBrackets(conv.matrix4x5);
                    if (!nameInput.value.trim()) nameInput.value = f.name.replace(/\.[^.]+$/, '');
                    log('PNG -> 4x5 matrix generated:', f.name, `(${conv.width}x${conv.height})`, `lutSize=${conv.layout.lutSize}`);
                }
            } catch (e) {
                logW('LUT file convert failed:', e);
            }
        });


        const matrixArea = document.createElement('textarea');
        matrixArea.placeholder = 'Or paste a 4x5 row-major matrix here (20 numbers) or JSON {matrix4x5:[...]}...';
        matrixArea.style.cssText = `
            width: 100%;
            min-height: 110px;
            resize: vertical;
            background: rgba(0,0,0,0.5);
            border: 1px solid rgba(255,255,255,0.2);
            border-radius: 8px;
            padding: 10px 12px;
            color: #fff;
            font-size: 12px;
            outline: none;
            line-height: 1.35;
            font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
        `;
        stopEventsOn(matrixArea);

        const helpRow = document.createElement('div');
        helpRow.style.cssText = 'display:flex;gap:8px;align-items:center;flex-wrap:wrap;';
        const clearBtn = document.createElement('button');
        clearBtn.type = 'button';
        clearBtn.textContent = 'Clear';
        clearBtn.style.cssText = `
            cursor:pointer;
            padding: 6px 10px;border-radius: 10px;
            border: 1px solid rgba(255,255,255,0.14);
            background: rgba(255,255,255,0.10);
            color: #fff;
            font-weight: 900;
            font-size: 12px;
        `;
        stopEventsOn(clearBtn);
        clearBtn.addEventListener('click', () => { matrixArea.value = ''; });

        const loadActiveBtn = document.createElement('button');
        loadActiveBtn.type = 'button';
        loadActiveBtn.textContent = 'Load Active';
        loadActiveBtn.style.cssText = `
            cursor:pointer;
            padding: 6px 10px;border-radius: 10px;
            border: 1px solid rgba(255,255,255,0.14);
            background: rgba(255, 138, 0, 0.18);
            color: #ffd7a6;
            font-weight: 900;
            font-size: 12px;
        `;
        stopEventsOn(loadActiveBtn);
        loadActiveBtn.addEventListener('click', () => {
            const activeName = (activeLutProfileKey && activeLutProfileKey !== 'none') ? lutParseKey(activeLutProfileKey).name : '';
            if (!activeName || activeName === 'none') { alert('No active LUT profile.'); return; }
            const p = (Array.isArray(lutProfiles) ? lutProfiles : []).find(x => String(x.name) === activeName);
            if (!p || !Array.isArray(p.matrix4x5) || p.matrix4x5.length !== 20) { alert('Active LUT profile has no valid matrix.'); return; }
            matrixArea.value = p.matrix4x5.join(' ');
            nameInput.value = activeName;
        });


        helpRow.appendChild(loadActiveBtn);
        helpRow.appendChild(clearBtn);


        const saveBtn = document.createElement('button');
        saveBtn.type = 'button';
        saveBtn.textContent = 'Save / Replace';
        saveBtn.style.cssText = `
            background: #ff8a00;
            border: none;
            color: #111;
            padding: 10px 14px;
            border-radius: 8px;
            font-size: 13px;
            font-weight: 900;
            cursor: pointer;
        `;
        stopEventsOn(saveBtn);

        const hint = document.createElement('div');
        hint.textContent = 'Upload a PNG LUT (e.g. 512×512). Same names will be overwritten.';
        hint.style.cssText = `font-size: 12px; color: rgba(255,255,255,0.75);`;

        saveBtn.addEventListener('click', async () => {
            const name = String(nameInput.value || '').trim();
            const file = fileInput.files && fileInput.files[0] ? fileInput.files[0] : null;
            const raw = String(matrixArea.value || '').trim();

            if (!name) { alert('Please enter a profile name.'); return; }

            const parseMatrix4x5 = (input) => {
                const s = String(input || '').trim();
                if (!s) return null;

                // Try JSON first
                if (s[0] === '{' || s[0] === '[') {
                    try {
                        const obj = JSON.parse(s);
                        if (Array.isArray(obj) && obj.length === 20) return obj.map(Number);
                        if (obj && Array.isArray(obj.matrix4x5) && obj.matrix4x5.length === 20) return obj.matrix4x5.map(Number);
                        if (obj && Array.isArray(obj.matrix4x5) && obj.matrix4x5.length === 4 && obj.matrix4x5.every(r => Array.isArray(r) && r.length === 5)) {
                            return obj.matrix4x5.flat().map(Number);
                        }
                    } catch (_) { /* ignore */ }
                }

                // Parse numbers from text (commas/spaces/newlines)
                const nums = s.replace(/\[/g, ' ')
                              .replace(/\]/g, ' ')
                              .replace(/,/g, ' ')
                              .trim()
                              .split(/\s+/)
                              .filter(Boolean)
                              .map((v) => Number(v));

                if (nums.length !== 20 || nums.some((n) => !Number.isFinite(n))) return null;
                return nums;
            };

            try {
                // Priority: manual matrix textarea
                let matrix = parseMatrix4x5(raw);

                if (!matrix) {
                    // Fallback: PNG conversion
                    if (!file) { alert('Please select a PNG LUT file or paste a 4x5 matrix.'); return; }
                    const lower = String(file.name || '').toLowerCase();
                    if (lower.endsWith('.cube')) {
                        const conv = await cubeFileToMatrix4x5(file, { samplesPerAxis: 11, linearizeIn: false, delinearizeOut: false });
                        matrix = conv.matrix4x5;
                        log('CUBE -> 4x5 matrix generated:', name, `size=${conv.size}`);
                    } else {
                        const conv = await pngFileToMatrix4x5(file, { samplesPerAxis: 11, flipY: false, linearizeIn: false, delinearizeOut: false });
                        matrix = conv.matrix4x5;
                        log('PNG -> 4x5 matrix generated:', name, `(${conv.width}x${conv.height})`, `lutSize=${conv.layout.lutSize}`, `tiles=${conv.layout.tilesX}x${conv.layout.tilesY}`);
                    }
                } else {
                    log('Manual 4x5 matrix saved:', name);
                }

                // Allow duplicate names across different groups, but enforce uniqueness within the same group.
                const newGroup = groupSelect.value ? String(groupSelect.value).trim() : '';
                const newKey = lutMakeKey(name, newGroup);

                const oldKey = String(nameInput.dataset.gvfLutEditKey || '').trim();
                if (oldKey) {
                    const old = lutParseKey(oldKey);
                    // If key changed (rename and/or move group), prevent collision in target group.
                    if (old.key !== newKey) {
                        const exists = (Array.isArray(lutProfiles) ? lutProfiles : []).some(p =>
                            lutKeyFromProfile(p) === newKey
                        );
                        if (exists) { alert('A profile with the same name already exists in this group.'); return; }
                        // Remove old entry first to avoid stale duplicates.
                        deleteLutProfile(old.key);
                    }
                }

                // Upsert into the target group
                upsertLutProfile({ name, group: (newGroup ? newGroup : undefined), matrix4x5: matrix });
                setActiveLutProfile(newKey);

                // Clear edit marker
                try { delete nameInput.dataset.gvfLutEditKey; } catch (_) { }

                nameInput.value = '';
                fileInput.value = '';
                matrixArea.value = '';

                try { _rebuildLutGroupUis(); } catch (_) { }
                try { if (typeof refreshLutDropdownFn === 'function') refreshLutDropdownFn(); } catch (_) { }

                updateLutProfileList();
                setActiveLutInfo();

            } catch (e) {
                logW('LUT save failed:', e);
                alert('LUT save failed. Check console for details.');
            }
        });
        nameRow.appendChild(nameInput);
        nameRow.appendChild(groupSelect);
        nameRow.appendChild(fileInput);
        nameRow.appendChild(saveBtn);

        form.appendChild(nameRow);
        form.appendChild(helpRow);
        form.appendChild(matrixArea);
        form.appendChild(hint);
        menu.appendChild(form);

        function updateLutProfileListInner() {
            const container = menu.querySelector('#gvf-lut-profile-list');
            if (!container) return;

            while (container.firstChild) container.removeChild(container.firstChild);

            try { _rebuildLutGroupUis(); } catch (_) { }

            const list = Array.isArray(lutProfiles) ? lutProfiles.slice() : [];
            list.sort((a, b) => String(a.name).localeCompare(String(b.name)));

            // Group filter
            const gf = String((groupFilter && (groupFilter.dataset.gvfValue || groupFilter.value)) || '__all__');
            let filtered = list;
            if (gf === '__ungrouped__') {
                filtered = list.filter(p => !(p && p.group && String(p.group).trim()));
            } else if (gf !== '__all__') {
                filtered = list.filter(p => (p && p.group && String(p.group).trim() === gf));
            }

            if (filtered.length === 0) {
                const empty = document.createElement('div');
                empty.textContent = 'No LUT profiles yet.';
                empty.style.cssText = 'opacity:0.7;font-size:13px;padding:6px;';
                container.appendChild(empty);
                return;
            }

            // Capture ONE shared raw frame (320x180) for all profiles — fast, done once
            const sharedRaw = captureLutPreviewFrame(); // ImageData 320x180 or null

            const mkBtn = (text, bg, fg) => {
                const b = document.createElement('button');
                b.type = 'button';
                b.textContent = text;
                b.style.cssText = `
                    cursor:pointer;
                    padding: 6px 10px;border-radius: 10px;
                    border: 1px solid rgba(255,255,255,0.14);
                    background: ${bg};
                    color: ${fg};
                    font-weight: 900;
                    font-size: 12px;
                `;
                stopEventsOn(b);
                return b;
            };

            const lutSlideshowEntries = []; // for slideshow across all rendered LUT profiles
            for (const p of filtered) {
                const row = document.createElement('div');
                row.style.cssText = `
                    display:flex;align-items:center;gap:12px;
                    padding: 10px 10px;border-radius: 10px;
                    background: rgba(0,0,0,0.35);
                    border: 1px solid rgba(255,255,255,0.12);
                `;

                const left = document.createElement('div');
                left.style.cssText = 'display:flex;flex-direction:column;gap:2px;flex:1;min-width:120px;overflow:hidden;';

                const nm = document.createElement('div');
                nm.textContent = String(p.name);
                nm.style.cssText = `font-weight:900;font-size:14px;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`;

                const meta = document.createElement('div');
                const isActive = (lutKeyFromProfile(p) === String(activeLutProfileKey));
                meta.textContent = isActive ? 'Active' : '';
                meta.style.cssText = isActive ? 'font-size:12px;color:#ffb35a;font-weight:900;' : 'font-size:12px;opacity:0.7;';

                left.appendChild(nm);
                left.appendChild(meta);

                const grp = (p && p.group && String(p.group).trim()) ? String(p.group).trim() : '';
                if (grp) {
                    const gEl = document.createElement('div');
                    gEl.textContent = 'Group: ' + grp;
                    gEl.style.cssText = 'font-size:12px;opacity:0.75;';
                    left.appendChild(gEl);
                }

                // --- LUT Preview Canvas (thumbnail 160x90, built once in background) ---
                const PW = 160, PH = 90;
                const previewWrap = document.createElement('div');
                previewWrap.style.cssText = `
                    flex-shrink:0;border-radius:8px;overflow:hidden;
                    border:1px solid rgba(255,138,0,0.35);
                    width:${PW}px;height:${PH}px;position:relative;
                    background:#111;cursor:zoom-in;
                `;
                const thumbCanvas = document.createElement('canvas');
                thumbCanvas.width = PW; thumbCanvas.height = PH;
                thumbCanvas.style.cssText = `display:block;width:${PW}px;height:${PH}px;`;
                previewWrap.appendChild(thumbCanvas);

                // Big canvas for lightbox (1280x720) — pre-rendered, never re-rendered
                const LW = 1280, LH = 720;
                const bigCanvas = document.createElement('canvas');
                bigCanvas.width = LW; bigCanvas.height = LH;
                bigCanvas.style.cssText = 'display:block;width:auto;height:auto;max-width:90vw;max-height:65vh;border-radius:10px;';
                let bigReady = false;

                const lutEntry = { bigCanvas, get bigReady() { return bigReady; }, label: '🎨 ' + String(p.name) + (grp ? '  ·  ' + grp : '') };
                lutSlideshowEntries.push(lutEntry);

                // Build both canvases in background — LUT applied async in chunks, no main-thread block
                setTimeout(() => {
                    try {
                    if (!sharedRaw) return; // sharedRaw is already 1280x720

                    const applyAndRender = (id) => {
                        try {
                            // Write LUT-processed frame into bigCanvas (1280x720)
                            const bCtx = bigCanvas.getContext('2d', { alpha: false });
                            if (bCtx) {
                                bCtx.putImageData(id, 0, 0);
                                bigReady = true;
                            }
                            // Scale down to thumbnail 160x90
                            const tCtx = thumbCanvas.getContext('2d', { alpha: false });
                            if (tCtx) {
                                tCtx.imageSmoothingEnabled = true;
                                try { tCtx.imageSmoothingQuality = 'high'; } catch(_) {}
                                tCtx.drawImage(bigCanvas, 0, 0, PW, PH);
                            }
                        } catch(_) {}
                    };

                    // Clone sharedRaw so each profile gets its own copy to mutate
                    const idCopy = new ImageData(
                        new Uint8ClampedArray(sharedRaw.data),
                        sharedRaw.width, sharedRaw.height
                    );

                    if (Array.isArray(p.matrix4x5) && p.matrix4x5.length === 20) {
                        applyLut4x5Async(idCopy, p.matrix4x5, applyAndRender);
                    } else {
                        applyAndRender(idCopy);
                    }

                    } catch(_) {}
                }, 0);

                // Slideshow lightbox
                previewWrap.addEventListener('click', (e) => {
                    e.stopPropagation();
                    openSlideshow(lutSlideshowEntries, lutSlideshowEntries.indexOf(lutEntry), '#ff8a00');
                });
                stopEventsOn(previewWrap);
                // --- end LUT Preview Canvas ---

                const right = document.createElement('div');
                right.style.cssText = 'display:flex;gap:8px;align-items:center;';

                const useBtn = mkBtn('Use', 'rgba(255, 138, 0, 0.18)', '#ffd7a6');
                useBtn.addEventListener('click', () => {
                    setActiveLutProfile(lutKeyFromProfile(p));
                    setActiveLutInfo();
                    updateLutProfileListInner();
                });

                const editBtn = mkBtn('Edit', 'rgba(255, 255, 255, 0.10)', '#ffffff');
                editBtn.addEventListener('click', () => {
                    nameInput.value = String(p.name);
                    try { nameInput.dataset.gvfLutEditKey = lutKeyFromProfile(p); } catch (_) { }
                    fileInput.value = '';
                    try { groupSelect.value = (p && p.group) ? String(p.group).trim() : ''; } catch (_) { }
                    if (Array.isArray(p.matrix4x5) && p.matrix4x5.length === 20) {
                        matrixArea.value = p.matrix4x5.join(' ');
                    } else {
                        matrixArea.value = '';
                    }
                });

                const delBtn = mkBtn('Delete', 'rgba(255, 68, 68, 0.20)', '#ffd0d0');
                delBtn.addEventListener('click', () => {
                    deleteLutProfile(lutKeyFromProfile(p));
                    setActiveLutInfo();
                    updateLutProfileListInner();
                });

                right.appendChild(useBtn);
                right.appendChild(editBtn);
                right.appendChild(delBtn);

                row.appendChild(previewWrap);
                row.appendChild(left);
                row.appendChild(right);
                container.appendChild(row);
            }
        }

        menu._gvfUpdateLutProfileList = updateLutProfileListInner;
        menu._gvfSetActiveLutInfo = setActiveLutInfo;

        try { updateLutProfileListInner(); } catch(_) {}

        (document.body || document.documentElement).appendChild(menu);
        applyManagerPosition(menu, K.LUT_PROFILE_MANAGER_POS);
        return menu;
    }

    function updateLutProfileList() {
        const menu = document.getElementById(LUT_CONFIG_MENU_ID);
        if (menu && typeof menu._gvfUpdateLutProfileList === 'function') menu._gvfUpdateLutProfileList();
    }

    function toggleLutConfigMenu() {
        lutConfigMenuVisible = !lutConfigMenuVisible;
        const menu = document.getElementById(LUT_CONFIG_MENU_ID);

        if (!menu) {
            const newMenu = createLutConfigMenu();
            if (lutConfigMenuVisible) {
                setTimeout(() => {
                    updateLutProfileList();
                    if (typeof newMenu._gvfSetActiveLutInfo === 'function') newMenu._gvfSetActiveLutInfo();
                    newMenu.style.display = 'flex';
                }, 10);
            }
            return;
        }

        if (lutConfigMenuVisible) {
            updateLutProfileList();
            if (typeof menu._gvfSetActiveLutInfo === 'function') menu._gvfSetActiveLutInfo();
            menu.style.display = 'flex';
        } else {
            menu.style.display = 'none';
        }
    }

        // -------------------------


    // -------------------------
    // Overlay infrastructure
    // -------------------------
    const overlaysMain = new WeakMap();
    const overlaysGrade = new WeakMap();
    const overlaysIO = new WeakMap();
    const overlaysScopes = new WeakMap();
    let rafScheduled = false;

    function getFsEl() {
        return document.fullscreenElement
            || document.webkitFullscreenElement
            || document.mozFullScreenElement
            || document.msFullscreenElement
            || null;
    }

    function stopEventsOn(el) {
        const stop = (e) => { e.stopPropagation(); };
        [
            'pointerdown', 'pointerup', 'pointermove',
            'mousedown', 'mouseup', 'mousemove',
            'touchstart', 'touchmove', 'touchend',
            'wheel'
        ].forEach(ev => el.addEventListener(ev, stop, { passive: true }));
    }


    function readManagerPosition(key) {
        try {
            const raw = gmGet(key, null);
            if (raw && typeof raw === 'object') {
                const x = Number(raw.x);
                const y = Number(raw.y);
                if (Number.isFinite(x) && Number.isFinite(y)) return { x, y };
            }
        } catch (_) { }
        try {
            const raw = localStorage.getItem(key);
            if (!raw) return null;
            const parsed = JSON.parse(raw);
            const x = Number(parsed && parsed.x);
            const y = Number(parsed && parsed.y);
            if (Number.isFinite(x) && Number.isFinite(y)) return { x, y };
        } catch (_) { }
        return null;
    }

    function writeManagerPosition(key, pos) {
        if (!pos || !Number.isFinite(Number(pos.x)) || !Number.isFinite(Number(pos.y))) return;
        const safe = { x: Math.round(Number(pos.x)), y: Math.round(Number(pos.y)) };
        try { gmSet(key, safe); } catch (_) { }
        try { localStorage.setItem(key, JSON.stringify(safe)); } catch (_) { }
    }

    function clampManagerPosition(menu, x, y) {
        const rect = (menu && typeof menu.getBoundingClientRect === 'function') ? menu.getBoundingClientRect() : null;
        const width = rect && rect.width ? rect.width : (menu ? menu.offsetWidth : 0);
        const height = rect && rect.height ? rect.height : (menu ? menu.offsetHeight : 0);
        const vw = Math.max(document.documentElement ? document.documentElement.clientWidth : 0, window.innerWidth || 0);
        const vh = Math.max(document.documentElement ? document.documentElement.clientHeight : 0, window.innerHeight || 0);
        const margin = 12;
        const maxX = Math.max(margin, vw - width - margin);
        const maxY = Math.max(margin, vh - height - margin);
        return {
            x: clamp(Number(x) || margin, margin, maxX),
            y: clamp(Number(y) || margin, margin, maxY)
        };
    }

    function applyManagerPosition(menu, storageKey) {
        if (!menu) return;
        const stored = readManagerPosition(storageKey);
        const pos = stored
            ? clampManagerPosition(menu, stored.x, stored.y)
            : clampManagerPosition(menu, (window.innerWidth - menu.offsetWidth) / 2, (window.innerHeight - menu.offsetHeight) / 2);
        menu.style.left = pos.x + 'px';
        menu.style.top = pos.y + 'px';
        menu.style.transform = 'none';
    }

    function makeFloatingManagerDraggable(menu, header, storageKey) {
        if (!menu || !header || !storageKey) return;
        header.style.cursor = 'move';
        header.style.touchAction = 'none';

        let dragState = null;

        const finishDrag = () => {
            if (!dragState) return;
            const pos = clampManagerPosition(menu, parseFloat(menu.style.left), parseFloat(menu.style.top));
            menu.style.left = pos.x + 'px';
            menu.style.top = pos.y + 'px';
            menu.style.transform = 'none';
            writeManagerPosition(storageKey, pos);
            dragState = null;
        };

        const onPointerMove = (e) => {
            if (!dragState) return;
            const clientX = Number(e && e.clientX);
            const clientY = Number(e && e.clientY);
            if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) return;
            const pos = clampManagerPosition(menu, clientX - dragState.offsetX, clientY - dragState.offsetY);
            menu.style.left = pos.x + 'px';
            menu.style.top = pos.y + 'px';
            menu.style.transform = 'none';
        };

        const onPointerUp = () => finishDrag();

        header.addEventListener('pointerdown', (e) => {
            if (!e || e.button !== 0) return;
            const target = e.target;
            if (target && typeof target.closest === 'function' && target.closest('button, input, select, textarea, a, label')) return;
            const rect = menu.getBoundingClientRect();
            dragState = {
                offsetX: e.clientX - rect.left,
                offsetY: e.clientY - rect.top
            };
            menu.style.transform = 'none';
            menu.style.left = rect.left + 'px';
            menu.style.top = rect.top + 'px';
            try { header.setPointerCapture(e.pointerId); } catch (_) { }
            e.preventDefault();
            e.stopPropagation();
        });

        header.addEventListener('pointermove', onPointerMove);
        header.addEventListener('pointerup', onPointerUp);
        header.addEventListener('pointercancel', onPointerUp);
        window.addEventListener('resize', () => {
            const pos = clampManagerPosition(menu, parseFloat(menu.style.left), parseFloat(menu.style.top));
            menu.style.left = pos.x + 'px';
            menu.style.top = pos.y + 'px';
            menu.style.transform = 'none';
            writeManagerPosition(storageKey, pos);
        });
    }

    function mkMainOverlay() {
        const overlay = document.createElement('div');
        overlay.className = 'gvf-video-overlay-main';
        overlay.style.cssText = `
      position: fixed;
      display: none;
      flex-direction: column;
      gap: 6px;
      margin-top: 15px;
      z-index: 2147483647;
      pointer-events: auto;
      opacity: 0.92;
      font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
      transform: translateZ(0);
      user-select: none;
    `;

        const top = document.createElement('div');
        top.style.cssText = `display:flex;align-items:center;justify-content: space-between;gap: 8px;`;

        const row = document.createElement('div');
        row.style.cssText = `display:flex; gap:6px; align-items:center;`;

        const profBadge = document.createElement('div');
        profBadge.className = 'gvf-prof-badge';
        profBadge.style.cssText = `
      padding: 4px 8px;border-radius: 10px;font-size: 11px;font-weight: 900;
      background: rgba(0,0,0,0.92);color: #eaeaea;
      box-shadow: 0 0 0 1px rgba(255,255,255,0.14) inset;white-space: nowrap;
    `;

        const renderBadge = document.createElement('div');
        renderBadge.className = 'gvf-render-badge';
        renderBadge.style.cssText = `
      padding: 2px 6px;border-radius: 8px;font-size: 9px;font-weight: 900;
      background: rgba(0,0,0,0.92);color: #ffaa00;
      box-shadow: 0 0 0 1px rgba(255,255,255,0.14) inset;
      margin-left: 4px;
    `;
        renderBadge.textContent = renderMode === 'gpu' ? 'GPU' : 'SVG';

        const mkBtn = (key, label) => {
            const el = document.createElement('div');
            el.dataset.key = key;
            el.textContent = label;
            el.style.cssText = `
        width: 24px;height: 24px;border-radius: 6px;background: #000;color: #666;
        display:flex;align-items:center;justify-content:center;
        font-size: 11px;font-weight: 800;
        box-shadow: 0 0 0 1px rgba(255,255,255,0.18) inset;
        text-shadow: 0 1px 1px rgba(0,0,0,0.6);
      `;
            return el;
        };

        row.appendChild(mkBtn('base', 'B'));
        row.appendChild(mkBtn('moody', 'D'));
        row.appendChild(mkBtn('teal', 'O'));
        row.appendChild(mkBtn('vib', 'V'));
        row.appendChild(mkBtn('hdr', 'P'));
        row.appendChild(mkBtn('auto', 'A'));
        top.appendChild(row);

        const badgeRow = document.createElement('div');

        badgeRow.style.cssText = `display:flex;align-items:center;gap:4px;`;
        badgeRow.appendChild(profBadge);
        badgeRow.appendChild(renderBadge);
        top.appendChild(badgeRow);

        overlay.appendChild(top);

        const mkSliderRow = (name, labelText, min, max, step, getVal, setVal, gmKey, snapZero, fmt = v => Number(v).toFixed(1)) => {
            const wrap = document.createElement('div');
            wrap.style.cssText = `
        display:flex;align-items:center;gap:8px;padding: 6px 8px;border-radius: 10px;
        background: rgba(0,0,0,0.92);box-shadow: 0 0 0 1px rgba(255,255,255,0.14) inset;
      `;

            const lbl = document.createElement('div');
            lbl.textContent = labelText;
            lbl.style.cssText = `min-width: 36px;text-align:center;font-size: 11px;font-weight: 900;color:#cfcfcf;`;

            const rng = document.createElement('input');
            rng.type = 'range';
            rng.min = String(min);
            rng.max = String(max);
            rng.step = String(step);
            rng.value = String(getVal());
            rng.dataset.gvfRange = name;
            rng.style.cssText = `width: 210px; height: 18px; accent-color: #fff;`;

            const val = document.createElement('div');
            val.dataset.gvfVal = name;
            val.textContent = fmt(getVal());
            val.style.cssText = `width: 52px;text-align:right;font-size: 11px;font-weight: 900;color:#e6e6e6;`;

            stopEventsOn(rng);

            rng.addEventListener('input', () => {
                let v = clamp(parseFloat(rng.value), min, max);
                if (snapZero) v = snap0(v, Math.max(0.005, Number(step) / 2));
                v = roundTo(v, step);

                setVal(v);
                rng.value = String(getVal());
                val.textContent = fmt(getVal());

                gmSet(gmKey, getVal());
                if (gmKey === K.HDR && getVal() !== 0) gmSet(K.HDR_LAST, getVal());

                // Save current settings in active profile
                updateCurrentProfileSettings();

                if (renderMode === 'gpu') {
                    applyGpuFilter();
                } else {
                    regenerateSvgImmediately();
                }
            });

            wrap.appendChild(lbl);
            wrap.appendChild(rng);
            wrap.appendChild(val);
            return wrap;
        };

        overlay.appendChild(mkSliderRow('SL', 'SL', -2, 2, 0.01, () => normSL(), (v) => { sl = v; }, K.SL, true, v => Number(v).toFixed(2)));
        overlay.appendChild(mkSliderRow('SR', 'SR', -2, 2, 0.01, () => normSR(), (v) => { sr = v; }, K.SR, true, v => Number(v).toFixed(2)));
        overlay.appendChild(mkSliderRow('BL', 'BL', -2, 2, 0.01, () => normBL(), (v) => { bl = v; }, K.BL, true, v => Number(v).toFixed(2)));
        overlay.appendChild(mkSliderRow('WL', 'WL', -2, 2, 0.01, () => normWL(), (v) => { wl = v; }, K.WL, true, v => Number(v).toFixed(2)));
        overlay.appendChild(mkSliderRow('DN', 'DN', -1.5, 1.5, 0.01, () => normDN(), (v) => { dn = v; }, K.DN, true, v => Number(v).toFixed(2)));
        overlay.appendChild(mkSliderRow('HDR', 'HDR', -1.0, 2.0, 0.01, () => normHDR(), (v) => { hdr = v; }, K.HDR, true, v => Number(v).toFixed(2)));

        (document.body || document.documentElement).appendChild(overlay);
        return overlay;
    }

    function mkGradingOverlay() {
        const overlay = document.createElement('div');
        overlay.className = 'gvf-video-overlay-grade';
        overlay.style.cssText = `
      position: fixed;display: none;flex-direction: column;gap: 6px;z-index: 2147483647;
      pointer-events: auto;opacity: 0.92;
      font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
      transform: translateZ(0);user-select: none;
      width: 340px;
      max-height: 90vh;
      overflow-y: auto;
    `;

        const head = document.createElement('div');
        head.style.cssText = `
      display:flex;justify-content: space-between;align-items:center;
      padding: 6px 8px;border-radius: 10px;background: rgba(0,0,0,0.92);
      box-shadow: 0 0 0 1px rgba(255,255,255,0.14) inset;
    `;

        const title = document.createElement('div');
        title.textContent = 'Grading (G) & RGB Gain (0-255)';
        title.style.cssText = `font-size:11px; font-weight:900; color:#eaeaea;`;
        head.appendChild(title);
        overlay.appendChild(head);

        const mkRow = (name, labelText, keyGet, keySet, gmKey) => {
            const wrap = document.createElement('div');
            wrap.style.cssText = `
        display:flex;align-items:center;gap:8px;padding: 6px 8px;border-radius: 10px;
        background: rgba(0,0,0,0.92);box-shadow: 0 0 0 1px rgba(255,255,255,0.14) inset;
      `;

            const lbl = document.createElement('div');
            lbl.textContent = labelText;
            lbl.style.cssText = `
        min-width: 100px;text-align:left;font-size: 11px;font-weight: 900;
        color:#cfcfcf;padding-left: 2px;
      `;

            const rng = document.createElement('input');
            rng.type = 'range';
            rng.min = '-10';
            rng.max = '10';
            rng.step = '0.1';
            rng.value = String(keyGet());
            rng.dataset.gvfRange = name;
            rng.style.cssText = `width: 120px; height: 18px; accent-color: #fff;`;

            const val = document.createElement('div');
            val.dataset.gvfVal = name;
            val.textContent = Number(keyGet()).toFixed(1);
            val.style.cssText = `width: 54px;text-align:right;font-size: 11px;font-weight: 900;color:#e6e6e6;`;

            stopEventsOn(rng);

            rng.addEventListener('input', () => {
                const v = normU(parseFloat(rng.value));
                keySet(v);
                rng.value = String(keyGet());
                val.textContent = Number(keyGet()).toFixed(1);
                gmSet(gmKey, keyGet());

                // Save current settings in active profile
                updateCurrentProfileSettings();

                if (renderMode === 'gpu') {
                    applyGpuFilter();
                } else {
                    regenerateSvgImmediately();
                }
                scheduleOverlayUpdate();
            });

            wrap.appendChild(lbl);
            wrap.appendChild(rng);
            wrap.appendChild(val);
            return wrap;
        };

        const mkRGBRow = (name, labelText, keyGet, keySet, gmKey, color) => {
            const wrap = document.createElement('div');
            wrap.style.cssText = `
        display:flex;align-items:center;gap:8px;padding: 6px 8px;border-radius: 10px;
        background: rgba(0,0,0,0.92);box-shadow: 0 0 0 1px rgba(255,255,255,0.14) inset;
      `;

            const lbl = document.createElement('div');
            lbl.textContent = labelText;
            lbl.style.cssText = `
        min-width: 100px;text-align:left;font-size: 11px;font-weight: 900;
        color:${color};padding-left: 2px;
      `;

            const rng = document.createElement('input');
            rng.type = 'range';
            rng.min = '0';
            rng.max = '255';
            rng.step = '1';
            rng.value = String(keyGet());
            rng.dataset.gvfRange = name;
            rng.style.cssText = `width: 120px; height: 18px; accent-color: ${color};`;

            const val = document.createElement('div');
            val.dataset.gvfVal = name;
            val.textContent = String(Math.round(keyGet()));
            val.style.cssText = `width: 54px;text-align:right;font-size: 11px;font-weight: 900;color:${color};`;

            stopEventsOn(rng);

            rng.addEventListener('input', () => {
                const v = normRGB(parseFloat(rng.value));
                keySet(v);
                rng.value = String(keyGet());
                val.textContent = String(Math.round(keyGet()));
                gmSet(gmKey, keyGet());

                // Save current settings in active profile
                updateCurrentProfileSettings();

                if (renderMode === 'gpu') {
                    applyGpuFilter();
                } else {
                    regenerateSvgImmediately();
                }
                scheduleOverlayUpdate();
            });

            wrap.appendChild(lbl);
            wrap.appendChild(rng);
            wrap.appendChild(val);
            return wrap;
        };

        overlay.appendChild(mkRow('U_CONTRAST', 'Contrast', () => normU(u_contrast), (v) => { u_contrast = v; }, K.U_CONTRAST));
        overlay.appendChild(mkRow('U_BLACK', 'Black Level', () => normU(u_black), (v) => { u_black = v; }, K.U_BLACK));
        overlay.appendChild(mkRow('U_WHITE', 'White Level', () => normU(u_white), (v) => { u_white = v; }, K.U_WHITE));
        overlay.appendChild(mkRow('U_HIGHLIGHTS', 'Highlights', () => normU(u_highlights), (v) => { u_highlights = v; }, K.U_HIGHLIGHTS));
        overlay.appendChild(mkRow('U_SHADOWS', 'Shadows', () => normU(u_shadows), (v) => { u_shadows = v; }, K.U_SHADOWS));
        overlay.appendChild(mkRow('U_SAT', 'Saturation', () => normU(u_sat), (v) => { u_sat = v; }, K.U_SAT));
        overlay.appendChild(mkRow('U_VIB', 'Vibrance', () => normU(u_vib), (v) => { u_vib = v; }, K.U_VIB));
        overlay.appendChild(mkRow('U_SHARP', 'Sharpen', () => normU(u_sharp), (v) => { u_sharp = v; }, K.U_SHARP));
        overlay.appendChild(mkRow('U_GAMMA', 'Gamma', () => normU(u_gamma), (v) => { u_gamma = v; }, K.U_GAMMA));
        overlay.appendChild(mkRow('U_GRAIN', 'Grain (Banding)', () => normU(u_grain), (v) => { u_grain = v; }, K.U_GRAIN));
        overlay.appendChild(mkRow('U_HUE', 'Hue Correction', () => normU(u_hue), (v) => { u_hue = v; }, K.U_HUE));

        const sep = document.createElement('div');
        sep.style.cssText = `height:1px;background:rgba(255,255,255,0.14);margin:8px 0;`;
        overlay.appendChild(sep);

        overlay.appendChild(mkRGBRow('U_R_GAIN', 'R Gain (0-255)', () => normRGB(u_r_gain), (v) => { u_r_gain = v; }, K.U_R_GAIN, '#ff6b6b'));
        overlay.appendChild(mkRGBRow('U_G_GAIN', 'G Gain (0-255)', () => normRGB(u_g_gain), (v) => { u_g_gain = v; }, K.U_G_GAIN, '#6bff6b'));
        overlay.appendChild(mkRGBRow('U_B_GAIN', 'B Gain (0-255)', () => normRGB(u_b_gain), (v) => { u_b_gain = v; }, K.U_B_GAIN, '#6b6bff'));

        // Add color blindness filter dropdown
        const cbSep = document.createElement('div');
        cbSep.style.cssText = `height:1px;background:rgba(255,255,255,0.14);margin:8px 0;`;
        overlay.appendChild(cbSep);

        const cbSection = document.createElement('div');
        cbSection.style.cssText = `
      display:flex;align-items:center;gap:8px;padding: 6px 8px;border-radius: 10px;
      background: rgba(0,0,0,0.92);box-shadow: 0 0 0 1px rgba(255,255,255,0.14) inset;
      margin-top: 4px;
    `;

        const cbLabel = document.createElement('div');
        cbLabel.textContent = 'Color Blind';
        cbLabel.style.cssText = `
      min-width: 100px;text-align:left;font-size: 11px;font-weight: 900;
      color:#cfcfcf;padding-left: 2px;
    `;

        const cbSelect = document.createElement('select');
        cbSelect.dataset.gvfSelect = 'cb_filter';
        cbSelect.style.cssText = `
      width: 120px;background: rgba(30,30,30,0.9);color: #eaeaea;
      border: 1px solid rgba(255,255,255,0.2);border-radius: 6px;
      padding: 4px;font-size: 11px;font-weight: 900;cursor: pointer;
    `;

        const options = [
            { value: 'none', text: 'None' },
            { value: 'protanopia', text: 'Protanopia (Red-Green)' },
            { value: 'deuteranopia', text: 'Deuteranopia (Green-Red)' },
            { value: 'tritanomaly', text: 'Tritanomaly (Blue-Yellow)' }
        ];

        options.forEach(opt => {
            const option = document.createElement('option');
            option.value = opt.value;
            option.textContent = opt.text;
            if (opt.value === cbFilter) {
                option.selected = true;
            }
            cbSelect.appendChild(option);
        });

        stopEventsOn(cbSelect);

        cbSelect.addEventListener('change', () => {
            const newVal = cbSelect.value;
            if (newVal === cbFilter) return;

            cbFilter = newVal;
            gmSet(K.CB_FILTER, cbFilter);
            log('Color blindness filter:', cbFilter);

            // Save current settings in active profile
            updateCurrentProfileSettings();

            if (renderMode === 'gpu') {
                applyGpuFilter();
            } else {
                regenerateSvgImmediately();
            }
            scheduleOverlayUpdate();
        });

        const cbHint = document.createElement('div');

        cbSection.appendChild(cbLabel);
        cbSection.appendChild(cbSelect);
        cbSection.appendChild(cbHint);
        overlay.appendChild(cbSection);

        // Add LUT dropdown + manager button (below Color Blind)
            const lutSection = document.createElement('div');
            lutSection.style.cssText = `
          display:flex;align-items:center;gap:8px;padding: 6px 8px;border-radius: 10px;
          background: rgba(0,0,0,0.92);box-shadow: 0 0 0 1px rgba(255,255,255,0.14) inset;
          margin-top: 6px;
        `;

            const lutLabel = document.createElement('div');
            lutLabel.textContent = 'LUT';
            lutLabel.style.cssText = `
          min-width: 100px;text-align:left;font-size: 11px;font-weight: 900;
          color:#cfcfcf;padding-left: 2px;
        `;

            const lutSelect = document.createElement('select');
            lutSelect.dataset.gvfSelect = 'lut_profile';
            lutSelect.style.cssText = `
          width: 180px;background: rgba(30,30,30,0.9);color: #eaeaea;
          border: 1px solid rgba(255,255,255,0.2);border-radius: 6px;
          padding: 4px;font-size: 11px;font-weight: 900;cursor: pointer;
        `;

            const lutPlus = document.createElement('button');
            lutPlus.type = 'button';
            lutPlus.textContent = '+';
            lutPlus.title = 'Open LUT Profile Manager';
            lutPlus.style.cssText = `
          width: 28px;height: 24px;display:flex;align-items:center;justify-content:center;
          border-radius: 6px;cursor:pointer;
          background: rgba(255, 138, 0, 0.22);
          color: #ffd7a6;
          border: 1px solid rgba(255, 138, 0, 0.55);
          font-weight: 900;
        `;

            stopEventsOn(lutSelect);
            stopEventsOn(lutPlus);


            // Expose for immediate sync/apply
            lutSelectEl = lutSelect;
            const refreshLutDropdown = () => {
                while (lutSelect.firstChild) lutSelect.removeChild(lutSelect.firstChild);

                const optNone = document.createElement('option');
                optNone.value = 'none';
                optNone.textContent = 'None';
                lutSelect.appendChild(optNone);

                const list = Array.isArray(lutProfiles) ? lutProfiles.slice() : [];
                const normGroup = (g) => {
                    const s = String(g || '').trim();
                    return s ? s : '';
                };

                // group -> profiles
                const groups = new Map();
                for (const p of list) {
                    const g = normGroup(p && p.group);
                    if (!groups.has(g)) groups.set(g, []);
                    groups.get(g).push(p);
                }

                // sort groups + members
                const groupNames = Array.from(groups.keys()).sort((a, b) => {
                    if (a === '' && b !== '') return 1; // ungrouped last
                    if (b === '' && a !== '') return -1;
                    return a.localeCompare(b);
                });

                for (const g of groupNames) {
                    const arr = groups.get(g) || [];
                    arr.sort((a, b) => String(a.name).localeCompare(String(b.name)));

                    if (g) {
                        const og = document.createElement('optgroup');
                        og.label = g;
                        for (const p of arr) {
                            const o = document.createElement('option');
                            o.value = lutKeyFromProfile(p);
                            o.textContent = String(p.name);
                            og.appendChild(o);
                        }
                        lutSelect.appendChild(og);
                    } else {
                        // Ungrouped
                        for (const p of arr) {
                            const o = document.createElement('option');
                            o.value = lutKeyFromProfile(p);
                            o.textContent = String(p.name);
                            lutSelect.appendChild(o);
                        }
                    }
                }

                lutSelect.value = String(activeLutProfileKey || 'none');
            };


            refreshLutDropdownFn = refreshLutDropdown;
            refreshLutDropdown();

            lutSelect.addEventListener('change', () => {
                const v = String(lutSelect.value || 'none');
                if (v === String(activeLutProfileKey || 'none')) return;
                setActiveLutProfile(v);
            });

            lutPlus.addEventListener('click', () => {
                toggleLutConfigMenu();
                refreshLutDropdown();
            });

            lutSection.appendChild(lutLabel);
            lutSection.appendChild(lutSelect);
            lutSection.appendChild(lutPlus);
            overlay.appendChild(lutSection);

        // ---- Custom SVG Codes ----
        const svgCodesSep = document.createElement('div');
        svgCodesSep.style.cssText = `height:1px;background:rgba(255,255,255,0.14);margin:8px 0;`;
        overlay.appendChild(svgCodesSep);

        const svgCodesRow = document.createElement('div');
        svgCodesRow.style.cssText = `display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:10px;background:rgba(0,0,0,0.92);box-shadow:0 0 0 1px rgba(255,255,255,0.14) inset;margin-top:4px;`;

        const svgCodesLabel = document.createElement('div');
        svgCodesLabel.textContent = 'SVG Codes';
        svgCodesLabel.style.cssText = `min-width:100px;text-align:left;font-size:11px;font-weight:900;color:#cfcfcf;padding-left:2px;`;

        const svgCodesBtn = document.createElement('button');
        svgCodesBtn.textContent = '⬡ Manage';
        svgCodesBtn.style.cssText = `padding:4px 12px;background:rgba(100,180,255,0.18);color:#a0d4ff;border:1px solid rgba(100,180,255,0.45);border-radius:6px;font-size:11px;font-weight:900;cursor:pointer;transition:background 0.15s;`;
        svgCodesBtn.addEventListener('mouseenter', () => { svgCodesBtn.style.background = 'rgba(100,180,255,0.32)'; });
        svgCodesBtn.addEventListener('mouseleave', () => { svgCodesBtn.style.background = 'rgba(100,180,255,0.18)'; });
        stopEventsOn(svgCodesBtn);
        svgCodesBtn.addEventListener('click', () => openCustomSvgModal());

        const svgCodesCount = document.createElement('div');
        svgCodesCount.id = 'gvf-svg-codes-count';
        svgCodesCount.style.cssText = `font-size:10px;font-weight:900;color:#6ca8ff;opacity:0.85;`;
        const activeCount = customSvgCodes.filter(e => e.enabled).length;
        svgCodesCount.textContent = customSvgCodes.length ? `${activeCount}/${customSvgCodes.length} active` : '';

        svgCodesRow.appendChild(svgCodesLabel);
        svgCodesRow.appendChild(svgCodesBtn);
        svgCodesRow.appendChild(svgCodesCount);
        overlay.appendChild(svgCodesRow);



        (document.body || document.documentElement).appendChild(overlay);
        return overlay;
    }

    function mkIOOverlay() {
        const overlay = document.createElement('div');
        overlay.className = 'gvf-video-overlay-io';
        overlay.style.cssText = `
      position: fixed;display: none;flex-direction: column;gap: 6px;z-index: 2147483647;
      pointer-events: auto;opacity: 0.95;
      font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
      transform: translateZ(0);user-select: none;
      width: 420px;
    `;

        const head = document.createElement('div');
        head.style.cssText = `
      display:flex;justify-content: space-between;align-items:center;
      padding: 6px 8px;border-radius: 10px;background: rgba(0,0,0,0.92);
      box-shadow: 0 0 0 1px rgba(255,255,255,0.14) inset;
    `;

        const title = document.createElement('div');
        title.textContent = 'Settings (I) Export/Import';
        title.style.cssText = `font-size:11px; font-weight:900; color:#eaeaea;`;

        const hint = document.createElement('div');
        hint.textContent = 'JSON';
        hint.style.cssText = `font-size:10px;font-weight:900;color:#cfcfcf;opacity:0.9;`;

        head.appendChild(title);
        head.appendChild(hint);
        overlay.appendChild(head);

        const box = document.createElement('div');
        box.style.cssText = `
      padding: 8px;border-radius: 10px;background: rgba(0,0,0,0.92);
      box-shadow: 0 0 0 1px rgba(255,255,255,0.14) inset;
    `;

        const ta = document.createElement('textarea');
        ta.className = 'gvf-io-text';
        ta.spellcheck = false;
        ta.wrap = 'off';
        ta.style.cssText = `
      width: 100%;height: 220px;resize: vertical;
      background: rgba(10,10,10,0.98);color:#eaeaea;
      border: 1px solid rgba(255,255,255,0.14);
      border-radius: 10px;padding: 8px;
      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
      font-size: 11px; line-height: 1.25;
      outline: none;
    `;
        stopEventsOn(ta);

        const setDirty = (on) => { if (on) ta.dataset.dirty = '1'; else delete ta.dataset.dirty; };
        let ioJsonAutoSaveTimer = null;
        let lastIoJsonApplied = '';

        ta.addEventListener('input', () => {
            setDirty(true);
            status.textContent = 'JSON changed. Waiting for valid JSON...';

            if (ioJsonAutoSaveTimer) {
                clearTimeout(ioJsonAutoSaveTimer);
                ioJsonAutoSaveTimer = null;
            }

            ioJsonAutoSaveTimer = setTimeout(() => {
                const raw = String(ta.value || '').trim();
                if (!raw) return;

                let obj = null;
                try {
                    obj = JSON.parse(raw);
                } catch (_) {
                    status.textContent = 'JSON invalid. Auto-save skipped.';
                    return;
                }

                if (raw === lastIoJsonApplied) return;

                const ok = importSettings(obj);
                if (!ok) {
                    status.textContent = 'Invalid JSON structure.';
                    return;
                }

                const changed = updateCurrentProfileSettings();
                try { updateProfileList(); } catch (_) { }

                lastIoJsonApplied = JSON.stringify(exportSettings(), null, 2);
                ta.value = lastIoJsonApplied;
                setDirty(false);

                status.textContent = changed ? 'Auto-saved + applied to active profile.' : 'No settings change detected.';
                if (changed) {
                    showScreenNotification('', {
                        title: `Profile "${String(activeUserProfile?.name || 'Default')}" auto-saved`,
                        detail: 'IO HUD JSON applied to active profile',
                        detailColor: '#4cff6a'
                    });
                }
            }, 450);
        });

        const row = document.createElement('div');
        row.style.cssText = `display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;`;

        const mkBtn = (text) => {
            const b = document.createElement('button');
            b.type = 'button';
            b.textContent = text;
            b.style.cssText = `
        cursor:pointer;
        padding: 6px 10px;border-radius: 10px;
        border: 1px solid rgba(255,255,255,0.14);
        background: rgba(255,255,255,0.08);
        color:#eaeaea;font-size: 11px;font-weight: 900;
        transition: all 0.2s ease;
      `;

            // hover effect
            b.addEventListener('mouseenter', () => {
                b.style.background = 'rgba(255,255,255,0.15)';
            });
            b.addEventListener('mouseleave', () => {
                b.style.background = 'rgba(255,255,255,0.08)';
            });

            stopEventsOn(b);
            return b;
        };

        const status = document.createElement('div');
        status.className = 'gvf-io-status';
        status.style.cssText = `margin-top:8px;font-size:11px;font-weight:900;color:#cfcfcf;opacity:0.95;`;
        status.textContent = 'Tip: paste JSON here → Save';

        const btnRefresh = mkBtn('Refresh');
        const btnSave = mkBtn('Save');
        const btnSelect = mkBtn('Select All');
        const btnReset = mkBtn('Reset to defaults');
        const btnExportFile = mkBtn('Export .json');
        const btnImportFile = mkBtn('Import .json');
        const btnShot = mkBtn('Screenshot');
        const btnRec = mkBtn('Record');

        // CONFIG BUTTON - Improved version
        const btnConfig = mkBtn('⚙️ Config');
        btnConfig.style.background = 'rgba(42, 111, 219, 0.4)';
        btnConfig.style.border = '2px solid #2a6fdb';
        btnConfig.style.color = '#fff';
        btnConfig.style.fontWeight = 'bold';
        btnConfig.style.padding = '6px 12px';

        // Important: Direct event listener with console.log for testing
        btnConfig.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            log('Config button clicked!');
            toggleConfigMenu();
        });

        // Debug Button
        const btnDebug = mkBtn(debug ? '🐞 Debug: ON' : '🐞 Debug: OFF');
        btnDebug.style.background = debug ? 'rgba(0,255,0,0.2)' : 'rgba(255,0,0,0.2)';
        btnDebug.style.border = debug ? '1px solid #00ff00' : '1px solid #ff0000';
        btnDebug.style.color = debug ? '#00ff00' : '#ff6666';

        btnDebug.addEventListener('click', () => {
            toggleDebug();
            btnDebug.textContent = debug ? '🐞 Debug: ON' : '🐞 Debug: OFF';
            btnDebug.style.background = debug ? 'rgba(0,255,0,0.2)' : 'rgba(255,0,0,0.2)';
            btnDebug.style.border = debug ? '1px solid #00ff00' : '1px solid #ff0000';
            btnDebug.style.color = debug ? '#00ff00' : '#ff6666';
            status.textContent = debug ? 'Debug mode activated' : 'Debug mode deactivated';
        });

        overlay.__btnRec = btnRec;
        overlay.__status = status;

        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = 'application/json,.json';
        fileInput.style.display = 'none';
        stopEventsOn(fileInput);

        function downloadJsonToPC(obj) {
            const jsonStr = JSON.stringify(obj, null, 2);
            const blob = new Blob([jsonStr], { type: 'application/json;charset=utf-8' });
            const url = URL.createObjectURL(blob);

            const a = document.createElement('a');
            a.href = url;

            const d = new Date();
            const pad = (n) => String(n).padStart(2, '0');
            const name = `gvf-settings_${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}_${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}.json`;
            a.download = name;

            document.body.appendChild(a);
            a.click();
            a.remove();

            setTimeout(() => URL.revokeObjectURL(url), 1000);
        }

        btnExportFile.addEventListener('click', () => {
            try { downloadJsonToPC(exportSettings()); status.textContent = 'Exported to .json file.'; }
            catch (_) { status.textContent = 'Export failed.'; }
        });

        btnImportFile.addEventListener('click', () => {
            try { fileInput.value = ''; } catch (_) { }
            fileInput.click();
        });

        fileInput.addEventListener('change', () => {
            const f = fileInput.files && fileInput.files[0];
            if (!f) return;

            const reader = new FileReader();
            reader.onload = () => {
                try {
                    const raw = String(reader.result || '').trim();
                    const obj = JSON.parse(raw);
                    const ok = importSettings(obj);
                    if (!ok) { status.textContent = 'Invalid JSON structure.'; return; }

                    setDirty(false);
                    ta.value = JSON.stringify(exportSettings(), null, 2);
                    lastIoJsonApplied = ta.value;
                    status.textContent = 'Imported + applied.';
                } catch (_) {
                    status.textContent = 'Import failed (invalid JSON).';
                } finally {
                    try { fileInput.value = ''; } catch (_) { }
                }
            };
            reader.onerror = () => {
                status.textContent = 'Import failed (read error).';
                try { fileInput.value = ''; } catch (_) { }
            };
            reader.readAsText(f);
        });

        btnRefresh.addEventListener('click', () => {
            setDirty(false);
            ta.value = JSON.stringify(exportSettings(), null, 2);
            lastIoJsonApplied = ta.value;
            status.textContent = 'Exported current settings.';
        });

        btnSelect.addEventListener('click', () => { ta.focus(); ta.select(); status.textContent = 'Selected.'; });

        btnSave.addEventListener('click', () => {
            const raw = String(ta.value || '').trim();
            if (!raw) { status.textContent = 'Empty JSON.'; return; }
            try {
                const obj = JSON.parse(raw);
                const ok = importSettings(obj);
                if (!ok) { status.textContent = 'Invalid JSON structure.'; return; }

                const changed = updateCurrentProfileSettings(true);
                try { updateProfileList(); } catch (_) { }

                setDirty(false);
                ta.value = JSON.stringify(exportSettings(), null, 2);
                lastIoJsonApplied = ta.value;
                status.textContent = changed ? 'Saved + applied.' : 'No settings change detected.';
                showScreenNotification('', {
                    title: `Profile "${String(activeUserProfile?.name || 'Default')}" saved`,
                    detail: 'IO HUD JSON saved + applied',
                    detailColor: '#4cff6a'
                });
            } catch (_) {
                status.textContent = 'JSON parse error.';
            }
        });

        // Reset to defaults
        btnReset.addEventListener('click', () => {

            const firefoxDetected = isFirefox();

            let defaults;

            if (firefoxDetected) {

                defaults = {
                    enabled: true, notify: true, darkMoody: true, tealOrange: false, vibrantSat: false,
                    sl: 1.3, sr: -1.1, bl: 0.3, wl: 0.2, dn: 0.0,
                    edge: 0.0,
                    hdr: 0.0, profile: 'off',
                    renderMode: 'svg',
            lutProfile: 'none',
                    autoOn: true,
                    autoStrength: 0.65,
                    autoLockWB: true,
                    user: {
                        contrast: 0, black: 0, white: 0, highlights: 0, shadows: 0, saturation: 0, vibrance: 0, sharpen: 0, gamma: 0, grain: 0, hue: 0,
                        r_gain: 128, g_gain: 128, b_gain: 128
                    },
                    debug: false,
                    logs: true,
                    cbFilter: 'none'
                };
            } else {

                defaults = {
                    enabled: true, notify: true, darkMoody: true, tealOrange: false, vibrantSat: false,
                    sl: 1.0, sr: 0.5, bl: -1.2, wl: 0.2, dn: 0.0,
                    edge: 0.1,
                    hdr: 0.0, profile: 'user',
                    renderMode: 'svg',
            lutProfile: 'none',
                    autoOn: true,
                    autoStrength: 0.65,
                    autoLockWB: true,
                    user: {
                        contrast: 0, black: 0, white: 0, highlights: 0, shadows: 0, saturation: 0, vibrance: 0, sharpen: 0, gamma: 0, grain: 0, hue: 0,
                        r_gain: 128, g_gain: 128, b_gain: 128
                    },
                    debug: false,
                    logs: true,
                    cbFilter: 'none'
                };
            }

            importSettings(defaults);
            try { updateProfileList(); } catch (_) { }
            setDirty(true);
            ta.value = JSON.stringify(exportSettings(), null, 2);
            lastIoJsonApplied = '';
            status.textContent = 'Reset applied. Waiting for auto-save or Save.';
            showScreenNotification('', {
                title: `Profile "${String(activeUserProfile?.name || 'Default')}" reset`,
                detail: 'Defaults restored',
                detailColor: '#ffcc66'
            });

            // Update Debug button
            btnDebug.textContent = '🐞 Debug: OFF';
            btnDebug.style.background = 'rgba(255,0,0,0.2)';
            btnDebug.style.border = '1px solid #ff0000';
            btnDebug.style.color = '#ff6666';
        });

        btnShot.addEventListener('click', async () => { await takeVideoScreenshot(status); });

        btnRec.addEventListener('click', async () => {
            if (btnRec.disabled) return;
            await toggleVideoRecord(status, btnRec);
        });

        // Load Example button
        const btnLoadExample = mkBtn('⬇ Load Example');
        btnLoadExample.style.background = 'rgba(76, 255, 106, 0.12)';
        btnLoadExample.style.border = '1px solid rgba(76, 255, 106, 0.4)';
        btnLoadExample.style.color = '#fff';
        btnLoadExample.addEventListener('mouseenter', () => {
            btnLoadExample.style.background = 'rgba(76, 255, 106, 0.22)';
            btnLoadExample.style.borderColor = '#4cff6a';
        });
        btnLoadExample.addEventListener('mouseleave', () => {
            btnLoadExample.style.background = 'rgba(76, 255, 106, 0.12)';
            btnLoadExample.style.borderColor = 'rgba(76, 255, 106, 0.4)';
        });
        btnLoadExample.addEventListener('click', async () => {
            btnLoadExample.disabled = true;
            btnLoadExample.textContent = '⏳ Loading…';
            status.textContent = 'Fetching example profile…';
            try {
                const rawUrl = isFirefox()
                    ? 'https://raw.githubusercontent.com/nextscript/Globale-Video-Filter-Overlay/refs/heads/main/firefox_fix.json'
                    : 'https://raw.githubusercontent.com/nextscript/Globale-Video-Filter-Overlay/main/My_Profile.json';
                const text = await new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: rawUrl,
                        anonymous: true,
                        redirect: 'follow',
                        onload: (r) => r.status >= 200 && r.status < 300 ? resolve(r.responseText) : reject(new Error('HTTP ' + r.status)),
                        onerror: () => reject(new Error('Network error')),
                        ontimeout: () => reject(new Error('Timeout')),
                        timeout: 15000
                    });
                });
                const obj = JSON.parse(text.trim());
                const ok = importSettings(obj);
                if (!ok) { status.textContent = 'Load Example: invalid JSON structure.'; return; }
                updateCurrentProfileSettings(true);
                try { updateProfileList(); } catch (_) { }
                setDirty(false);
                ta.value = JSON.stringify(exportSettings(), null, 2);
                lastIoJsonApplied = ta.value;
                status.textContent = 'Example profile loaded + applied.';
                try { showValueNotification('Profile Import', 'Example profile loaded.', '#4cff6a'); } catch (_) { }
                log('Example profile loaded from GitHub.');
            } catch (err) {
                logW('Load Example failed:', err);
                status.textContent = 'Load Example failed: ' + (err && err.message ? err.message : err);
            } finally {
                btnLoadExample.disabled = false;
                btnLoadExample.textContent = '⬇ Load Example';
            }
        });

        // Add buttons in the correct order
        row.appendChild(btnRefresh);
        row.appendChild(btnSave);
        row.appendChild(btnSelect);
        row.appendChild(btnExportFile);
        row.appendChild(btnImportFile);
        row.appendChild(btnReset);
        row.appendChild(btnLoadExample);
        row.appendChild(btnShot);
        row.appendChild(btnRec);
        row.appendChild(btnConfig);  // Config Button
        row.appendChild(btnDebug);

        box.appendChild(ta);
        box.appendChild(row);
        box.appendChild(status);
        box.appendChild(fileInput);

        overlay.appendChild(box);

        // Append directly to the body
        if (document.body) {
            document.body.appendChild(overlay);
            log('IO overlay created and added to the body');
        } else {
            logW('Body not yet available');
        }

        return overlay;
    }

    function mkScopesOverlay() {
        const overlay = document.createElement('div');
        overlay.className = 'gvf-video-overlay-scopes';
        overlay.style.cssText = `
      position: fixed;
      display: none;
      flex-direction: column;
      gap: 6px;
      z-index: 2147483647;
      pointer-events: none;
      opacity: 0.95;
      font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
      transform: translateZ(0);
      user-select: none;
      width: 280px;
    `;

        const head = document.createElement('div');
        head.style.cssText = `
      display:flex;justify-content: space-between;align-items:center;
      padding: 4px 8px;border-radius: 8px;background: rgba(0,0,0,0.85);
      box-shadow: 0 0 0 1px rgba(255,255,255,0.2) inset;
      backdrop-filter: blur(2px);
    `;

        const title = document.createElement('div');
        title.textContent = 'Scopes (S)';
        title.style.cssText = `font-size:10px; font-weight:900; color:#eaeaea;`;

        const hint = document.createElement('div');
        hint.textContent = 'live';
        hint.style.cssText = `font-size:9px;font-weight:900;color:#aaa;`;

        head.appendChild(title);
        head.appendChild(hint);
        overlay.appendChild(head);

        const content = document.createElement('div');
        content.style.cssText = `
      padding: 8px;border-radius: 8px;background: rgba(0,0,0,0.85);
      box-shadow: 0 0 0 1px rgba(255,255,255,0.2) inset;
      backdrop-filter: blur(2px);
      display: flex;
      flex-direction: column;
      gap: 10px;
    `;

        const lumaSection = document.createElement('div');
        lumaSection.style.cssText = `display:flex;flex-direction:column;gap:2px;`;

        const lumaTitle = document.createElement('div');
        lumaTitle.style.cssText = `font-size:9px;font-weight:900;color:#cfcfcf;text-transform:uppercase;letter-spacing:0.5px;`;
        lumaTitle.textContent = 'Luma Y';
        lumaSection.appendChild(lumaTitle);

        const lumaBars = document.createElement('div');
        lumaBars.style.cssText = `
      display:flex;align-items:flex-end;height:40px;gap:1px;
      background:rgba(20,20,20,0.6);border-radius:4px;padding:2px;
    `;
        lumaBars.className = 'gvf-scope-luma';
        for (let i = 0; i < 16; i++) {
            const bar = document.createElement('div');
            bar.style.cssText = `
        flex:1;height:2px;background:#4CAF50;border-radius:1px;
        transition:height 0.1s ease;
      `;
            bar.dataset.index = i;
            lumaBars.appendChild(bar);
        }
        lumaSection.appendChild(lumaBars);

        const rgbSection = document.createElement('div');
        rgbSection.style.cssText = `display:flex;flex-direction:column;gap:2px;`;

        const rgbTitle = document.createElement('div');
        rgbTitle.style.cssText = `font-size:9px;font-weight:900;color:#cfcfcf;text-transform:uppercase;letter-spacing:0.5px;`;
        rgbTitle.textContent = 'RGB';
        rgbSection.appendChild(rgbTitle);

        const rgbGrid = document.createElement('div');
        rgbGrid.style.cssText = `
      display:grid;grid-template-columns:1fr 1fr 1fr;gap:2px;
      background:rgba(20,20,20,0.6);border-radius:4px;padding:4px;
    `;

        const redCol = document.createElement('div');
        redCol.style.cssText = `display:flex;flex-direction:column;gap:1px;`;
        const redLabel = document.createElement('div');
        redLabel.style.cssText = `font-size:8px;font-weight:900;color:#ff6b6b;text-align:center;`;
        redLabel.textContent = 'R';
        redCol.appendChild(redLabel);
        const redBars = document.createElement('div');
        redBars.style.cssText = `display:flex;align-items:flex-end;height:32px;gap:1px;`;
        redBars.className = 'gvf-scope-red';
        for (let i = 0; i < 16; i++) {
            const bar = document.createElement('div');
            bar.style.cssText = `flex:1;height:2px;background:#ff5252;border-radius:1px;transition:height 0.1s ease;`;
            bar.dataset.index = i;
            redBars.appendChild(bar);
        }
        redCol.appendChild(redBars);
        rgbGrid.appendChild(redCol);

        const greenCol = document.createElement('div');
        greenCol.style.cssText = `display:flex;flex-direction:column;gap:1px;`;
        const greenLabel = document.createElement('div');
        greenLabel.style.cssText = `font-size:8px;font-weight:900;color:#6bff6b;text-align:center;`;
        greenLabel.textContent = 'G';
        greenCol.appendChild(greenLabel);
        const greenBars = document.createElement('div');
        greenBars.style.cssText = `display:flex;align-items:flex-end;height:32px;gap:1px;`;
        greenBars.className = 'gvf-scope-green';
        for (let i = 0; i < 16; i++) {
            const bar = document.createElement('div');
            bar.style.cssText = `flex:1;height:2px;background:#52ff52;border-radius:1px;transition:height 0.1s ease;`;
            bar.dataset.index = i;
            greenBars.appendChild(bar);
        }
        greenCol.appendChild(greenBars);
        rgbGrid.appendChild(greenCol);

        const blueCol = document.createElement('div');
        blueCol.style.cssText = `display:flex;flex-direction:column;gap:1px;`;
        const blueLabel = document.createElement('div');
        blueLabel.style.cssText = `font-size:8px;font-weight:900;color:#6b6bff;text-align:center;`;
        blueLabel.textContent = 'B';
        blueCol.appendChild(blueLabel);
        const blueBars = document.createElement('div');
        blueBars.style.cssText = `display:flex;align-items:flex-end;height:32px;gap:1px;`;
        blueBars.className = 'gvf-scope-blue';
        for (let i = 0; i < 16; i++) {
            const bar = document.createElement('div');
            bar.style.cssText = `flex:1;height:2px;background:#5252ff;border-radius:1px;transition:height 0.1s ease;`;
            bar.dataset.index = i;
            blueBars.appendChild(bar);
        }
        blueCol.appendChild(blueBars);
        rgbGrid.appendChild(blueCol);

        rgbSection.appendChild(rgbGrid);

        const satSection = document.createElement('div');
        satSection.style.cssText = `display:flex;flex-direction:column;gap:2px;`;

        const satTitle = document.createElement('div');
        satTitle.style.cssText = `font-size:9px;font-weight:900;color:#cfcfcf;text-transform:uppercase;letter-spacing:0.5px;`;
        satTitle.textContent = 'Sat';
        satSection.appendChild(satTitle);

        const satMeter = document.createElement('div');
        satMeter.style.cssText = `
      display:flex;align-items:center;gap:6px;
      background:rgba(20,20,20,0.6);border-radius:4px;padding:4px;
    `;

        const satBarBg = document.createElement('div');
        satBarBg.style.cssText = `flex:1;height:8px;background:#333;border-radius:4px;overflow:hidden;`;

        const satBarFill = document.createElement('div');
        satBarFill.style.cssText = `height:100%;width:0%;background:linear-gradient(90deg,#ffd700,#ff8c00);border-radius:4px;transition:width 0.1s ease;`;
        satBarFill.className = 'gvf-scope-sat-fill';

        const satValue = document.createElement('div');
        satValue.style.cssText = `font-size:9px;font-weight:900;color:#eaeaea;min-width:36px;text-align:right;`;
        satValue.className = 'gvf-scope-sat-value';
        satValue.textContent = '0.00';

        satBarBg.appendChild(satBarFill);
        satMeter.appendChild(satBarBg);
        satMeter.appendChild(satValue);
        satSection.appendChild(satMeter);

        const avgSection = document.createElement('div');
        avgSection.style.cssText = `
      display:grid;grid-template-columns:1fr 1fr 1fr;gap:2px;margin-top:2px;
      font-size:8px;font-weight:900;color:#aaa;
    `;

        const avgY = document.createElement('div');
        avgY.className = 'gvf-scope-avg-y';
        avgY.style.cssText = `text-align:center;background:rgba(30,30,30,0.6);border-radius:4px;padding:2px;`;
        avgY.textContent = 'Y: 0.00';

        const avgRGB = document.createElement('div');
        avgRGB.className = 'gvf-scope-avg-rgb';
        avgRGB.style.cssText = `text-align:center;background:rgba(30,30,30,0.6);border-radius:4px;padding:2px;`;
        avgRGB.textContent = 'RGB: 0.00';

        const avgSat = document.createElement('div');
        avgSat.className = 'gvf-scope-avg-sat';
        avgSat.style.cssText = `text-align:center;background:rgba(30,30,30,0.6);border-radius:4px;padding:2px;`;
        avgSat.textContent = 'Sat: 0.00';

        avgSection.appendChild(avgY);
        avgSection.appendChild(avgRGB);
        avgSection.appendChild(avgSat);

        content.appendChild(lumaSection);
        content.appendChild(rgbSection);
        content.appendChild(satSection);
        content.appendChild(avgSection);

        overlay.appendChild(content);
        (document.body || document.documentElement).appendChild(overlay);
        return overlay;
    }

    const SCOPES = {
        running: false,
        canvas: document.createElement('canvas'),
        ctx: null,
        lastUpdate: 0,
        updateInterval: 100,
        lastVideo: null
    };

    SCOPES.canvas.width = 160;
    SCOPES.canvas.height = 90;
    try {
        SCOPES.ctx = SCOPES.canvas.getContext('2d', { willReadFrequently: true, alpha: false });
    } catch (_) {
        try {
            SCOPES.ctx = SCOPES.canvas.getContext('2d', { alpha: false });
        } catch (__) {
            SCOPES.ctx = SCOPES.canvas.getContext('2d');
        }
    }

    function updateScopesData() {
        if (!scopesHudShown) return;

        const v = choosePrimaryVideo();
        if (!v || !SCOPES.ctx) return;

        if (v.paused || v.seeking || v.ended || v.readyState < 2) {
            return;
        }

        const now = nowMs();
        if (now - SCOPES.lastUpdate < SCOPES.updateInterval) return;

        try {
            const w = Math.max(2, v.videoWidth || 0);
            const h = Math.max(2, v.videoHeight || 0);
            if (!w || !h) return;

            const cssFilter = getAppliedCssFilterString(v);

            SCOPES.ctx.save();
            if (cssFilter) {
                SCOPES.ctx.filter = cssFilter;
            }
            SCOPES.ctx.drawImage(v, 0, 0, 160, 90);
            SCOPES.ctx.restore();

            let imgData;
            try {
                imgData = SCOPES.ctx.getImageData(0, 0, 160, 90);
            } catch (e) {
                SCOPES.lastUpdate = now;
                return;
            }

            const d = imgData.data;

            const lumaHist = new Array(16).fill(0);
            const redHist = new Array(16).fill(0);
            const greenHist = new Array(16).fill(0);
            const blueHist = new Array(16).fill(0);

            let sumR = 0, sumG = 0, sumB = 0, sumSat = 0;
            let count = 0;

            for (let y = 0; y < 90; y += 2) {
                for (let x = 0; x < 160; x += 2) {
                    const i = (y * 160 + x) * 4;
                    const r = d[i];
                    const g = d[i + 1];
                    const b = d[i + 2];

                    const yVal = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
                    const lumaBucket = Math.floor(yVal / 16);
                    if (lumaBucket >= 0 && lumaBucket < 16) lumaHist[lumaBucket]++;

                    const rBucket = Math.floor(r / 16);
                    const gBucket = Math.floor(g / 16);
                    const bBucket = Math.floor(b / 16);
                    if (rBucket >= 0 && rBucket < 16) redHist[rBucket]++;
                    if (gBucket >= 0 && gBucket < 16) greenHist[gBucket]++;
                    if (bBucket >= 0 && bBucket < 16) blueHist[bBucket]++;

                    const max = Math.max(r, g, b);
                    const min = Math.min(r, g, b);
                    const sat = max - min;

                    sumR += r;
                    sumG += g;
                    sumB += b;
                    sumSat += sat;
                    count++;
                }
            }

            if (count === 0) return;

            const maxLuma = Math.max(...lumaHist, 1);
            const maxRed = Math.max(...redHist, 1);
            const maxGreen = Math.max(...greenHist, 1);
            const maxBlue = Math.max(...blueHist, 1);

            document.querySelectorAll('.gvf-scope-luma [data-index]').forEach(bar => {
                const idx = parseInt(bar.dataset.index);
                const val = lumaHist[idx] || 0;
                const pct = (val / maxLuma) * 36;
                bar.style.height = Math.max(2, pct) + 'px';
            });

            document.querySelectorAll('.gvf-scope-red [data-index]').forEach(bar => {
                const idx = parseInt(bar.dataset.index);
                const val = redHist[idx] || 0;
                const pct = (val / maxRed) * 28;
                bar.style.height = Math.max(2, pct) + 'px';
            });

            document.querySelectorAll('.gvf-scope-green [data-index]').forEach(bar => {
                const idx = parseInt(bar.dataset.index);
                const val = greenHist[idx] || 0;
                const pct = (val / maxGreen) * 28;
                bar.style.height = Math.max(2, pct) + 'px';
            });

            document.querySelectorAll('.gvf-scope-blue [data-index]').forEach(bar => {
                const idx = parseInt(bar.dataset.index);
                const val = blueHist[idx] || 0;
                const pct = (val / maxBlue) * 28;
                bar.style.height = Math.max(2, pct) + 'px';
            });

            const avgSat = sumSat / count / 255;
            const satPct = Math.min(100, avgSat * 200);
            const satFill = document.querySelector('.gvf-scope-sat-fill');
            const satValue = document.querySelector('.gvf-scope-sat-value');
            if (satFill) satFill.style.width = satPct + '%';
            if (satValue) satValue.textContent = avgSat.toFixed(2);

            const avgY = (0.299 * (sumR / count) + 0.587 * (sumG / count) + 0.114 * (sumB / count)) / 255;
            const avgR = (sumR / count) / 255;
            const avgG = (sumG / count) / 255;
            const avgB = (sumB / count) / 255;
            const avgRGB = (avgR + avgG + avgB) / 3;

            const avgYEl = document.querySelector('.gvf-scope-avg-y');
            const avgRGBEl = document.querySelector('.gvf-scope-avg-rgb');
            const avgSatEl = document.querySelector('.gvf-scope-avg-sat');

            if (avgYEl) avgYEl.textContent = `Y: ${avgY.toFixed(2)}`;
            if (avgRGBEl) avgRGBEl.textContent = `RGB: ${avgRGB.toFixed(2)}`;
            if (avgSatEl) avgSatEl.textContent = `Sat: ${avgSat.toFixed(2)}`;

            SCOPES.lastUpdate = now;

        } catch (e) {
            if (debug) console.log('[GVF] Scopes update failed:', e);
            SCOPES.lastUpdate = now;
        }
    }

    function startScopesLoop() {
        if (SCOPES.running) return;
        SCOPES.running = true;

        const loop = () => {
            if (!SCOPES.running) return;

            if (scopesHudShown) {
                try {
                    updateScopesData();
                } catch (e) {
                    if (debug) console.log('[GVF] Scopes loop error:', e);
                }
            }

            setTimeout(loop, SCOPES.updateInterval);
        };

        setTimeout(loop, 100);
    }

    function exportSettings() {
        return {
            schema: 'gvf-settings',
            ver: '1.10',
            enabled: !!enabled,
            notify: !!notify,
            darkMoody: !!darkMoody,
            tealOrange: !!tealOrange,
            vibrantSat: !!vibrantSat,

            sl: nFix(normSL(), 1),
            sr: nFix(normSR(), 1),
            bl: nFix(normBL(), 1),
            wl: nFix(normWL(), 1),
            dn: nFix(normDN(), 1),
            edge: nFix(normEDGE(), 2),

            hdr: nFix(normHDR(), 2),
            profile: String(profile),
            lutProfile: String((typeof activeLutProfileKey==='string' && activeLutProfileKey.trim()) ? activeLutProfileKey.trim() : 'none'),
            renderMode: String(renderMode),

            autoOn: !!autoOn,
            autoStrength: nFix(autoStrength, 2),
            autoLockWB: !!autoLockWB,

            adaptiveFps: {
                min: ADAPTIVE_FPS.MIN,
                max: ADAPTIVE_FPS.MAX,
                current: ADAPTIVE_FPS.current
            },

            user: {
                contrast: nFix(normU(u_contrast), 1),
                black: nFix(normU(u_black), 1),
                white: nFix(normU(u_white), 1),
                highlights: nFix(normU(u_highlights), 1),
                shadows: nFix(normU(u_shadows), 1),
                saturation: nFix(normU(u_sat), 1),
                vibrance: nFix(normU(u_vib), 1),
                sharpen: nFix(normU(u_sharp), 1),
                gamma: nFix(normU(u_gamma), 1),
                grain: nFix(normU(u_grain), 1),
                hue: nFix(normU(u_hue), 1),

                r_gain: Math.round(normRGB(u_r_gain)),
                g_gain: Math.round(normRGB(u_g_gain)),
                b_gain: Math.round(normRGB(u_b_gain))
            },
            debug: !!debug,
            logs: !!logs,
            cbFilter: String(cbFilter)
        };
    }

    function importSettings(obj) {
        if (!obj || typeof obj !== 'object') return false;

        _suspendSync = true;
        _inSync = true;

        try {
            const u = (obj.user && typeof obj.user === 'object') ? obj.user : {};

            if ('enabled' in obj) enabled = !!obj.enabled;
            if ('notify' in obj) {
                notify = !!obj.notify;
                gmSet(K.NOTIFY, notify);
            }
            if ('darkMoody' in obj) darkMoody = !!obj.darkMoody;
            if ('tealOrange' in obj) tealOrange = !!obj.tealOrange;
            if ('vibrantSat' in obj) vibrantSat = !!obj.vibrantSat;

            if ('sl' in obj) sl = clamp(Number(obj.sl), -2, 2);
            if ('sr' in obj) sr = clamp(Number(obj.sr), -2, 2);
            if ('bl' in obj) bl = clamp(Number(obj.bl), -2, 2);
            if ('wl' in obj) wl = clamp(Number(obj.wl), -2, 2);
            if ('dn' in obj) dn = clamp(Number(obj.dn), -1.5, 1.5);
            if ('edge' in obj) edge = clamp(Number(obj.edge), 0, 1);

            if ('hdr' in obj) hdr = clamp(Number(obj.hdr), -1, 2);

            if ('profile' in obj) {
                const p = String(obj.profile).toLowerCase();
                profile = (['off', 'film', 'anime', 'gaming', 'eyecare', 'user'].includes(p) ? p : 'off');
            }

            if ('renderMode' in obj) {
                const r = String(obj.renderMode).toLowerCase();
                renderMode = (r === 'gpu' ? 'gpu' : 'svg');
            }

            if ('autoOn' in obj) autoOn = !!obj.autoOn;
            if ('autoStrength' in obj) autoStrength = clamp(Number(obj.autoStrength), 0, 1);
            if ('autoLockWB' in obj) autoLockWB = !!obj.autoLockWB;

            if ('debug' in obj) {
                debug = !!obj.debug;
                gmSet(K.DEBUG, debug);
            }
            if ('logs' in obj) {
                logs = !!obj.logs;
                gmSet(K.LOGS, logs);
                LOG.on = logs;
            }

            if ('cbFilter' in obj) {
                const cb = String(obj.cbFilter).toLowerCase();
                cbFilter = (['none', 'protanopia', 'deuteranopia', 'tritanomaly'].includes(cb) ? cb : 'none');
                gmSet(K.CB_FILTER, cbFilter);
            }


// LUT profile selection (persist/restore via IO HUD config)
if ('lutProfile' in obj) {
    const raw = String(obj.lutProfile || 'none').trim() || 'none';

    // Accept either a key ("group||name") or legacy name-only value.
    const want = lutParseKey(raw);
    let key = (raw.includes('||') || raw === 'none') ? raw : want.key;

    // If legacy name-only was stored, pick the first matching profile (any group).
    if (!raw.includes('||') && raw !== 'none') {
        const p0 = (Array.isArray(lutProfiles) ? lutProfiles : []).find(x => _lutNormName(x && x.name) === want.name) || null;
        if (p0) key = lutKeyFromProfile(p0);
    }

    setActiveLutProfile(key);

    try {
        if (lutSelectEl) lutSelectEl.value = String(activeLutProfileKey || 'none');
        if (typeof refreshLutDropdownFn === 'function') refreshLutDropdownFn();
    } catch (_) { }

    log('Imported LUT profile selection:', activeLutProfileKey);
}            if ('contrast' in u) u_contrast = normU(u.contrast);
            if ('black' in u) u_black = normU(u.black);
            if ('white' in u) u_white = normU(u.white);
            if ('highlights' in u) u_highlights = normU(u.highlights);
            if ('shadows' in u) u_shadows = normU(u.shadows);
            if ('saturation' in u) u_sat = normU(u.saturation);
            if ('vibrance' in u) u_vib = normU(u.vibrance);
            if ('sharpen' in u) u_sharp = normU(u.sharpen);
            if ('gamma' in u) u_gamma = normU(u.gamma);
            if ('grain' in u) u_grain = normU(u.grain);
            if ('hue' in u) u_hue = normU(u.hue);

            if ('r_gain' in u) u_r_gain = normRGB(u.r_gain);
            if ('g_gain' in u) u_g_gain = normRGB(u.g_gain);
            if ('b_gain' in u) u_b_gain = normRGB(u.b_gain);

            enabled = !!enabled; darkMoody = !!darkMoody; tealOrange = !!tealOrange; vibrantSat = !!vibrantSat; iconsShown = !!iconsShown;

            sl = normSL(); sr = normSR(); bl = normBL(); wl = normWL(); dn = normDN(); hdr = normHDR();

            u_contrast = normU(u_contrast);
            u_black = normU(u_black);
            u_white = normU(u_white);
            u_highlights = normU(u_highlights);
            u_shadows = normU(u_shadows);
            u_sat = normU(u_sat);
            u_vib = normU(u_vib);
            u_sharp = normU(u_sharp);
            u_gamma = normU(u_gamma);
            u_grain = normU(u_grain);
            u_hue = normU(u_hue);

            u_r_gain = normRGB(u_r_gain);
            u_g_gain = normRGB(u_g_gain);
            u_b_gain = normRGB(u_b_gain);

            gmSet(K.enabled, enabled);
            gmSet(K.moody, darkMoody);
            gmSet(K.teal, tealOrange);
            gmSet(K.vib, vibrantSat);
            gmSet(K.icons, iconsShown);

            gmSet(K.SL, sl);
            gmSet(K.SR, sr);
            gmSet(K.BL, bl);
            gmSet(K.WL, wl);
            gmSet(K.DN, dn);
            gmSet(K.EDGE, edge);

            gmSet(K.HDR, hdr);
            if (hdr !== 0) gmSet(K.HDR_LAST, hdr);

            gmSet(K.PROF, profile);
            gmSet(K.RENDER_MODE, renderMode);
            gmSet(K.NOTIFY, notify);
            gmSet(K.G_HUD, gradingHudShown);
            gmSet(K.I_HUD, ioHudShown);
            gmSet(K.S_HUD, scopesHudShown);

            gmSet(K.U_CONTRAST, u_contrast);
            gmSet(K.U_BLACK, u_black);
            gmSet(K.U_WHITE, u_white);
            gmSet(K.U_HIGHLIGHTS, u_highlights);
            gmSet(K.U_SHADOWS, u_shadows);
            gmSet(K.U_SAT, u_sat);
            gmSet(K.U_VIB, u_vib);
            gmSet(K.U_SHARP, u_sharp);
            gmSet(K.U_GAMMA, u_gamma);
            gmSet(K.U_GRAIN, u_grain);
            gmSet(K.U_HUE, u_hue);

            gmSet(K.U_R_GAIN, u_r_gain);
            gmSet(K.U_G_GAIN, u_g_gain);
            gmSet(K.U_B_GAIN, u_b_gain);

            gmSet(K.AUTO_ON, autoOn);
            gmSet(K.AUTO_STRENGTH, autoStrength);
            gmSet(K.AUTO_LOCK_WB, autoLockWB);

            setAutoOn(autoOn, { silent: true });

            if (renderMode === 'gpu') {
                applyGpuFilter();
            } else {
                regenerateSvgImmediately();
            }
            scheduleOverlayUpdate();

            return true;
        } catch (_) {
            return false;
        } finally {
            _inSync = false;
            _suspendSync = false;
        }
    }

    function toggleRenderMode() {
        renderMode = renderMode === 'svg' ? 'gpu' : 'svg';
        gmSet(K.RENDER_MODE, renderMode);
        logToggle('Render Mode (Ctrl+Alt+X)', renderMode === 'gpu', `Mode: ${renderMode === 'gpu' ? 'WebGL2 Canvas Pipeline' : 'SVG'}`);

        // Save current settings in active profile
        updateCurrentProfileSettings();

        if (renderMode === 'gpu') {
            deactivateSVGMode();
            activateWebGLMode();
            applyGpuFilter();
        } else {
            deactivateWebGLMode();
            regenerateSvgImmediately();
        }

        scheduleOverlayUpdate();
    }

    function deactivateSVGMode() {
        const style = document.getElementById(STYLE_ID);
        if (style) style.remove();
        const svg = document.getElementById(SVG_ID);
        if (svg) svg.remove();
    }

    function ensureGpuSvgHost() {
        let svg = document.getElementById(GPU_SVG_ID);
        if (svg) return svg;

        svg = document.createElementNS(svgNS, 'svg');
        svg.id = GPU_SVG_ID;
        svg.setAttribute('width', '0');
        svg.setAttribute('height', '0');
        svg.style.position = 'absolute';
        svg.style.left = '-9999px';
        svg.style.top = '-9999px';

        const defs = document.createElementNS(svgNS, 'defs');
        svg.appendChild(defs);

        (document.body || document.documentElement).appendChild(svg);
        return svg;
    }

    function upsertGpuGainFilter() {
        const svg = ensureGpuSvgHost();
        if (!svg) return;

        const defs = svg.querySelector('defs') || svg;

        let f = defs.querySelector(`#${GPU_GAIN_FILTER_ID}`);
        if (!f) {
            f = document.createElementNS(svgNS, 'filter');
            f.setAttribute('id', GPU_GAIN_FILTER_ID);
            defs.appendChild(f);
        } else {
            while (f.firstChild) f.removeChild(f.firstChild);
        }

        const r = rgbGainToFactor(u_r_gain);
        const g = rgbGainToFactor(u_g_gain);
        const b = rgbGainToFactor(u_b_gain);

        const fe = document.createElementNS(svgNS, 'feColorMatrix');
        fe.setAttribute('type', 'matrix');
        fe.setAttribute('values', [
            r, 0, 0, 0, 0,
            0, g, 0, 0, 0,
            0, 0, b, 0, 0,
            0, 0, 0, 1, 0
        ].join(' '));
        f.appendChild(fe);
    }

    function gpuProfileMatrixActive() {
        return (profile === 'film' || profile === 'anime' || profile === 'gaming' || profile === 'eyecare');
    }

    function upsertGpuProfileFilter() {
        const svg = ensureGpuSvgHost();
        if (!svg) return;

        const defs = svg.querySelector('defs') || svg;

        let f = defs.querySelector(`#${GPU_PROFILE_FILTER_ID}`);
        if (!f) {
            f = document.createElementNS(svgNS, 'filter');
            f.setAttribute('id', GPU_PROFILE_FILTER_ID);
            f.setAttribute('color-interpolation-filters', 'sRGB');
            defs.appendChild(f);
        } else {
            const lastP = f.getAttribute('data-prof');
            if (lastP === profile) return;
            while (f.firstChild) f.removeChild(f.firstChild);
        }

        f.setAttribute('data-prof', profile);

        const profMat = mkProfileMatrixCT(profile);
        if (profMat) f.appendChild(profMat);

        if (profile === 'eyecare') {
            const sat = document.createElementNS(svgNS, 'feColorMatrix');
            sat.setAttribute('type', 'saturate');
            sat.setAttribute('values', '0.82');
            f.appendChild(sat);

            const sepia = document.createElementNS(svgNS, 'feColorMatrix');
            sepia.setAttribute('type', 'matrix');
            sepia.setAttribute('values', [
                0.85, 0.15, 0.00, 0, 0,
                0.10, 0.80, 0.10, 0, 0,
                0.05, 0.05, 0.70, 0, 0,
                0, 0, 0, 1, 0
            ].join(' '));
            f.appendChild(sepia);

            const hue = document.createElementNS(svgNS, 'feColorMatrix');
            hue.setAttribute('type', 'hueRotate');
            hue.setAttribute('values', '-22');
            f.appendChild(hue);
        }

        if (profile === 'anime') {

            const blur = document.createElementNS(svgNS, 'feGaussianBlur');
            blur.setAttribute('stdDeviation', '0.8');
            blur.setAttribute('in', 'SourceGraphic');
            blur.setAttribute('result', 'denoised');
            f.appendChild(blur);


            const sobel = document.createElementNS(svgNS, 'feConvolveMatrix');
            sobel.setAttribute('order', '3');
            sobel.setAttribute('kernelMatrix',
                '-1 -2 -1 ' +
                ' 0  0  0 ' +
                ' 1  2  1'
            );
            sobel.setAttribute('divisor', '1');
            sobel.setAttribute('in', 'denoised');
            sobel.setAttribute('result', 'edges');
            f.appendChild(sobel);

            const componentTransfer = document.createElementNS(svgNS, 'feComponentTransfer');
            componentTransfer.setAttribute('in', 'edges');
            componentTransfer.setAttribute('result', 'darkEdges');

            const funcR = document.createElementNS(svgNS, 'feFuncR');
            funcR.setAttribute('type', 'linear');
            funcR.setAttribute('slope', '2.2');
            funcR.setAttribute('intercept', '-0.3');
            componentTransfer.appendChild(funcR);

            const funcG = funcR.cloneNode();
            const funcB = funcR.cloneNode();
            componentTransfer.appendChild(funcG);
            componentTransfer.appendChild(funcB);
            f.appendChild(componentTransfer);

            const threshold = document.createElementNS(svgNS, 'feComponentTransfer');
            threshold.setAttribute('in', 'darkEdges');
            threshold.setAttribute('result', 'thresholdEdges');

            const tFuncR = document.createElementNS(svgNS, 'feFuncR');
            tFuncR.setAttribute('type', 'linear');
            tFuncR.setAttribute('slope', '3');
            tFuncR.setAttribute('intercept', '-0.4');
            threshold.appendChild(tFuncR);

            const tFuncG = tFuncR.cloneNode();
            const tFuncB = tFuncR.cloneNode();
            threshold.appendChild(tFuncG);
            threshold.appendChild(tFuncB);
            f.appendChild(threshold);

            const blend = document.createElementNS(svgNS, 'feComposite');
            blend.setAttribute('operator', 'arithmetic');
            blend.setAttribute('k1', '0');
            blend.setAttribute('k2', '1');
            blend.setAttribute('k3', '0.3');
            blend.setAttribute('k4', '0');
            blend.setAttribute('in', 'SourceGraphic');
            blend.setAttribute('in2', 'thresholdEdges');
            blend.setAttribute('result', 'final');
            f.appendChild(blend);
        }
    }

    function removeGpuProfileFilter() {
        const svg = document.getElementById(GPU_SVG_ID);
        if (!svg) return;
        const f = svg.querySelector(`#${GPU_PROFILE_FILTER_ID}`);
        if (f && f.parentNode) f.parentNode.removeChild(f);
    }

    function removeGpuGainFilter() {
        const svg = document.getElementById(GPU_SVG_ID);
        if (!svg) return;
        const f = svg.querySelector(`#${GPU_GAIN_FILTER_ID}`);
        if (f && f.parentNode) f.parentNode.removeChild(f);
    }

    function gpuGainActive() {
        if (profile !== 'user') return false;
        return (u_r_gain !== 128) || (u_g_gain !== 128) || (u_b_gain !== 128);
    }

    function applyGpuFilter() {
        if (renderMode === 'gpu' && webglPipeline && webglPipeline.active) {
            let style = document.getElementById(STYLE_ID);
            if (style) style.remove();
            scheduleOverlayUpdate();
            return;
        }

        let style = document.getElementById(STYLE_ID);

        const nothingOn =
            !enabled && !darkMoody && !tealOrange && !vibrantSat && normEDGE() === 0 && normHDR() === 0 && (profile === 'off') && !autoOn && cbFilter === 'none'
            && (!activeLutMatrix4x5 || String(activeLutProfileKey || 'none') === 'none');

        if (nothingOn) {
            if (style) style.remove();
            removeGpuGainFilter();
            scheduleOverlayUpdate();
            return;
        }

        if (!style) {
            style = document.createElement('style');
            style.id = STYLE_ID;
            document.head.appendChild(style);
        }

        let gpuFilterString = getGpuFilterString();

        if (gpuProfileMatrixActive()) {
            upsertGpuProfileFilter();
            const urlP = `url(#${GPU_PROFILE_FILTER_ID})`;
            gpuFilterString = gpuFilterString ? (gpuFilterString + ' ' + urlP) : urlP;
        } else {
            removeGpuProfileFilter();
        }

        if (gpuGainActive()) {
            upsertGpuGainFilter();
            const url = `url(#${GPU_GAIN_FILTER_ID})`;
            gpuFilterString = gpuFilterString ? (gpuFilterString + ' ' + url) : url;
        } else {
            removeGpuGainFilter();
        }

        const outlineCss = (PROFILE_VIDEO_OUTLINE && profile !== 'off')
            ? `outline: 2px solid ${(PROF[profile] || PROF.off).color} !important; outline-offset: -2px;`
            : `outline: none !important;`;

        const finalFilter = (gpuFilterString && String(gpuFilterString).trim()) ? String(gpuFilterString).trim() : 'none';

        style.textContent = `
      video {
        will-change: filter;
        transform: translateZ(0);
        filter: ${finalFilter} !important;
        ${outlineCss}
      }
    `;

        scheduleOverlayUpdate();
    }

    function updateMainOverlayState(overlay) {
        if (!iconsShown) { overlay.style.display = 'none'; return; }
        overlay.style.display = 'flex';

        const state = {
            base: enabled,
            moody: darkMoody,
            teal: tealOrange,
            vib: vibrantSat,
            hdr: (normHDR() !== 0),
            auto: autoOn,
            mode: true
        };

        overlay.querySelectorAll('[data-key]').forEach(el => {
            const key = el.dataset.key;
            let on = !!state[key];

            if (key === 'mode') {
                el.textContent = renderMode === 'gpu' ? 'C' : 'S';
                on = true;
                el.style.color = renderMode === 'gpu' ? '#ffaa00' : '#88ccff';
                el.style.background = 'rgba(255,255,255,0.15)';
            } else {
                el.style.color = on ? '#fff' : '#666';
                el.style.background = on ? 'rgba(255,255,255,0.22)' : '#000';
            }
            el.style.boxShadow = '0 0 0 1px rgba(255,255,255,0.18) inset';
        });

        const badge = overlay.querySelector('.gvf-prof-badge');
        if (badge) {
            const p = PROF[profile] || PROF.off;
            const c = p.color;
            badge.textContent = `${p.name} (C)`;

            if (c && c !== 'transparent') {
                badge.style.background = 'rgba(0,0,0,0.92)';
                badge.style.border = `1px solid ${c}`;
                badge.style.boxShadow = `0 0 0 1px rgba(255,255,255,0.14) inset, 0 0 0 2px ${c}, 0 0 18px ${c}55`;
            } else {
                badge.style.background = 'rgba(0,0,0,0.92)';
                badge.style.border = '1px solid rgba(255,255,255,0.10)';
                badge.style.boxShadow = '0 0 0 1px rgba(255,255,255,0.14) inset';
            }
        }

        const renderBadge = overlay.querySelector('.gvf-render-badge');
        if (renderBadge) {
            renderBadge.textContent = renderMode === 'gpu' ? 'GPU' : 'SVG';
            renderBadge.style.color = renderMode === 'gpu' ? '#ffaa00' : '#88ccff';
        }

        const setPair = (name, v) => {
            const r = overlay.querySelector(`[data-gvf-range="${cssEscape(name)}"]`);
            const t = overlay.querySelector(`[data-gvf-val="${cssEscape(name)}"]`);
            if (r) r.value = String(v);
            if (t) t.textContent = Number(v).toFixed(2);
        };

        setPair('SL', normSL());
        setPair('SR', normSR());
        setPair('BL', normBL());
        setPair('WL', normWL());
        setPair('DN', normDN());
        setPair('HDR', normHDR());
    }

    function updateGradingOverlayState(overlay) {
        if (!gradingHudShown) { overlay.style.display = 'none'; return; }
        overlay.style.display = 'flex';

        const setPair = (name, v) => {
            const r = overlay.querySelector(`[data-gvf-range="${cssEscape(name)}"]`);
            const t = overlay.querySelector(`[data-gvf-val="${cssEscape(name)}"]`);
            if (r) r.value = String(v);
            if (t) t.textContent = Number(v).toFixed(1);
        };

        setPair('U_CONTRAST', normU(u_contrast));
        setPair('U_BLACK', normU(u_black));
        setPair('U_WHITE', normU(u_white));
        setPair('U_HIGHLIGHTS', normU(u_highlights));
        setPair('U_SHADOWS', normU(u_shadows));
        setPair('U_SAT', normU(u_sat));
        setPair('U_VIB', normU(u_vib));
        setPair('U_SHARP', normU(u_sharp));
        setPair('U_GAMMA', normU(u_gamma));
        setPair('U_GRAIN', normU(u_grain));
        setPair('U_HUE', normU(u_hue));

        const setRGBPair = (name, v) => {
            const r = overlay.querySelector(`[data-gvf-range="${cssEscape(name)}"]`);
            const t = overlay.querySelector(`[data-gvf-val="${cssEscape(name)}"]`);
            if (r) r.value = String(v);
            if (t) t.textContent = String(Math.round(v));
        };

        setRGBPair('U_R_GAIN', normRGB(u_r_gain));
        setRGBPair('U_G_GAIN', normRGB(u_g_gain));
        setRGBPair('U_B_GAIN', normRGB(u_b_gain));

        // Update color blindness dropdown
        const cbSelect = overlay.querySelector('[data-gvf-select="cb_filter"]');
        if (cbSelect) {
            cbSelect.value = cbFilter;
        }
    }

    function updateIOOverlayState(overlay) {
        if (!ioHudShown) { overlay.style.display = 'none'; return; }
        overlay.style.display = 'flex';

        try {
            const btnRec = overlay.__btnRec;
            const status = overlay.__status;
            if (btnRec && !REC.active) {
                const v = getActiveVideoForCapture();
                if (!v) {
                    btnRec.disabled = true;
                    btnRec.textContent = 'No video';
                    btnRec.style.opacity = '0.55';
                    btnRec.style.cursor = 'not-allowed';
                } else {
                    const chk = canBakeToCanvas(v);
                    if (!chk.ok) {
                        btnRec.disabled = true;
                        btnRec.textContent = 'DRM blocked';
                        btnRec.style.opacity = '0.55';
                        btnRec.style.cursor = 'not-allowed';
                        if (status && status.textContent === 'Tip: paste JSON here → Save') {
                            status.textContent = `Recording disabled: ${chk.reason}`;
                        }
                    } else {
                        btnRec.disabled = false;
                        btnRec.textContent = 'Record';
                        btnRec.style.opacity = '1';
                        btnRec.style.cursor = 'pointer';

                        if (isFirefox()) {
                            const tap = ensureAudioTap(v);
                            if (tap && tap.tracks && tap.tracks.length && status && !status.textContent.startsWith('Recording disabled')) {
                                if (status.textContent === 'Tip: paste JSON here → Save') {
                                    status.textContent = 'Firefox: recording uses WebAudio tap (should keep audio + no auto-mute).';
                                }
                            }
                        }
                    }
                }
            }

            // Update Debug button
            const btnDebug = Array.from(overlay.querySelectorAll('button')).find(b => b.textContent.startsWith('🐞') || b.textContent.startsWith('Debug'));
            if (btnDebug) {
                btnDebug.textContent = debug ? '🐞 Debug: ON' : '🐞 Debug: OFF';
                btnDebug.style.background = debug ? 'rgba(0,255,0,0.2)' : 'rgba(255,0,0,0.2)';
                btnDebug.style.border = debug ? '1px solid #00ff00' : '1px solid #ff0000';
                btnDebug.style.color = debug ? '#00ff00' : '#ff6666';
            }

            // Config Button Status
            const btnConfig = Array.from(overlay.querySelectorAll('button')).find(b => b.textContent.includes('Config'));
            if (btnConfig) {
                if (configMenuVisible) {
                    btnConfig.style.background = 'rgba(42, 111, 219, 0.6)';
                } else {
                    btnConfig.style.background = 'rgba(42, 111, 219, 0.4)';
                }
            }
        } catch (_) { }

        const ta = overlay.querySelector('.gvf-io-text');
        if (!ta) return;
        if (ta.dataset.dirty) return;

        ta.value = JSON.stringify(exportSettings(), null, 2);
    }

    function updateScopesOverlayState(overlay) {
        if (!scopesHudShown) { overlay.style.display = 'none'; return; }
        overlay.style.display = 'flex';
    }

    const fsWraps2 = new WeakMap();

    function ensureFsWrapper(video) {
        if (fsWraps2.has(video)) return fsWraps2.get(video);
        if (!video || !video.parentNode) return null;

        const parent = video.parentNode;

        const wrap = document.createElement('div');
        wrap.className = 'gvf-fs-wrap';
        wrap.style.cssText = `
      position: relative;display: inline-block;width: 100%;height: 100%;
      max-width: 100%;background: black;
    `;

        const ph = document.createComment('gvf-video-placeholder');
        parent.insertBefore(ph, video);
        parent.insertBefore(wrap, video);
        wrap.appendChild(video);

        wrap.__gvfPlaceholder = ph;
        fsWraps2.set(video, wrap);
        return wrap;
    }

    function restoreFromFsWrapper(video) {
        const wrap = fsWraps2.get(video);
        if (!wrap) return;
        const ph = wrap.__gvfPlaceholder;
        if (ph && ph.parentNode) {
            ph.parentNode.insertBefore(video, ph);
            ph.parentNode.removeChild(ph);
        }
        if (wrap.parentNode) wrap.parentNode.removeChild(wrap);
        fsWraps2.delete(video);
    }

    function patchFullscreenRequest(video) {
        if (!video || video.__gvfFsPatched) return;
        video.__gvfFsPatched = true;

        if (typeof video.webkitEnterFullscreen === 'function') return;

        const origReq = video.requestFullscreen || video.webkitRequestFullscreen || video.msRequestFullscreen;
        if (!origReq) return;

        const callWrapFs = async () => {
            const wrap = ensureFsWrapper(video);
            if (!wrap) return origReq.call(video);
            const req = wrap.requestFullscreen || wrap.webkitRequestFullscreen || wrap.msRequestFullscreen;
            if (req) return req.call(wrap);
            return origReq.call(video);
        };

        if (video.requestFullscreen) {
            const _orig = video.requestFullscreen.bind(video);
            video.requestFullscreen = function () { return callWrapFs() || _orig(); };
        }
        if (video.webkitRequestFullscreen) {
            const _orig = video.webkitRequestFullscreen.bind(video);
            video.webkitRequestFullscreen = function () { return callWrapFs() || _orig(); };
        }
        if (video.msRequestFullscreen) {
            const _orig = video.msRequestFullscreen.bind(video);
            video.msRequestFullscreen = function () { return callWrapFs() || _orig(); };
        }
    }

    function getOverlayContainer(video) {
        const fsEl = getFsEl();
        const wrap = fsWraps2.get(video);

        if (fsEl && wrap && fsEl === wrap) return wrap;

        if (fsEl && (fsEl === video || (fsEl.contains && fsEl.contains(video)))) {
            if (fsEl.tagName && fsEl.tagName.toLowerCase() === 'video') return document.body || document.documentElement;
            return fsEl;
        }
        return document.body || document.documentElement;
    }

    function positionOverlayAt(video, overlay, dx, dy) {
        const fsEl = getFsEl();
        const container = getOverlayContainer(video);

        if (overlay.parentNode !== container) container.appendChild(overlay);

        const isWrapFs = fsEl && container === fsEl && fsEl.classList && fsEl.classList.contains('gvf-fs-wrap');
        overlay.style.position = isWrapFs ? 'absolute' : 'fixed';

        const r = video.getBoundingClientRect();
        if (!r || r.width < 40 || r.height < 40) { overlay.style.display = 'none'; return; }

        if (!fsEl) {
            if (r.bottom < 0 || r.right < 0 || r.top > (window.innerHeight || 0) || r.left > (window.innerWidth || 0)) {
                overlay.style.display = 'none';
                return;
            }
        }

        if (overlay.classList.contains('gvf-video-overlay-scopes')) {
            if (isWrapFs) {
                const cr = container.getBoundingClientRect();
                overlay.style.top = `${Math.round((r.top - cr.top) + dy)}px`;
                overlay.style.left = `${Math.round((r.left - cr.left) + dx)}px`;
                overlay.style.transform = 'none';
            } else {
                overlay.style.top = `${Math.round(r.top + dy)}px`;
                overlay.style.left = `${Math.round(r.left + dx)}px`;
                overlay.style.transform = 'none';
            }
        } else {
            if (isWrapFs) {
                const cr = container.getBoundingClientRect();
                overlay.style.top = `${Math.round((r.top - cr.top) + dy)}px`;
                overlay.style.left = `${Math.round((r.left - cr.left) + r.width - dx)}px`;
                overlay.style.transform = 'translateX(-100%) translateZ(0)';
            } else {
                overlay.style.top = `${Math.round(r.top + dy)}px`;
                overlay.style.left = `${Math.round(r.left + r.width - dx)}px`;
                overlay.style.transform = 'translateX(-100%) translateZ(0)';
            }
        }
    }

    function ensureOverlays() {
        document.querySelectorAll('video').forEach(v => {
            patchFullscreenRequest(v);

            if (!overlaysMain.has(v)) overlaysMain.set(v, mkMainOverlay());
            if (!overlaysGrade.has(v)) overlaysGrade.set(v, mkGradingOverlay());
            if (!overlaysIO.has(v)) overlaysIO.set(v, mkIOOverlay());
            if (!overlaysScopes.has(v)) overlaysScopes.set(v, mkScopesOverlay());
            if (debug && !overlaysAutoDot.has(v)) overlaysAutoDot.set(v, mkAutoDotOverlay());
        });
    }

    function updateAllOverlays() {
        ensureOverlays();

        const primary = choosePrimaryVideo();
        const hudPrimary = getHudPrimaryVideo();

        document.querySelectorAll('video').forEach(v => {
            const oMain = overlaysMain.get(v);
            const oGr = overlaysGrade.get(v);
            const oIO = overlaysIO.get(v);
            const oScopes = overlaysScopes.get(v);
            const oDot = overlaysAutoDot.get(v);
            const visible = isVideoRenderable(v);
            const hudVisible = isHudVideoVisible(v);

            if (oMain) {
                updateMainOverlayState(oMain);
                if (iconsShown && hudVisible && hudPrimary === v) positionOverlayAt(v, oMain, 10, 10);
                else oMain.style.display = 'none';
            }
            if (oGr) {
                if (gradingHudShown && hudVisible && hudPrimary === v) {
                    updateGradingOverlayState(oGr);
                    positionOverlayAt(v, oGr, 10, 10 + 280);
                } else {
                    oGr.style.display = 'none';
                }
            }
            if (oIO) {
                if (ioHudShown && hudVisible && hudPrimary === v) {
                    updateIOOverlayState(oIO);
                    positionOverlayAt(v, oIO, 10, 10 + 560);
                } else {
                    oIO.style.display = 'none';
                }
            }
            if (oScopes) {
                if (scopesHudShown && hudVisible && hudPrimary === v) {
                    updateScopesOverlayState(oScopes);
                    positionOverlayAt(v, oScopes, 10, 10);
                } else {
                    oScopes.style.display = 'none';
                }
            }

            if (oDot) {
                applyAutoDotStyle(oDot);

                if (!debug || !autoOn || !primary || v !== primary || !visible) {
                    oDot.style.display = 'none';
                } else {
                    positionOverlayAt(v, oDot, 10, 10);
                    oDot.style.display = 'block';
                }
            }
        });
    }

    function scheduleOverlayUpdate() {
        if (rafScheduled) return;
        rafScheduled = true;
        requestAnimationFrame(() => {
            rafScheduled = false;
            updateAllOverlays();
        });
    }

    function onFsChange() {
        const fsEl = getFsEl();
        if (!fsEl) {
            document.querySelectorAll('video').forEach(v => {
                if (fsWraps2.has(v)) restoreFromFsWrapper(v);
            });
        }
        scheduleOverlayUpdate();
    }

    document.addEventListener('visibilitychange', scheduleOverlayUpdate, { passive: true });
    window.addEventListener('focus', scheduleOverlayUpdate, { passive: true });
    window.addEventListener('blur', () => setTimeout(scheduleOverlayUpdate, 100), { passive: true });

    function mkGamma(ch, amp, exp, off) {
        const f = document.createElementNS(svgNS, ch);
        f.setAttribute('type', 'gamma');
        f.setAttribute('amplitude', String(amp));
        f.setAttribute('exponent', String(exp));
        f.setAttribute('offset', String(off));
        return f;
    }

    function mkOffsetCT(inId, outId, offset) {
        const ct = document.createElementNS(svgNS, 'feComponentTransfer');
        ct.setAttribute('in', inId);
        ct.setAttribute('result', outId);
        ct.appendChild(mkGamma('feFuncR', 1.0, 1.0, offset));
        ct.appendChild(mkGamma('feFuncG', 1.0, 1.0, offset));
        ct.appendChild(mkGamma('feFuncB', 1.0, 1.0, offset));
        return ct;
    }

    function mkHighlightsTableCT(inId, outId, hiAdj) {
        const knee = 0.78;
        const steps = 17;
        const vals = [];
        for (let i = 0; i < steps; i++) {
            const x = i / (steps - 1);
            let y = x;
            if (x > knee) {
                const t = (x - knee) / (1 - knee);
                y = x + hiAdj * t;
            }
            y = clamp(y, 0, 1);
            vals.push(y.toFixed(4));
        }

        const ct = document.createElementNS(svgNS, 'feComponentTransfer');
        ct.setAttribute('in', inId);
        ct.setAttribute('result', outId);

        const mkTable = (tag) => {
            const f = document.createElementNS(svgNS, tag);
            f.setAttribute('type', 'table');
            f.setAttribute('tableValues', vals.join(' '));
            return f;
        };

        ct.appendChild(mkTable('feFuncR'));
        ct.appendChild(mkTable('feFuncG'));
        ct.appendChild(mkTable('feFuncB'));
        return ct;
    }

    function mkDenoiseBlend(inId, outId, sigma, mix) {
        const blur = document.createElementNS(svgNS, 'feGaussianBlur');
        blur.setAttribute('in', inId);
        blur.setAttribute('stdDeviation', String(sigma));
        blur.setAttribute('result', outId + '_b');

        const comp = document.createElementNS(svgNS, 'feComposite');
        comp.setAttribute('in', inId);
        comp.setAttribute('in2', outId + '_b');
        comp.setAttribute('operator', 'arithmetic');
        comp.setAttribute('k1', '0');
        comp.setAttribute('k2', String(1 - mix));
        comp.setAttribute('k3', String(mix));
        comp.setAttribute('k4', '0');
        comp.setAttribute('result', outId);

        return [blur, comp];
    }

    function mkGrain(inId, outId, alpha) {
        const turb = document.createElementNS(svgNS, 'feTurbulence');
        turb.setAttribute('type', 'fractalNoise');
        turb.setAttribute('baseFrequency', '0.9');
        turb.setAttribute('numOctaves', '2');
        turb.setAttribute('seed', '2');
        turb.setAttribute('result', outId + '_n');

        const noiseCM = document.createElementNS(svgNS, 'feColorMatrix');
        noiseCM.setAttribute('in', outId + '_n');
        noiseCM.setAttribute('type', 'matrix');
        noiseCM.setAttribute('values',
            '0.33 0.33 0.33 0 0 ' +
            '0.33 0.33 0.33 0 0 ' +
            '0.33 0.33 0.33 0 0 ' +
            '0    0    0    1 0'
        );
        noiseCM.setAttribute('result', outId + '_nm');

        const comp = document.createElementNS(svgNS, 'feComposite');
        comp.setAttribute('in', inId);
        comp.setAttribute('in2', outId + '_nm');
        comp.setAttribute('operator', 'arithmetic');
        comp.setAttribute('k1', '0');
        comp.setAttribute('k2', '1');
        comp.setAttribute('k3', String(alpha));
        comp.setAttribute('k4', '0');
        comp.setAttribute('result', outId);

        return [turb, noiseCM, comp];
    }

    function mkClarityHighpass(inId, outId, sigma, amount) {
        const blur = document.createElementNS(svgNS, 'feGaussianBlur');
        blur.setAttribute('in', inId);
        blur.setAttribute('stdDeviation', String(sigma));
        blur.setAttribute('result', outId + '_b');

        const comp = document.createElementNS(svgNS, 'feComposite');
        comp.setAttribute('in', inId);
        comp.setAttribute('in2', outId + '_b');
        comp.setAttribute('operator', 'arithmetic');
        comp.setAttribute('k1', '0');
        comp.setAttribute('k2', String(1 + amount));
        comp.setAttribute('k3', String(-amount));
        comp.setAttribute('k4', '0');
        comp.setAttribute('result', outId);

        return [blur, comp];
    }

    function mkBlend(inA, inB, outId, mixB) {
        const comp = document.createElementNS(svgNS, 'feComposite');
        comp.setAttribute('in', inA);
        comp.setAttribute('in2', inB);
        comp.setAttribute('operator', 'arithmetic');
        comp.setAttribute('k1', '0');
        comp.setAttribute('k2', String(1 - mixB));
        comp.setAttribute('k3', String(mixB));
        comp.setAttribute('k4', '0');
        comp.setAttribute('result', outId);
        return comp;
    }

    function mkLinearCT(inId, outId, slope, intercept) {
        const ct = document.createElementNS(svgNS, 'feComponentTransfer');
        ct.setAttribute('in', inId);
        ct.setAttribute('result', outId);

        const mkLin = (tag) => {
            const f = document.createElementNS(svgNS, tag);
            f.setAttribute('type', 'linear');
            f.setAttribute('slope', String(slope));
            f.setAttribute('intercept', String(intercept));
            return f;
        };

        ct.appendChild(mkLin('feFuncR'));
        ct.appendChild(mkLin('feFuncG'));
        ct.appendChild(mkLin('feFuncB'));
        return ct;
    }

    function mkSCurveTableCT(inId, outId, strength) {
        const s = clamp(strength, 0, 2);

        const steps = 33;
        const vals = [];
        const toe = 0.20 + s * 0.06;
        const shoulder = 0.78 - s * 0.05;
        const shoulderGain = 0.16 + s * 0.10;

        for (let i = 0; i < steps; i++) {
            const x = i / (steps - 1);
            let y = x;

            if (x < toe) {
                const t = x / toe;
                const ss = t * t * (3 - 2 * t);
                y = x + (toe - x) * (0.10 + s * 0.10) * (1 - ss);
            }

            if (x > shoulder) {
                const t = (x - shoulder) / (1 - shoulder);
                const ss = t * t * (3 - 2 * t);
                y = x - shoulderGain * ss * t;
            }

            y = clamp(y, 0, 1);
            vals.push(y.toFixed(4));
        }

        const ct = document.createElementNS(svgNS, 'feComponentTransfer');
        ct.setAttribute('in', inId);
        ct.setAttribute('result', outId);

        const mkTable = (tag) => {
            const f = document.createElementNS(svgNS, tag);
            f.setAttribute('type', 'table');
            f.setAttribute('tableValues', vals.join(' '));
            return f;
        };

        ct.appendChild(mkTable('feFuncR'));
        ct.appendChild(mkTable('feFuncG'));
        ct.appendChild(mkTable('feFuncB'));
        return ct;
    }

    function mkProfileMatrixCT(prof) {
        const cm = document.createElementNS(svgNS, 'feColorMatrix');
        cm.setAttribute('type', 'matrix');

        let values = null;

        if (prof === 'film') {
            values =
                '1.06 0.02 0.00 0 -0.03 ' +
                '0.01 1.03 0.01 0 -0.02 ' +
                '0.00 0.03 1.05 0 -0.03 ' +
                '0    0    0    1  0';
        } else if (prof === 'anime') {
            values =
                '1.06 0.01 0.00 0 -0.012 ' +
                '0.00 1.07 0.01 0 -0.012 ' +
                '0.01 0.03 1.10 0 -0.016 ' +
                '0    0    0    1  0';
        } else if (prof === 'gaming') {
            values =
                '1.04 0.00 0.00 0 -0.010 ' +
                '0.00 1.04 0.00 0 -0.010 ' +
                '0.00 0.00 1.04 0 -0.010 ' +
                '0    0    0    1  0';
        } else if (prof === 'eyecare') {
            values =
                '1.08 0.00 0.00 0 0.00 ' +
                '0.15 1.05 0.00 0 0.00 ' +
                '0.25 0.00 0.50 0 0.00 ' +
                '0    0    0    1  0';
        } else {
            return null;
        }

        cm.setAttribute('values', values);
        return cm;
    }

    function userToneCss() {
        if (profile !== 'user') return '';

        const c = clamp(1.0 + (uDelta(u_contrast) * 0.04), 0.60, 1.60);
        const sat = clamp(1.0 + (uDelta(u_sat) * 0.05), 0.40, 1.80);
        const vib = clamp(1.0 + (uDelta(u_vib) * 0.02), 0.70, 1.35);
        const hue = clamp(uDelta(u_hue) * 3.0, -30, 30);

        const blk = clamp(uDelta(u_black) * 0.012, -0.12, 0.12);
        const wht = clamp(uDelta(u_white) * 0.012, -0.12, 0.12);
        const sh = clamp(uDelta(u_shadows) * 0.010, -0.10, 0.10);
        const hi = clamp(uDelta(u_highlights) * 0.010, -0.10, 0.10);

        const br = clamp(1.0 + (-blk + wht + sh + hi) * 0.6, 0.70, 1.35);

        const g = clamp(1.0 + (uDelta(u_gamma) * 0.025), 0.60, 1.60);
        const gBr = clamp(1.0 + (1.0 - g) * 0.18, 0.85, 1.20);
        const gCt = clamp(1.0 + (g - 1.0) * 0.10, 0.90, 1.15);

        const s = uDelta(u_sharp);
        const cssSharp = s > 0 ? ` drop-shadow(0 0 ${Math.max(0.001, (s / 10) * 0.35).toFixed(3)}px rgba(0,0,0,0.0))` : '';

        return ` brightness(${(br * gBr).toFixed(3)}) contrast(${(c * gCt).toFixed(3)}) saturate(${(sat * vib).toFixed(3)}) hue-rotate(${hue.toFixed(1)}deg)${cssSharp}`;
    }

    function buildFilter(svg, id, opts, radius, sharpenA, blurSigma, blackOffset, whiteAdj, dnVal, edgeVal, hdrVal, prof) {
        const { moody, teal, vib } = opts;

        const filter = document.createElementNS(svgNS, 'filter');
        filter.setAttribute('id', id);
        filter.setAttribute('color-interpolation-filters', 'sRGB');

        let last = 'SourceGraphic';

        if (blurSigma > 0) {
            const b = document.createElementNS(svgNS, 'feGaussianBlur');
            b.setAttribute('in', last);
            b.setAttribute('stdDeviation', String(radius));
            b.setAttribute('result', 'r_blur');
            filter.appendChild(b);
            last = 'r_blur';
        } else {
            const blur = document.createElementNS(svgNS, 'feGaussianBlur');
            blur.setAttribute('in', 'SourceGraphic');
            blur.setAttribute('stdDeviation', String(radius));
            blur.setAttribute('result', 'blur');
            filter.appendChild(blur);

            const comp = document.createElementNS(svgNS, 'feComposite');
            comp.setAttribute('in', 'SourceGraphic');
            comp.setAttribute('in2', 'blur');
            comp.setAttribute('operator', 'arithmetic');
            comp.setAttribute('k1', '0');
            comp.setAttribute('k2', String(1 + sharpenA));
            comp.setAttribute('k3', String(-sharpenA));
            comp.setAttribute('k4', '0');
            comp.setAttribute('result', 'r0');
            filter.appendChild(comp);

            last = 'r0';
        }

        if (blackOffset !== 0) {
            filter.appendChild(mkOffsetCT(last, 'r_bl', blackOffset));
            last = 'r_bl';
        }

        if (whiteAdj !== 0) {
            filter.appendChild(mkHighlightsTableCT(last, 'r_wl', whiteAdj));
            last = 'r_wl';
        }

        if (dnVal > 0) {
            const mix = dnToDenoiseMix(dnVal);
            const sig = dnToDenoiseSigma(dnVal);
            const [b, c] = mkDenoiseBlend(last, 'r_dn', sig, mix);
            filter.appendChild(b);
            filter.appendChild(c);
            last = 'r_dn';
        } else if (dnVal < 0) {
            const alpha = dnToGrainAlpha(dnVal);
            const parts = mkGrain(last, 'r_gr', alpha);
            parts.forEach(p => filter.appendChild(p));
            last = 'r_gr';
        }

        if (hdrVal !== 0) {
            if (hdrVal > 0) {
                const s = clamp(hdrVal, 0, 2);

                const clarityAmt = 0.55 + s * 0.55;
                const claritySigma = clamp(1.3 + radius * 0.75, 1.3, 3.6);
                const [b, c] = mkClarityHighpass(last, 'r_hdr_cl', claritySigma, clarityAmt);
                filter.appendChild(b);
                filter.appendChild(c);

                filter.appendChild(mkBlend(last, 'r_hdr_cl', 'r_hdr_clb', clamp(0.65 + s * 0.12, 0.65, 0.89)));
                last = 'r_hdr_clb';

                filter.appendChild(mkSCurveTableCT(last, 'r_hdr_tm', s));
                last = 'r_hdr_tm';

                const slope = 1.10 + s * 0.18;
                const intercept = -0.015 + s * 0.006;
                filter.appendChild(mkLinearCT(last, 'r_hdr_lin', slope, intercept));
                last = 'r_hdr_lin';

                const sat = document.createElementNS(svgNS, 'feColorMatrix');
                sat.setAttribute('type', 'saturate');
                sat.setAttribute('values', String(1.10 + s * 0.30));
                sat.setAttribute('in', last);
                sat.setAttribute('result', 'r_hdr_sat');
                filter.appendChild(sat);
                last = 'r_hdr_sat';
            } else {
                const s = clamp(-hdrVal, 0, 1);

                const mix = clamp(s * 0.55, 0, 0.55);
                const sig = clamp(0.9 + s * 1.8, 0.9, 2.7);
                const [b, c] = mkDenoiseBlend(last, 'r_hdr_soft', sig, mix);
                filter.appendChild(b);
                filter.appendChild(c);
                last = 'r_hdr_soft';

                const sat = document.createElementNS(svgNS, 'feColorMatrix');
                sat.setAttribute('type', 'saturate');
                sat.setAttribute('values', String(1.0 - s * 0.18));
                sat.setAttribute('in', last);
                sat.setAttribute('result', 'r_hdr_soft2');
                filter.appendChild(sat);
                last = 'r_hdr_soft2';
            }
        }

        // Apply color blindness filter if enabled
        if (cbFilter !== 'none') {
            const cbMatrix = getColorBlindnessMatrix(cbFilter);
            const cbCM = document.createElementNS(svgNS, 'feColorMatrix');
            cbCM.setAttribute('type', 'matrix');
            cbCM.setAttribute('in', last);
            cbCM.setAttribute('result', 'r_cb');
            cbCM.setAttribute('values', matToSvgValues(cbMatrix));
            filter.appendChild(cbCM);
            last = 'r_cb';
        }

        // Apply LUT matrix if a LUT profile is active (approximation)
        if (activeLutMatrix4x5 && Array.isArray(activeLutMatrix4x5) && activeLutMatrix4x5.length === 20 && activeLutProfileKey !== 'none') {
            const lutCM = document.createElementNS(svgNS, 'feColorMatrix');
            lutCM.setAttribute('type', 'matrix');
            lutCM.setAttribute('in', last);
            lutCM.setAttribute('result', 'r_lut');
            lutCM.setAttribute('values', matToSvgValues(activeLutMatrix4x5));
            filter.appendChild(lutCM);
            last = 'r_lut';
        }


        if (profile === 'user') {
            const rGain = rgbGainToFactor(u_r_gain);
            const gGain = rgbGainToFactor(u_g_gain);
            const bGain = rgbGainToFactor(u_b_gain);

            if (Math.abs(rGain - 1.0) > 0.01 || Math.abs(gGain - 1.0) > 0.01 || Math.abs(bGain - 1.0) > 0.01) {

                const rgbMatrix = matRGBGain(rGain, gGain, bGain);
                const rgbCM = document.createElementNS(svgNS, 'feColorMatrix');
                rgbCM.setAttribute('type', 'matrix');
                rgbCM.setAttribute('in', last);
                rgbCM.setAttribute('result', 'r_rgb');
                rgbCM.setAttribute('values', matToSvgValues(rgbMatrix));
                filter.appendChild(rgbCM);
                last = 'r_rgb';
            }
        }

        if (moody) {
            const ct = document.createElementNS(svgNS, 'feComponentTransfer');
            ct.setAttribute('in', last);
            ct.setAttribute('result', 'r1');
            ct.appendChild(mkGamma('feFuncR', 0.96, 1.14, -0.015));
            ct.appendChild(mkGamma('feFuncG', 0.96, 1.13, -0.015));
            ct.appendChild(mkGamma('feFuncB', 0.97, 1.11, -0.015));
            filter.appendChild(ct);

            const sat = document.createElementNS(svgNS, 'feColorMatrix');
            sat.setAttribute('type', 'saturate');
            sat.setAttribute('values', '0.90');
            sat.setAttribute('in', 'r1');
            sat.setAttribute('result', 'r2');
            filter.appendChild(sat);

            last = 'r2';
        }

        if (teal) {
            const cool = document.createElementNS(svgNS, 'feColorMatrix');
            cool.setAttribute('type', 'matrix');
            cool.setAttribute('values',
                '0.96 0.02 0.00 0 0 ' +
                '0.02 1.02 0.02 0 0 ' +
                '0.00 0.04 1.06 0 0 ' +
                '0    0    0    1 0'
            );
            cool.setAttribute('in', last);
            cool.setAttribute('result', 'r3');
            filter.appendChild(cool);

            const warm = document.createElementNS(svgNS, 'feColorMatrix');
            warm.setAttribute('type', 'matrix');
            warm.setAttribute('values',
                '1.10 0.02 0.00 0 0 ' +
                '0.02 1.00 0.00 0 0 ' +
                '0.00 0.00 0.90 0 0 ' +
                '0    0    0    1 0'
            );
            warm.setAttribute('in', 'r3');
            warm.setAttribute('result', 'r4');
            filter.appendChild(warm);

            const pop = document.createElementNS(svgNS, 'feColorMatrix');
            pop.setAttribute('type', 'saturate');
            pop.setAttribute('values', '1.08');
            pop.setAttribute('in', 'r4');
            pop.setAttribute('result', 'r4b');
            filter.appendChild(pop);

            last = 'r4b';
        }

        if (vib) {
            const vSat = document.createElementNS(svgNS, 'feColorMatrix');
            vSat.setAttribute('type', 'saturate');
            vSat.setAttribute('values', '1.35');
            vSat.setAttribute('in', last);
            vSat.setAttribute('result', 'r5');
            filter.appendChild(vSat);
            last = 'r5';
        }

        if (prof && (prof === 'film' || prof === 'anime' || prof === 'gaming' || prof === 'eyecare')) {
            const pm = mkProfileMatrixCT(prof);
            if (pm) {
                pm.setAttribute('in', last);
                pm.setAttribute('result', 'r_prof');
                filter.appendChild(pm);
                last = 'r_prof';

                const sat = document.createElementNS(svgNS, 'feColorMatrix');
                sat.setAttribute('type', 'saturate');
                sat.setAttribute('in', last);
                sat.setAttribute('result', 'r_prof_sat');
                if (prof === 'film') sat.setAttribute('values', '1.08');
                if (prof === 'anime') sat.setAttribute('values', '1.18');
                if (prof === 'gaming') sat.setAttribute('values', '1.06');
                if (prof === 'eyecare') sat.setAttribute('values', '0.90');
                filter.appendChild(sat);
                last = 'r_prof_sat';
            }
        }

        if (prof === 'anime') {

            const blur = document.createElementNS(svgNS, 'feGaussianBlur');
            blur.setAttribute('stdDeviation', '0.8');
            blur.setAttribute('in', last);
            blur.setAttribute('result', 'anime_denoised');
            filter.appendChild(blur);

            const sobel = document.createElementNS(svgNS, 'feConvolveMatrix');
            sobel.setAttribute('order', '3');
            sobel.setAttribute('kernelMatrix',
                '-1 -2 -1 ' +
                ' 0  0  0 ' +
                ' 1  2  1'
            );
            sobel.setAttribute('divisor', '1');
            sobel.setAttribute('in', 'anime_denoised');
            sobel.setAttribute('result', 'anime_edges');
            filter.appendChild(sobel);

            const componentTransfer = document.createElementNS(svgNS, 'feComponentTransfer');
            componentTransfer.setAttribute('in', 'anime_edges');
            componentTransfer.setAttribute('result', 'anime_darkEdges');

            const funcR = document.createElementNS(svgNS, 'feFuncR');
            funcR.setAttribute('type', 'linear');
            funcR.setAttribute('slope', '2.2');
            funcR.setAttribute('intercept', '-0.3');
            componentTransfer.appendChild(funcR);

            const funcG = funcR.cloneNode();
            const funcB = funcR.cloneNode();
            componentTransfer.appendChild(funcG);
            componentTransfer.appendChild(funcB);
            filter.appendChild(componentTransfer);

            const threshold = document.createElementNS(svgNS, 'feComponentTransfer');
            threshold.setAttribute('in', 'anime_darkEdges');
            threshold.setAttribute('result', 'anime_threshold');

            const tFuncR = document.createElementNS(svgNS, 'feFuncR');
            tFuncR.setAttribute('type', 'linear');
            tFuncR.setAttribute('slope', '3');
            tFuncR.setAttribute('intercept', '-0.4');
            threshold.appendChild(tFuncR);

            const tFuncG = tFuncR.cloneNode();
            const tFuncB = tFuncR.cloneNode();
            threshold.appendChild(tFuncG);
            threshold.appendChild(tFuncB);
            filter.appendChild(threshold);

            const blend = document.createElementNS(svgNS, 'feComposite');
            blend.setAttribute('operator', 'arithmetic');
            blend.setAttribute('k1', '0');
            blend.setAttribute('k2', '1');
            blend.setAttribute('k3', '0.3');
            blend.setAttribute('k4', '0');
            blend.setAttribute('in', last);
            blend.setAttribute('in2', 'anime_threshold');
            blend.setAttribute('result', 'r_anime_lines');
            filter.appendChild(blend);

            last = 'r_anime_lines';
        }


        if (edgeVal > 0.0001) {
            const edgeStrength = Math.pow(clamp(edgeVal, 0.0, 1.0), 2.2);
            const edgeSigma = 0.30 + edgeStrength * 0.70;
            const preBlur = document.createElementNS(svgNS, 'feGaussianBlur');
            preBlur.setAttribute('stdDeviation', String(edgeSigma));
            preBlur.setAttribute('in', last);
            preBlur.setAttribute('result', 'r_edge_pre');
            filter.appendChild(preBlur);

            const sobelX = document.createElementNS(svgNS, 'feConvolveMatrix');
            sobelX.setAttribute('order', '3');
            sobelX.setAttribute('kernelMatrix', '-1 0 1 -2 0 2 -1 0 1');
            sobelX.setAttribute('divisor', '1');
            sobelX.setAttribute('bias', '0');
            sobelX.setAttribute('preserveAlpha', 'true');
            sobelX.setAttribute('in', 'r_edge_pre');
            sobelX.setAttribute('result', 'r_edge_sx');
            filter.appendChild(sobelX);

            const sobelY = document.createElementNS(svgNS, 'feConvolveMatrix');
            sobelY.setAttribute('order', '3');
            sobelY.setAttribute('kernelMatrix', '-1 -2 -1 0 0 0 1 2 1');
            sobelY.setAttribute('divisor', '1');
            sobelY.setAttribute('bias', '0');
            sobelY.setAttribute('preserveAlpha', 'true');
            sobelY.setAttribute('in', 'r_edge_pre');
            sobelY.setAttribute('result', 'r_edge_sy');
            filter.appendChild(sobelY);

            const edgeMix = document.createElementNS(svgNS, 'feBlend');
            edgeMix.setAttribute('mode', 'lighten');
            edgeMix.setAttribute('in', 'r_edge_sx');
            edgeMix.setAttribute('in2', 'r_edge_sy');
            edgeMix.setAttribute('result', 'r_edge_mix');
            filter.appendChild(edgeMix);

            const edgeMask = document.createElementNS(svgNS, 'feComponentTransfer');
            edgeMask.setAttribute('in', 'r_edge_mix');
            edgeMask.setAttribute('result', 'r_edge_mask');

            const edgeSlope = -(0.35 + edgeStrength * 3.65);
            const edgeIntercept = 1.0;
            const feR = document.createElementNS(svgNS, 'feFuncR');
            feR.setAttribute('type', 'linear');
            feR.setAttribute('slope', String(edgeSlope));
            feR.setAttribute('intercept', String(edgeIntercept));
            edgeMask.appendChild(feR);

            const feG = document.createElementNS(svgNS, 'feFuncG');
            feG.setAttribute('type', 'linear');
            feG.setAttribute('slope', String(edgeSlope));
            feG.setAttribute('intercept', String(edgeIntercept));
            edgeMask.appendChild(feG);

            const feB = document.createElementNS(svgNS, 'feFuncB');
            feB.setAttribute('type', 'linear');
            feB.setAttribute('slope', String(edgeSlope));
            feB.setAttribute('intercept', String(edgeIntercept));
            edgeMask.appendChild(feB);

            const feA = document.createElementNS(svgNS, 'feFuncA');
            feA.setAttribute('type', 'identity');
            edgeMask.appendChild(feA);
            filter.appendChild(edgeMask);

            const edgeComposite = document.createElementNS(svgNS, 'feBlend');
            edgeComposite.setAttribute('mode', 'multiply');
            edgeComposite.setAttribute('in', last);
            edgeComposite.setAttribute('in2', 'r_edge_mask');
            edgeComposite.setAttribute('result', 'r_edge_final');
            filter.appendChild(edgeComposite);
            last = 'r_edge_final';
        }

        const autoCM = document.createElementNS(svgNS, 'feColorMatrix');
        autoCM.setAttribute('type', 'matrix');
        autoCM.setAttribute('in', last);
        autoCM.setAttribute('result', 'r_auto');
        autoCM.setAttribute('data-gvf-auto', '1');
        autoCM.setAttribute('values', autoMatrixStr || matToSvgValues(matIdentity4x5()));
        filter.appendChild(autoCM);
        last = 'r_auto';

        // Inject enabled custom SVG codes into filter pipeline
        if (Array.isArray(customSvgCodes)) {
            customSvgCodes.filter(e => e && e.enabled).forEach((entry, idx) => {
                const nodes = parseCustomSvgCode(entry.code);
                if (!nodes || !nodes.length) return;
                nodes.forEach((node, ni) => {
                    const imported = document.importNode(node, true);
                    const resultId = 'r_cust_' + idx + '_' + ni;
                    if (!imported.getAttribute('in')) imported.setAttribute('in', last);
                    imported.setAttribute('result', resultId);
                    filter.appendChild(imported);
                    last = resultId;
                });
            });
        }

        const merge = document.createElementNS(svgNS, 'feMerge');
        const n1 = document.createElementNS(svgNS, 'feMergeNode');
        n1.setAttribute('in', last);
        merge.appendChild(n1);
        filter.appendChild(merge);

        svg.appendChild(filter);
    }

    function ensureSvgFilter(force = false) {
        const SL = Number(normSL().toFixed(1));
        const SR = Number(normSR().toFixed(1));
        const R = Number(getRadius().toFixed(1));
        const A = Number(getSharpenA().toFixed(3));
        const BS = Number(getBlurSigma().toFixed(3));
        const BL = Number(normBL().toFixed(1));
        const WL = Number(normWL().toFixed(1));
        const DN = Number(normDN().toFixed(1));
        const HDR = Number(normHDR().toFixed(2));
        const EDGE = Number(normEDGE().toFixed(2));
        const P = (profile || 'off');
        const CB = cbFilter;

        const LUTN = String(activeLutProfileKey || 'none');
        const uSig = [
            normU(u_contrast), normU(u_black), normU(u_white), normU(u_highlights), normU(u_shadows),
            normU(u_sat), normU(u_vib), normU(u_sharp), normU(u_gamma), normU(u_grain), normU(u_hue),
            normRGB(u_r_gain), normRGB(u_g_gain), normRGB(u_b_gain)
        ].map(x => Number(x).toFixed(1)).join(',');

        const customSig = customSvgCodes.filter(e => e && e.enabled).map(e => e.id + ':' + e.code).join('||');
        const want = `${SL}|${SR}|${R}|${A}|${BS}|${BL}|${WL}|${DN}|${EDGE}|${HDR}|${P}|U:${uSig}|CB:${CB}|LUT:${LUTN}|CSVG:${customSig}`;

        const existing = document.getElementById(SVG_ID);
        if (existing) {
            const has = existing.getAttribute('data-params') || '';
            if (has === want && !force) {
                updateAutoMatrixInSvg(autoMatrixStr);
                return;
            }
            if (has === want && force) {
                existing.remove();
            }

            if (!force) {
                updateAutoMatrixInSvg(autoMatrixStr);
                return;
            }

            existing.remove();
        }

        const svg = document.createElementNS(svgNS, 'svg');
        svg.id = SVG_ID;
        svg.setAttribute('data-params', want);
        svg.setAttribute('width', '0');
        svg.setAttribute('height', '0');
        svg.style.position = 'absolute';
        svg.style.left = '-9999px';
        svg.style.top = '-9999px';

        const blackOffset = blackToOffset(BL);
        const whiteAdj = whiteToHiAdj(WL);

        buildFilter(svg, 'gvf_s', { moody: false, teal: false, vib: false }, R, A, BS, blackOffset, whiteAdj, DN, EDGE, HDR, P);
        buildFilter(svg, 'gvf_sm', { moody: true, teal: false, vib: false }, R, A, BS, blackOffset, whiteAdj, DN, EDGE, HDR, P);
        buildFilter(svg, 'gvf_st', { moody: false, teal: true, vib: false }, R, A, BS, blackOffset, whiteAdj, DN, EDGE, HDR, P);
        buildFilter(svg, 'gvf_sv', { moody: false, teal: false, vib: true }, R, A, BS, blackOffset, whiteAdj, DN, EDGE, HDR, P);
        buildFilter(svg, 'gvf_smt', { moody: true, teal: true, vib: false }, R, A, BS, blackOffset, whiteAdj, DN, EDGE, HDR, P);
        buildFilter(svg, 'gvf_smv', { moody: true, teal: false, vib: true }, R, A, BS, blackOffset, whiteAdj, DN, EDGE, HDR, P);
        buildFilter(svg, 'gvf_stv', { moody: false, teal: true, vib: true }, R, A, BS, blackOffset, whiteAdj, DN, EDGE, HDR, P);
        buildFilter(svg, 'gvf_smtv', { moody: true, teal: true, vib: true }, R, A, BS, blackOffset, whiteAdj, DN, EDGE, HDR, P);

        (document.body || document.documentElement).appendChild(svg);

        updateAutoMatrixInSvg(autoMatrixStr);
    }

    function pickComboId() {
        const m = !!darkMoody;
        const t = !!tealOrange;
        const v = !!vibrantSat;

        if (m && t && v) return 'gvf_smtv';
        if (m && t && !v) return 'gvf_smt';
        if (m && !t && v) return 'gvf_smv';
        if (!m && t && v) return 'gvf_stv';
        if (m && !t && !v) return 'gvf_sm';
        if (!m && t && !v) return 'gvf_st';
        if (!m && !t && v) return 'gvf_sv';
        return 'gvf_s';
    }

    function profileToneCss() {
        if (profile === 'film') return ' brightness(1.01) contrast(1.08) saturate(1.08)';
        if (profile === 'anime') return ' brightness(1.03) contrast(1.10) saturate(1.16)';
        if (profile === 'gaming') return ' brightness(1.01) contrast(1.12) saturate(1.06)';
        if (profile === 'eyecare') return ' brightness(1.05) contrast(0.96) saturate(0.88) hue-rotate(-12deg)';
        return '';
    }

    function applyFilter(opts = {}) {
        if (renderMode === 'gpu') {
            applyGpuFilter();
            return;
        }

        let style = document.getElementById(STYLE_ID);

        const nothingOn =
            !enabled && !darkMoody && !tealOrange && !vibrantSat && normEDGE() === 0 && normHDR() === 0 && (profile === 'off') && !autoOn && cbFilter === 'none';

        if (nothingOn) {
            if (style) style.remove();
            scheduleOverlayUpdate();
            return;
        }

        const skipSvgIfPossible = !!opts.skipSvgIfPossible;
        const svgExists = !!document.getElementById(SVG_ID);

        if (!skipSvgIfPossible || !svgExists) {
            ensureSvgFilter(true);
        }

        if (!style) {
            style = document.createElement('style');
            style.id = STYLE_ID;
            document.head.appendChild(style);
        }

        const baseTone = enabled ? ' brightness(1.02) contrast(1.05) saturate(1.21)' : '';
        const profTone = profileToneCss();
        const userTone = userToneCss();

        const outlineCss = (PROFILE_VIDEO_OUTLINE && profile !== 'off')
            ? `outline: 2px solid ${(PROF[profile] || PROF.off).color} !important; outline-offset: -2px;`
            : `outline: none !important;`;

        style.textContent = `
      video {
        will-change: filter;
        transform: translateZ(0);
        filter: url("#${pickComboId()}")${baseTone}${profTone}${userTone} !important;
        ${outlineCss}
      }
    `;

        scheduleOverlayUpdate();
    }

    function getSelfCode() {
        try {
            if (document.currentScript && document.currentScript.textContent) {
                const t = document.currentScript.textContent.trim();
                if (t.length > 200) return t;
            }
        } catch (_) { }
        try {
            if (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.source) {
                return String(GM_info.script.source || '');
            }
        } catch (_) { }
        return null;
    }

    function injectIntoIframe(iframe, code) {
        try {
            const doc = iframe.contentDocument;
            const win = iframe.contentWindow;
            if (!doc || !win) return;
            if (win.__GLOBAL_VIDEO_FILTER__) return;
            if (!code) return;

            const s = doc.createElement('script');
            s.type = 'text/javascript';
            s.textContent = code;
            (doc.head || doc.documentElement).appendChild(s);
            s.remove();
        } catch (_) { }
    }

    function watchIframes() {
        const code = getSelfCode();
        if (!code) return;

        const scan = () => document.querySelectorAll('iframe').forEach(ifr => injectIntoIframe(ifr, code));
        scan();

        document.addEventListener('load', (e) => {
            const t = e.target;
            if (t && t.tagName && t.tagName.toLowerCase() === 'iframe') injectIntoIframe(t, code);
        }, true);

        new MutationObserver(scan).observe(document.documentElement, { childList: true, subtree: true });
    }

    let _globalSyncApplyTimer = null;
    function scheduleGlobalSyncApply(delay = 50) {
        if (_globalSyncApplyTimer) clearTimeout(_globalSyncApplyTimer);
        _globalSyncApplyTimer = setTimeout(() => {
            _globalSyncApplyTimer = null;
            try {
                setAutoOn(autoOn);
                if (renderMode === 'gpu') {
                    applyGpuFilter();
                } else {
                    regenerateSvgImmediately();
                }
                scheduleOverlayUpdate();
            } catch (e) {
                logW('Global sync apply failed:', e);
            }
        }, Math.max(0, Number(delay) || 0));
    }

    function listenGlobalSync() {
        const profileAutoSaveKeys = new Set([
            K.enabled,
            K.moody,
            K.teal,
            K.vib,
            K.SL,
            K.SR,
            K.BL,
            K.WL,
            K.DN,
            K.HDR,
            K.PROF,
            K.RENDER_MODE,
            K.AUTO_ON,
            K.AUTO_STRENGTH,
            K.AUTO_LOCK_WB,
            K.U_CONTRAST,
            K.U_BLACK,
            K.U_WHITE,
            K.U_HIGHLIGHTS,
            K.U_SHADOWS,
            K.U_SAT,
            K.U_VIB,
            K.U_SHARP,
            K.U_GAMMA,
            K.U_GRAIN,
            K.U_HUE,
            K.U_R_GAIN,
            K.U_G_GAIN,
            K.U_B_GAIN,
            K.CB_FILTER,
            K.LUT_ACTIVE_PROFILE
        ]);

        const sync = (changedKey) => {
            if (_isSwitchingUserProfile) return;
            if (isValueSyncSuppressed()) return;
            if (!_applyingRemoteProfileSync && (changedKey === K.USER_PROFILES || changedKey === K.USER_PROFILES_REV || changedKey === K.ACTIVE_USER_PROFILE)) {
                // Manual save only: ignore automatic user-profile sync events to prevent profile reverts.
                return;
            }
            if (_suspendSync) return;

            _inSync = true;
            try {
                enabled = !!gmGet(K.enabled, enabled);
                darkMoody = !!gmGet(K.moody, darkMoody);
                tealOrange = !!gmGet(K.teal, tealOrange);
                vibrantSat = !!gmGet(K.vib, vibrantSat);
                iconsShown = !!gmGet(K.icons, iconsShown);

                sl = Number(gmGet(K.SL, sl));
                sr = Number(gmGet(K.SR, sr));
                bl = Number(gmGet(K.BL, bl));
                wl = Number(gmGet(K.WL, wl));
                dn = Number(gmGet(K.DN, dn));
                hdr = Number(gmGet(K.HDR, hdr));

                profile = String(gmGet(K.PROF, profile)).toLowerCase();
                if (!['off', 'film', 'anime', 'gaming', 'eyecare', 'user'].includes(profile)) profile = 'off';

                renderMode = String(gmGet(K.RENDER_MODE, renderMode)).toLowerCase();
                if (!['svg', 'gpu'].includes(renderMode)) renderMode = 'svg';

                gradingHudShown = !!gmGet(K.G_HUD, gradingHudShown);
                ioHudShown = !!gmGet(K.I_HUD, ioHudShown);
                scopesHudShown = !!gmGet(K.S_HUD, scopesHudShown);

                u_contrast = Number(gmGet(K.U_CONTRAST, u_contrast));
                u_black = Number(gmGet(K.U_BLACK, u_black));
                u_white = Number(gmGet(K.U_WHITE, u_white));
                u_highlights = Number(gmGet(K.U_HIGHLIGHTS, u_highlights));
                u_shadows = Number(gmGet(K.U_SHADOWS, u_shadows));
                u_sat = Number(gmGet(K.U_SAT, u_sat));
                u_vib = Number(gmGet(K.U_VIB, u_vib));
                u_sharp = Number(gmGet(K.U_SHARP, u_sharp));
                u_gamma = Number(gmGet(K.U_GAMMA, u_gamma));
                u_grain = Number(gmGet(K.U_GRAIN, u_grain));
                u_hue = Number(gmGet(K.U_HUE, u_hue));

                u_r_gain = Number(gmGet(K.U_R_GAIN, u_r_gain));
                u_g_gain = Number(gmGet(K.U_G_GAIN, u_g_gain));
                u_b_gain = Number(gmGet(K.U_B_GAIN, u_b_gain));

                autoOn = !!gmGet(K.AUTO_ON, autoOn);
                notify = !!gmGet(K.NOTIFY, notify);
                autoStrength = clamp(Number(gmGet(K.AUTO_STRENGTH, autoStrength)), 0, 1);
                autoLockWB = !!gmGet(K.AUTO_LOCK_WB, autoLockWB);

                cbFilter = String(gmGet(K.CB_FILTER, cbFilter)).toLowerCase();
                if (!['none', 'protanopia', 'deuteranopia', 'tritanomaly'].includes(cbFilter)) cbFilter = 'none';

                // Reload custom SVG codes and refresh modal if open
                if (changedKey === K.CUSTOM_SVG_CODES) {
                    loadCustomSvgCodes();
                    regenerateSvgImmediately();
                    const modal = document.getElementById('gvf-custom-svg-modal');
                    if (modal && modal._gvfRenderList) modal._gvfRenderList();
                    const badge = document.getElementById('gvf-svg-codes-count');
                    if (badge) {
                        const ac = customSvgCodes.filter(e => e.enabled).length;
                        badge.textContent = customSvgCodes.length ? `${ac}/${customSvgCodes.length} active` : '';
                    }
                    _inSync = false;
                    return;
                }

                // Debug/Load settings from storage
                logs = !!gmGet(K.LOGS, logs);
                debug = !!gmGet(K.DEBUG, debug);
                LOG.on = logs;

                scheduleGlobalSyncApply(profileAutoSaveKeys.has(changedKey) ? 70 : 40);
            } finally {
                _inSync = false;
            }
        };

        Object.values(K).forEach(key => {
            try {
                GM_addValueChangeListener(key, function() {
                    sync(key);
                });
            } catch (_) { }
        });

        try {
            window.addEventListener('storage', function(ev) {
                const key = ev && ev.key ? String(ev.key) : '';
                if (!key) return;
                // User profile storage sync is intentionally disabled here.
                // Manual save only: do not auto-switch or auto-merge profiles across tabs.
            }, false);
        } catch (_) { }
    }

    function cycleProfile() {
        const order = ['off', 'film', 'anime', 'gaming', 'eyecare', 'user'];
        const cur = order.indexOf(profile);
        profile = order[(cur < 0 ? 0 : (cur + 1)) % order.length];
        gmSet(K.PROF, profile);
        log('Profile cycled:', profile);
        showProfileCycleNotification(profile);

        // Save current settings in active profile
        updateCurrentProfileSettings();

        if (renderMode === 'gpu') {
            applyGpuFilter();
        } else {
            regenerateSvgImmediately();
        }
        scheduleOverlayUpdate();
    }

    function toggleGradingHud() {
        gradingHudShown = !gradingHudShown;
        gmSet(K.G_HUD, gradingHudShown);
        logToggle('Grading HUD (Ctrl+Alt+G)', gradingHudShown);
        scheduleOverlayUpdate();
    }

    function toggleIOHud() {
        ioHudShown = !ioHudShown;
        gmSet(K.I_HUD, ioHudShown);
        logToggle('IO HUD (Ctrl+Alt+I)', ioHudShown);
        scheduleOverlayUpdate();
    }

    function toggleScopesHud() {
        scopesHudShown = !scopesHudShown;
        gmSet(K.S_HUD, scopesHudShown);
        logToggle('Scopes HUD (Ctrl+Alt+S)', scopesHudShown);
        scheduleOverlayUpdate();

        if (scopesHudShown) {
            startScopesLoop();
        } else {
            document.querySelectorAll('.gvf-scope-luma [data-index]').forEach(bar => {
                bar.style.height = '2px';
            });
            document.querySelectorAll('.gvf-scope-red [data-index]').forEach(bar => {
                bar.style.height = '2px';
            });
            document.querySelectorAll('.gvf-scope-green [data-index]').forEach(bar => {
                bar.style.height = '2px';
            });
            document.querySelectorAll('.gvf-scope-blue [data-index]').forEach(bar => {
                bar.style.height = '2px';
            });
            const satFill = document.querySelector('.gvf-scope-sat-fill');
            if (satFill) satFill.style.width = '0%';
            const satValue = document.querySelector('.gvf-scope-sat-value');
            if (satValue) satValue.textContent = '0.00';

            const avgYEl = document.querySelector('.gvf-scope-avg-y');
            if (avgYEl) avgYEl.textContent = 'Y: 0.00';
            const avgRGBEl = document.querySelector('.gvf-scope-avg-rgb');
            if (avgRGBEl) avgRGBEl.textContent = 'RGB: 0.00';
            const avgSatEl = document.querySelector('.gvf-scope-avg-sat');
            if (avgSatEl) avgSatEl.textContent = 'Sat: 0.00';
        }
    }

    // -------------------------
    // Auto-Import LUT Profiles from URL (runs once when no LUT profiles are stored)
    // -------------------------
    async function autoImportLutProfilesFromUrl(url) {
        try {
            if (Array.isArray(lutProfiles) && lutProfiles.length > 0) {
                log('autoImportLutProfilesFromUrl: LUT profiles already present, skipping auto-import.');
                return;
            }
            log('autoImportLutProfilesFromUrl: No LUT profiles found – fetching from', url);
            const rawUrl = 'https://raw.githubusercontent.com/nextscript/Globale-Video-Filter-Overlay/main/LUTsProfiles_v2.0.zip';
            const candidates = [
                rawUrl,
                'https://api.allorigins.win/raw?url=' + encodeURIComponent(rawUrl),
                'https://corsproxy.io/?' + encodeURIComponent(rawUrl),
                'https://proxy.cors.sh/' + rawUrl,
            ];
            let response = null;
            for (const c of candidates) {
                try { const r = await fetch(c); if (r.ok) { response = r; break; } } catch (_) { }
            }
            if (!response) { logW('autoImportLutProfilesFromUrl: All fetch attempts failed.'); return; }
            if (!response.ok) {
                logW('autoImportLutProfilesFromUrl: Fetch failed:', response.status, response.statusText);
                return;
            }
            const blob = await response.blob();
            const fileName = url.split('/').pop() || 'LUTsProfiles.zip';
            const file = new File([blob], fileName, { type: 'application/zip' });
            const result = await importLutProfilesFromZipOrJsonFile(file);
            if (result && result.ok) {
                log('autoImportLutProfilesFromUrl:', result.msg);
                try { showValueNotification('LUT Import', result.msg, '#4cff6a'); } catch (_) { }
            } else {
                logW('autoImportLutProfilesFromUrl: Import failed –', result && result.msg);
            }
        } catch (e) {
            logW('autoImportLutProfilesFromUrl error:', e);
        }
    }

    function init() {
        const isFirefoxBrowser = isFirefox();
        if (activeUserProfile && activeUserProfile.settings && typeof activeUserProfile.settings === 'object') {
            try {
                applyUserProfileSettings(activeUserProfile.settings);
            } catch (e) {
                logW('Failed to restore active user profile on init:', e);
            }
        }

        sl = normSL(); gmSet(K.SL, sl);
        sr = normSR(); gmSet(K.SR, sr);
        bl = normBL(); gmSet(K.BL, bl);
        wl = normWL(); gmSet(K.WL, wl);
        dn = normDN(); gmSet(K.DN, dn);
        hdr = normHDR(); gmSet(K.HDR, hdr);
        if (hdr !== 0) gmSet(K.HDR_LAST, hdr);

        u_contrast = normU(u_contrast); gmSet(K.U_CONTRAST, u_contrast);
        u_black = normU(u_black); gmSet(K.U_BLACK, u_black);
        u_white = normU(u_white); gmSet(K.U_WHITE, u_white);
        u_highlights = normU(u_highlights); gmSet(K.U_HIGHLIGHTS, u_highlights);
        u_shadows = normU(u_shadows); gmSet(K.U_SHADOWS, u_shadows);
        u_sat = normU(u_sat); gmSet(K.U_SAT, u_sat);
        u_vib = normU(u_vib); gmSet(K.U_VIB, u_vib);
        u_sharp = normU(u_sharp); gmSet(K.U_SHARP, u_sharp);
        u_gamma = normU(u_gamma); gmSet(K.U_GAMMA, u_gamma);
        u_grain = normU(u_grain); gmSet(K.U_GRAIN, u_grain);
        u_hue = normU(u_hue); gmSet(K.U_HUE, u_hue);

        u_r_gain = normRGB(u_r_gain); gmSet(K.U_R_GAIN, u_r_gain);
        u_g_gain = normRGB(u_g_gain); gmSet(K.U_G_GAIN, u_g_gain);
        u_b_gain = normRGB(u_b_gain); gmSet(K.U_B_GAIN, u_b_gain);

        gmSet(K.G_HUD, gradingHudShown);
        gmSet(K.I_HUD, ioHudShown);
        gmSet(K.S_HUD, scopesHudShown);

        if (!['off', 'film', 'anime', 'gaming', 'eyecare', 'user'].includes(profile)) profile = 'off';
        gmSet(K.PROF, profile);

        if (!['svg', 'gpu'].includes(renderMode)) renderMode = 'svg';
        gmSet(K.RENDER_MODE, renderMode);

        gmSet(K.AUTO_ON, autoOn);
        gmSet(K.AUTO_STRENGTH, autoStrength);
        gmSet(K.AUTO_LOCK_WB, autoLockWB);

        gmSet(K.CB_FILTER, cbFilter);

        gmSet(K.LOGS, logs);
        gmSet(K.DEBUG, debug);

        // Manual save only: do not auto-save user profiles during init.

        setAutoDotState(autoOn ? (debug ? 'idle' : 'off') : 'off');

        autoMatrixStr = matToSvgValues(autoOn ? buildAutoMatrixValues() : matIdentity4x5());
        _autoLastMatrixStr = autoMatrixStr;
        AUTO.lastGoodMatrixStr = autoMatrixStr;
        AUTO.lastAppliedMs = 0;

        loadCustomSvgCodes();

        if (renderMode === 'gpu') {
            applyGpuFilter();
        } else {
            regenerateSvgImmediately();
        }

        listenGlobalSync();
        watchIframes();
        primeAutoOnVideoActivity();

        ensureAutoLoop();
        setAutoOn(autoOn);

        if (scopesHudShown) startScopesLoop();

        // Initialize config menu (but do not display)
        createConfigMenu();

        log('Init complete with WebGL2 Canvas Pipeline! RGB Gain now works correctly!', {
            enabled, darkMoody, tealOrange, vibrantSat, iconsShown,
            hdr: normHDR(), profile, renderMode,
            autoOn, autoStrength: Number(autoStrength.toFixed(2)), autoLockWB,
            scopesHudShown,
            rgb: { r_gain: u_r_gain, g_gain: u_g_gain, b_gain: u_b_gain },
            adaptiveFps: { min: ADAPTIVE_FPS.MIN, max: ADAPTIVE_FPS.MAX, current: ADAPTIVE_FPS.current },
            motionThresh: AUTO.motionThresh,
            motionMinFrames: AUTO.motionMinFrames,
            statsAlpha: AUTO.statsAlpha,
            gpuPipeline: renderMode === 'gpu',
            branchlessShader: true,
            debug: debug,
            logs: logs,
            colorBlindnessFilter: cbFilter,
            isFirefox: isFirefoxBrowser,
            bugfixes: 'REC.stopRequested evaluated, AUTO.blink reset, null check in updateAutoMatrixInSvg',
            userProfiles: userProfiles.length,
            activeProfile: activeUserProfile?.name,
            newFeatures: 'Shift+Q profile cycling, 3-second on-screen notification, real EDG slider with dark-edge Sobel detection'
        });

        document.addEventListener('keydown', (e) => {
            const tag = (e.target && e.target.tagName || '').toLowerCase();
            if (tag === 'input' || tag === 'textarea' || e.isComposing) return;

            const k = (e.key || '').toLowerCase();

            // NEW: Shift+Q for profile cycling
            if (e.shiftKey && !e.ctrlKey && !e.altKey && k === PROFILE_CYCLE_KEY) {
                e.preventDefault();
                log('Shift+Q pressed - cycling to next profile');
                cycleToNextProfile();
                return;
            }

            if (e.ctrlKey && e.altKey && !e.shiftKey && k === SCOPES_KEY) {
                e.preventDefault();
                toggleScopesHud();
                return;
            }

            if (e.ctrlKey && e.altKey && !e.shiftKey && k === IO_HUD_KEY) {
                e.preventDefault();
                toggleIOHud();
                return;
            }

            if (e.ctrlKey && e.altKey && !e.shiftKey && k === GRADE_HUD_KEY) {
                e.preventDefault();
                toggleGradingHud();
                return;
            }

            if (e.ctrlKey && e.altKey && !e.shiftKey && k === GPU_MODE_KEY) {
                e.preventDefault();
                toggleRenderMode();
                return;
            }

            if (e.ctrlKey && e.altKey && !e.shiftKey && k === PROF_TOGGLE_KEY) {
                e.preventDefault();
                cycleProfile();
                return;
            }

            if (e.ctrlKey && e.altKey && !e.shiftKey && k === HDR_TOGGLE_KEY) {
                e.preventDefault();
                const cur = normHDR();
                if (cur === 0) {
                    const last = Number(gmGet(K.HDR_LAST, 0.3));
                    hdr = clamp(last || 1.2, -1.0, 2.0);
                    logToggle('HDR (Ctrl+Alt+P)', true, `value=${normHDR().toFixed(2)}`);
                    showValueNotification('HDR', `Enabled (${normHDR().toFixed(2)})`, '#4cff6a');
                } else {
                    gmSet(K.HDR_LAST, cur);
                    hdr = 0;
                    logToggle('HDR (Ctrl+Alt+P)', false);
                    showToggleNotification('HDR', false);
                }
                gmSet(K.HDR, normHDR());

                // Save current settings in active profile
                updateCurrentProfileSettings();

                if (renderMode === 'gpu') {
                    applyGpuFilter();
                } else {
                    regenerateSvgImmediately();
                }
                return;
            }

            if (e.ctrlKey && e.altKey && !e.shiftKey && k === AUTO_KEY) {
                e.preventDefault();
                setAutoOn(!autoOn);
                return;
            }

            if (!(e.ctrlKey && e.altKey) || e.shiftKey) return;

            if (k === HK.base) {
                enabled = !enabled; gmSet(K.enabled, enabled); e.preventDefault(); logToggle('Base (Ctrl+Alt+B)', enabled); showToggleNotification('Base Tone Chain', enabled);
                updateCurrentProfileSettings();
                if (renderMode === 'gpu') applyGpuFilter(); else regenerateSvgImmediately(); return;
            }
            if (k === HK.moody) {
                darkMoody = !darkMoody; gmSet(K.moody, darkMoody); e.preventDefault(); logToggle('Dark&Moody (Ctrl+Alt+D)', darkMoody); showToggleNotification('Dark & Moody', darkMoody);
                updateCurrentProfileSettings();
                if (renderMode === 'gpu') applyGpuFilter(); else regenerateSvgImmediately(); return;
            }
            if (k === HK.teal) {
                tealOrange = !tealOrange; gmSet(K.teal, tealOrange); e.preventDefault(); logToggle('Teal&Orange (Ctrl+Alt+O)', tealOrange); showToggleNotification('Teal & Orange', tealOrange);
                updateCurrentProfileSettings();
                if (renderMode === 'gpu') applyGpuFilter(); else regenerateSvgImmediately(); return;
            }
            if (k === HK.vib) {
                vibrantSat = !vibrantSat; gmSet(K.vib, vibrantSat); e.preventDefault(); logToggle('Vibrant (Ctrl+Alt+V)', vibrantSat); showToggleNotification('Vibrant & Saturated', vibrantSat);
                updateCurrentProfileSettings();
                if (renderMode === 'gpu') applyGpuFilter(); else regenerateSvgImmediately(); return;
            }
            if (k === HK.icons) {
                iconsShown = !iconsShown; gmSet(K.icons, iconsShown); e.preventDefault(); logToggle('Overlay Icons (Ctrl+Alt+H)', iconsShown); scheduleOverlayUpdate(); return;
            }
        });

        window.addEventListener('scroll', scheduleOverlayUpdate, { passive: true });
        window.addEventListener('resize', scheduleOverlayUpdate, { passive: true });

        document.addEventListener('fullscreenchange', onFsChange);
        document.addEventListener('webkitfullscreenchange', onFsChange);

        new MutationObserver(() => {
            if (!document.getElementById(SVG_ID) && renderMode === 'svg') {
                regenerateSvgImmediately();
            }
            scheduleOverlayUpdate();
        }).observe(document.documentElement, { childList: true, subtree: true });

        scheduleOverlayUpdate();
    }

    document.readyState === 'loading'
        ? document.addEventListener('DOMContentLoaded', init, { once: true })
        : init();


})();