Video Barpic Maker

A simple script to create video barpics.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Video Barpic Maker
// @name:zh-CN   视频字幕截图制作工具
// @namespace    ckylin-script-video-barpic-maker
// @version      0.4.2
// @description  A simple script to create video barpics.
// @description:zh-CN 一个可以制作视频字幕截图的工具。
// @author       CKylinMC
// @match        https://*/*
// @grant        unsafeWindow
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_registerMenuCommand
// @license      Apache-2.0
// @run-at       document-end
// @require https://update.greasyfork.org/scripts/564901/1754426/CKUI.js
// ==/UserScript==

if (typeof unsafeWindow === 'undefined' || !unsafeWindow) {
    window.unsafeWindow = window;
}

(function (unsafeWindow, document) {
    if (typeof (GM_addStyle) === 'undefined') {
        unsafeWindow.GM_addStyle = function (css) {
            const style = document.createElement('style');
            style.textContent = css;
            document.head.appendChild(style);
        }
    }
    const logger = {
        log(...args) {
            console.log('[VideoBarpicMaker]', ...args);
        },
        error(...args) {
            console.error('[VideoBarpicMaker]', ...args);
        },
        warn(...args) {
            console.warn('[VideoBarpicMaker]', ...args);
        },
    }

    class Utils{
        static wait(ms = 0) {
            return new Promise(resolve => setTimeout(resolve, ms));
        }
        static $(selector, root = document) {
            return root.querySelector(selector);
        }
        static $all(selector, root = document) {
            return Array.from(root.querySelectorAll(selector));
        }
        static $child(parent, selector) {
            if (typeof parent === 'string') {
                return document.querySelector(parent+' '+selector);
            }
            return parent.querySelector(selector);
        }
        static $childAll(parent, selector) {
            if (typeof parent === 'string') {
                return Array.from(document.querySelectorAll(parent+' '+selector));
            }
            return Array.from(parent.querySelectorAll(selector));
        }
        static removeTailingSlash(str) {
            return str.replace(/\/+$/, '');
        }
        static fixUrlProtocol(url) {
            if (url.startsWith('http://') || url.startsWith('https://')) {
                return url;
            } else if (url.startsWith('//')) {
                return unsafeWindow.location.protocol + url;
            } else if (url.startsWith('data:')) {
                return url;
            } else if (url.startsWith('/')) {
                return unsafeWindow.location.origin + url;
            } else {
                return unsafeWindow.location.origin + Utils.removeTailingSlash(unsafeWindow.location.pathname) + '/' + url;
            }
        }
        static waitForElementFirstAppearForever(selector, root = document) {
            return new Promise(resolve => {
                const element = root.querySelector(selector);
                if (element) {
                    resolve(element);
                    return;
                }
                const observer = new MutationObserver(mutations => {
                    for (const mutation of mutations) {
                        for (const node of mutation.addedNodes) {
                            if (!(node instanceof HTMLElement)) continue;
                            const el = node.matches(selector)
                                ? node
                                : node.querySelector(selector);
                            if (el) {
                                resolve(el);
                                observer.disconnect();
                                return;
                            }
                        }
                    }
                });
                observer.observe(root, {
                    childList: true,
                    subtree: true
                });
            });
        }
        static waitForElementFirstAppearForeverWithTimeout(selector, root = document, timeout = 5000) {
            return new Promise(resolve => {
                const element = root.querySelector(selector);
                if (element) {
                    resolve(element);
                    return;
                }
                let done = false;
                const observer = new MutationObserver(mutations => {
                    if (done) return;
                    for (const mutation of mutations) {
                        for (const node of mutation.addedNodes) {
                            if (!(node instanceof HTMLElement)) continue;
                            const el = node.matches(selector)
                                ? node
                                : node.querySelector(selector);
                            if (el) {
                                done = true;
                                resolve(el);
                                observer.disconnect();
                                return;
                            }
                        }
                    }
                });
                observer.observe(root, {
                    childList: true,
                    subtree: true
                });
                if (timeout > 0) {
                    setTimeout(() => {
                        if (done) return;
                        done = true;
                        observer.disconnect();
                        resolve(null);
                    }, timeout);
                }
            });
        }
        static registerOnElementAttrChange(element, attr, callback) {
            const observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    if (mutation.type === 'attributes' && mutation.attributeName === attr) {
                        callback(mutation);
                    }
                });
            });
            observer.observe(element, { attributes: true });
            return observer;
        }
        static registerOnElementContentChange(element, callback) {
            const observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    if (mutation.type === 'characterData') {
                        callback(mutation);
                    }
                });
            });
            observer.observe(element, { characterData: true, subtree: true });
            return observer;
        }
        static registerOnceElementRemoved(element, callback, root = null) {
            if (!element) return null;
            if (!element.isConnected) {
                callback?.(element);
                return null;
            }
            const parent = root || element.parentNode || element.getRootNode?.();
            if (!parent) {
                callback?.(element);
                return null;
            }
            let done = false;
            const observer = new MutationObserver(mutations => {
                if (done) return;
                
                if (!element.isConnected) {
                    done = true;
                    observer.disconnect();
                    callback?.(element);
                    return;
                }
            });
            observer.observe(parent, { childList: true });
            return observer;
        }
        static formatDate(timestamp) {
            return (Intl.DateTimeFormat('zh-CN', {
                year: 'numeric',
                month: '2-digit',
                day: '2-digit',
                hour: '2-digit',
                minute: '2-digit',
                hour12: false,
            }).format(new Date(+timestamp))).replace(/\//g, '-').replace(',', '');
        }
        static daysBefore(timestamp) {
            const target = new Date(+timestamp);
            const now = Date.now();
            const diff = now - target.getTime();
            return Math.floor(diff / (1000 * 60 * 60 * 24));
        }
        static download(filename, text) {
            const element = document.createElement('a');
            element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
            element.setAttribute('download', filename);
            element.style.display = 'none';
            document.body.appendChild(element);
            element.click();
            document.body.removeChild(element);
        }
        static downloadBlob(filename, blob) {
            const url = URL.createObjectURL(blob);
            const element = document.createElement('a');
            element.setAttribute('href', url);
            element.setAttribute('download', filename);
            element.style.display = 'none';
            document.body.appendChild(element);
            element.click();
            document.body.removeChild(element);
            URL.revokeObjectURL(url);
        }
        static get ui() {
            return unsafeWindow.ckui;
        }
    }
    const Icons = {
        video: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z"/></g></svg>',
        capture: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19 19H5V5h14v14z"/><path d="M3 3h18v18H3z" opacity="0.3"/></svg>',
        captureDown: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 12h18" opacity="0.3"/><circle cx="12" cy="16" r="2"/></svg>',
        captureUp: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 12h18" opacity="0.3"/><circle cx="12" cy="8" r="2"/></svg>',
        settings: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M11 10.27L7 3.34m4 10.39l-4 6.93M12 22v-2m0-18v2m2 8h8m-5 8.66l-1-1.73m1-15.59l-1 1.73M2 12h2m16.66 5l-1.73-1m1.73-9l-1.73 1M3.34 17l1.73-1M3.34 7l1.73 1"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="12" r="8"/></g></svg>',
        copy: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M16 4h2a2 2 0 0 1 2 2v4m1 4H11"/><path d="m15 10l-4 4l4 4"/></g></svg>',
        save: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"/><path d="M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7M7 3v4a1 1 0 0 0 1 1h7"/></g></svg>',
        trash: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>',
        undo: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M9 14L4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11"/></g></svg>',
        redo: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m15 14l5-5l-5-5"/><path d="M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13"/></g></svg>',
        image: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15l-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></g></svg>',
    };
    class SettingsManager {
        constructor() {
            this.defaults = {
                captureMode: 'adaptive', // 'fixed' or 'adaptive'
                fixedWidth: 1280,
                minWidth: 640,
                maxWidth: 1920,
                topRange: 50,
                topRangeUnit: 'percent', // 'percent' or 'pixel'
                bottomRange: 50,
                bottomRangeUnit: 'percent',
                previewImageWidth: 260, 
                useLayerCapture: false, 
                manualOffsetLeft: 0, 
                manualOffsetTop: 0, 
                enableFloatButton: true, 
                showImageInfo: false,
                saveFormat: 'jpeg', // 'png', 'jpeg', 'webp'
                saveQuality: 0.75, // 0.0 - 1.0
                enabled: true,
                content: '使用 Barpic Maker 制作',
                fontSize: 16,
                textColor: '#333333',
                textAlign: 'right',
                backgroundColor: '#f5f5f5',
                padding: 20,
                containerHeight: 0,
                containerWidth: 0,
                watermarkApplyMode: 'always' // 'copy', 'save', 'always'
            };
            this.settings = this.load();
        }

        load() {
            try {
                const saved = GM_getValue('vbm_settings', null);
                return saved ? { ...this.defaults, ...JSON.parse(saved) } : { ...this.defaults };
            } catch (e) {
                logger.error('Failed to load settings:', e);
                return { ...this.defaults };
            }
        }

        save() {
            try {
                GM_setValue('vbm_settings', JSON.stringify(this.settings));
            } catch (e) {
                logger.error('Failed to save settings:', e);
            }
        }

        get(key) {
            return this.settings[key];
        }

        set(key, value) {
            this.settings[key] = value;
            this.save();
        }
    }
    class CanvasManager {
        constructor() {
            this.canvas = null;
            this.ctx = null;
            this.history = [];
            this.historyIndex = -1;
            this.firstWidth = null;
        }

        init(width, height) {
            if (!this.canvas) {
                this.canvas = document.createElement('canvas');
                this.ctx = this.canvas.getContext('2d');
            }
            this.canvas.width = width;
            this.canvas.height = height;
            this.firstWidth = width;
        }

        appendImage(imageData, targetWidth) {
            if (!this.canvas) {
                this.init(targetWidth, imageData.height);
                this.ctx.putImageData(imageData, 0, 0);
            } else {
                const oldHeight = this.canvas.height;
                const newHeight = oldHeight + imageData.height;
                const tempCanvas = document.createElement('canvas');
                const tempCtx = tempCanvas.getContext('2d');
                tempCanvas.width = imageData.width;
                tempCanvas.height = imageData.height;
                tempCtx.putImageData(imageData, 0, 0);
                const oldImageData = this.ctx.getImageData(0, 0, this.canvas.width, oldHeight);
                this.canvas.height = newHeight;
                this.ctx.putImageData(oldImageData, 0, 0);
                this.ctx.drawImage(tempCanvas, 0, oldHeight, this.canvas.width, imageData.height);
            }
            this.saveState();
        }

        saveState() {
            if (this.historyIndex < this.history.length - 1) {
                this.history = this.history.slice(0, this.historyIndex + 1);
            }
            const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
            this.history.push({
                width: this.canvas.width,
                height: this.canvas.height,
                data: imageData
            });
            this.historyIndex++;
            if (this.history.length > 20) {
                this.history.shift();
                this.historyIndex--;
            }
        }

        undo() {
            if (this.historyIndex > 0) {
                this.historyIndex--;
                const state = this.history[this.historyIndex];
                this.canvas.width = state.width;
                this.canvas.height = state.height;
                this.ctx.putImageData(state.data, 0, 0);
                return true;
            }
            return false;
        }

        redo() {
            if (this.historyIndex < this.history.length - 1) {
                this.historyIndex++;
                const state = this.history[this.historyIndex];
                this.canvas.width = state.width;
                this.canvas.height = state.height;
                this.ctx.putImageData(state.data, 0, 0);
                return true;
            }
            return false;
        }

        canUndo() {
            return this.historyIndex > 0;
        }

        canRedo() {
            return this.historyIndex < this.history.length - 1;
        }

        clear() {
            this.canvas = null;
            this.ctx = null;
            this.history = [];
            this.historyIndex = -1;
            this.firstWidth = null;
        }

        toBlob(format = 'png', quality = 0.95) {
            return new Promise(resolve => {
                const mimeType = `image/${format}`;
                this.canvas.toBlob(resolve, mimeType, quality);
            });
        }

        toDataURL(format = 'png', quality = 0.95) {
            const mimeType = `image/${format}`;
            return this.canvas.toDataURL(mimeType, quality);
        }

        async calculateSize(format = 'png', quality = 0.95) {
            if (!this.canvas) return 0;
            const blob = await this.toBlob(format, quality);
            return blob ? blob.size : 0;
        }

        getImageInfo() {
            if (!this.canvas) return null;
            return {
                width: this.canvas.width,
                height: this.canvas.height
            };
        }
    }
    class VideoBarpicMaker {
        constructor() {
            this.settings = new SettingsManager();
            this.canvas = new CanvasManager();
            this.toolbarWindow = null;
            this.toolbarContainer = null;
            this.previewWindow = null;
            this.previewContainer = null;
            this.selectedVideo = null;
            this.isSelectingVideo = false;
            this.highlightOverlay = null;
            this.rangeOverlay = null;
            this.settingsExpanded = false;
            this.displayMediaStream = null; 
            this.infoExpanded = false;
            this.imageInfo = { memorySize: 0, copySize: 0, saveSize: 0, width: 0, height: 0 };
            this.previewDebounceTimer = null;
        }

        init() {
            logger.log('Initializing Video Barpic Maker...');
            GM_registerMenuCommand('📷 打开视频截图工具', () => this.showToolbar());
            if (this.settings.get('enableFloatButton')) {
                this.initFloatButton();
            }
        }
        
        async initFloatButton() {
            if(document.getElementById('CKVIDBARPIC-floatbtn')) return;
            const videoElement = await Utils.waitForElementFirstAppearForeverWithTimeout('video', document, 10000);
            if (!videoElement) {
                logger.log('No video element found within 10 seconds, float button will not be shown');
                return;
            }
            
            logger.log('Video element detected, showing float button');
            
            GM_addStyle(`
            #CKVIDBARPIC-floatbtn{
                display: flex;
                justify-content: center;
                align-items: center;
                box-sizing: border-box;
                z-index: 9999;
                position: fixed;
                left: -15px;
                width: 30px;
                height: 30px;
                background: black;
                opacity: 0.8;
                color: white;
                cursor: pointer;
                border-radius: 50%;
                text-align: right;
                line-height: 24px;
                border: solid 3px #00000000;
                transition: opacity .3s 1s, background .3s, color .3s, left .3s, border .3s;
                top: 120px;
                top: 30vh;
            }
            #CKVIDBARPIC-floatbtn::after,#CKVIDBARPIC-floatbtn::before{
                z-index: 9990;
                content: "视频截图工具";
                pointer-events: none;
                position: fixed;
                left: -20px;
                height: 30px;
                background: black;
                opacity: 0;
                color: white;
                cursor: pointer;
                border-radius: 8px;
                padding: 0 12px;
                text-align: right;
                line-height: 30px;
                transition: all .3s;
                top: 123px;
                top: 30vh;
            }
                
            #CKVIDBARPIC-floatbtn::after{
                content: "← 视频截图工具";
                /*animation: CKVIDBARPIC-tipsOut forwards 5s 3.5s;*/
            }
                
            #CKVIDBARPIC-floatbtn:hover::before{
                left: 30px;
                opacity: 1;
            }
            #CKVIDBARPIC-floatbtn:hover{
                border: solid 3px black;
                transition: opacity .3s 0s, background .3s, color .3s, left .3s, border .3s;
                background: white;
                color: black;
                opacity: 1;
                left: -5px;
            }
            #CKVIDBARPIC-floatbtn.hide{
                left: -40px;
            }
            @keyframes CKVIDBARPIC-tipsOut{
                5%,95%{
                    opacity: 1;
                    left: 20px;
                }
                0%,100%{
                    left: -20px;
                    opacity: 0;
                }
            }
            `,);

            const toggle = document.createElement("div");
            toggle.id = "CKVIDBARPIC-floatbtn";
            toggle.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: inline-block;"><circle cx="12" cy="12" r="3"/><path d="M19 19H5V5h14v14z"/><path d="M3 3h18v18H3z" opacity="0.3"/></svg>`;
            toggle.onclick = () => this.showToolbar();
            document.body.appendChild(toggle);
        }
        checkLayerCaptureSupport() {
            return !!(navigator.mediaDevices && 
                     navigator.mediaDevices.getDisplayMedia && 
                     window.ImageCapture);
        }
        detectBrowserUIOffset() {
            const dpr = window.devicePixelRatio || 1;
            const manualLeft = this.settings.get('manualOffsetLeft') || 0;
            const manualTop = this.settings.get('manualOffsetTop') || 0;
            
            if (manualLeft !== 0 || manualTop !== 0) {
                logger.log('Using manual offset:', { left: manualLeft, top: manualTop });
                return {
                    left: manualLeft,
                    top: manualTop,
                    hasSignificantOffset: true,
                    isManual: true
                };
            }
            const widthDiff = window.outerWidth - window.innerWidth;
            const heightDiff = window.outerHeight - window.innerHeight;
            const leftOffset = Math.max(0, widthDiff);
            const topOffset = Math.max(0, heightDiff - 100); // Subtract typical title bar
            
            const hasSignificantOffset = leftOffset > 10 || topOffset > 50;
            
            logger.log('Browser UI offset detected:', {
                widthDiff,
                heightDiff,
                leftOffset,
                topOffset,
                hasSignificantOffset,
                dpr
            });
            
            return {
                left: leftOffset,
                top: topOffset,
                hasSignificantOffset,
                isManual: false
            };
        }

        showToolbar() {
            if (this.toolbarWindow) {
                return;
            }

            this.toolbarContainer = this.createToolbar();
            this.toolbarWindow = Utils.ui.floatWindow({
                title: '视频截图工具',
                content: this.toolbarContainer,
                width: "500px",
                position: { x: 100, y: 100 },
                shadow: true,
                onClose: () => {
                    this.cleanup();
                    this.toolbarWindow = null;
                    this.toolbarContainer = null;
                }
            });
            this.toolbarWindow.show();
            logger.log('Toolbar shown');
        }

        createToolbar() {
            const container = document.createElement('div');
            container.style.cssText = 'display: flex; flex-direction: column; gap: 12px; min-width: 400px';
            const videoSection = document.createElement('div');
            videoSection.innerHTML = `
                <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
                    <button class="ckui-btn ckui-btn-primary" id="vbm-select-video" style="flex: 1;">
                        ${Icons.video}
                        <span style="margin-left: 6px;">选择视频</span>
                    </button>
                    <div id="vbm-video-status" style="flex: 1; font-size: 12px; color: var(--ckui-text-secondary);">
                        未选择视频
                    </div>
                </div>
            `;
            container.appendChild(videoSection);
            const captureSection = document.createElement('div');
            captureSection.id = 'vbm-capture-section';
            captureSection.style.display = 'none';
            captureSection.innerHTML = `
                <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; margin-bottom: 8px;">
                    <button class="ckui-btn" id="vbm-capture-full" style="display: flex; flex-direction: column; align-items: center; height: auto; padding: 10px;">
                        ${Icons.capture}
                        <span style="font-size: 11px; margin-top: 4px;">全图</span>
                    </button>
                    <button class="ckui-btn" id="vbm-capture-bottom" style="display: flex; flex-direction: column; align-items: center; height: auto; padding: 10px;">
                        ${Icons.captureDown}
                        <span style="font-size: 11px; margin-top: 4px;">下部分</span>
                    </button>
                    <button class="ckui-btn" id="vbm-capture-top" style="display: flex; flex-direction: column; align-items: center; height: auto; padding: 10px;">
                        ${Icons.captureUp}
                        <span style="font-size: 11px; margin-top: 4px;">上部分</span>
                    </button>
                </div>
            `;
            container.appendChild(captureSection);
            const settingsBtn = document.createElement('button');
            settingsBtn.className = 'ckui-btn';
            settingsBtn.id = 'vbm-settings-toggle';
            settingsBtn.innerHTML = `${Icons.settings} <span style="margin-left: 6px;">设置</span>`;
            settingsBtn.style.width = '100%';
            container.appendChild(settingsBtn);
            const settingsPanel = document.createElement('div');
            settingsPanel.id = 'vbm-settings-panel';
            settingsPanel.style.display = 'none';
            settingsPanel.appendChild(this.createSettingsPanel());
            container.appendChild(settingsPanel);
            const divider = document.createElement('div');
            divider.className = 'ckui-divider';
            container.appendChild(divider);
            const actionsSection = document.createElement('div');
            actionsSection.id = 'vbm-actions-section';
            actionsSection.style.display = 'none';
            actionsSection.innerHTML = `
                <div style="display: flex; gap: 8px; margin-bottom: 8px; align-items: center;">
                    <button class="ckui-btn" id="vbm-undo" disabled title="撤销">
                        ${Icons.undo}
                    </button>
                    <span id="vbm-undo-count" style="font-size: 11px; color: var(--ckui-text-muted); min-width: 20px;">0</span>
                    <button class="ckui-btn" id="vbm-redo" disabled title="重做">
                        ${Icons.redo}
                    </button>
                    <span id="vbm-redo-count" style="font-size: 11px; color: var(--ckui-text-muted); min-width: 20px;">0</span>
                    <div style="flex: 1;"></div>
                    <button class="ckui-btn ckui-btn-success" id="vbm-copy" disabled title="复制到剪贴板">
                        ${Icons.copy}
                    </button>
                    <button class="ckui-btn ckui-btn-primary" id="vbm-save" disabled title="保存文件">
                        ${Icons.save}
                    </button>
                    <button class="ckui-btn ckui-btn-danger" id="vbm-clear" disabled title="清空重来">
                        ${Icons.trash}
                    </button>
                </div>
            `;
            container.appendChild(actionsSection);
            if (this.settings.get('showImageInfo')) {
                const infoBtn = document.createElement('button');
                infoBtn.className = 'ckui-btn';
                infoBtn.id = 'vbm-info-toggle';
                infoBtn.innerHTML = `${Icons.image} <span style="margin-left: 6px;vertical-align: super;">图片信息</span>`;
                infoBtn.style.width = '100%';
                infoBtn.style.display = 'none';
                container.appendChild(infoBtn);

                const infoPanel = document.createElement('div');
                infoPanel.id = 'vbm-info-panel';
                infoPanel.style.display = 'none';
                infoPanel.innerHTML = `
                    <div style="padding: 12px; background: var(--ckui-bg-secondary); border-radius: var(--ckui-radius); margin-top: 8px; font-size: 12px;">
                        <div style="margin-bottom: 8px;">
                            <strong>尺寸:</strong><span id="vbm-info-dimensions">-</span>
                        </div>
                        <div style="margin-bottom: 8px;">
                            <strong>内存格式:</strong>PNG | <strong>大小:</strong><span id="vbm-info-memory">-</span>
                        </div>
                        <div style="margin-bottom: 8px;">
                            <strong>复制:</strong>PNG | <strong>大小:</strong><span id="vbm-info-copy">-</span>
                        </div>
                        <div>
                            <strong>保存格式:</strong><span id="vbm-info-save-format">-</span> | <strong>大小:</strong><span id="vbm-info-save">-</span>
                        </div>
                    </div>
                `;
                container.appendChild(infoPanel);
            }
            setTimeout(() => this.bindToolbarEvents(container), 0);

            return container;
        }

        createCaptureSettings() {
            const settings = this.settings;
            const div = document.createElement('div');
            div.style.cssText = 'padding: 12px;';
            div.innerHTML = `
                <div style="margin-bottom: 12px;">
                    <label class="ckui-label">画布宽度模式</label>
                    <select class="ckui-select" id="vbm-capture-mode">
                        <option value="fixed" ${settings.get('captureMode') === 'fixed' ? 'selected' : ''}>固定宽度</option>
                        <option value="adaptive" ${settings.get('captureMode') === 'adaptive' ? 'selected' : ''}>自适应宽度</option>
                    </select>
                </div>
                <div id="vbm-fixed-width-container" style="margin-bottom: 12px;${settings.get('captureMode') !== 'fixed' ? ' display:none;' : ''}">
                    <label class="ckui-label">固定宽度(px)</label>
                    <input type="number" class="ckui-input" id="vbm-fixed-width" value="${settings.get('fixedWidth')}" min="100" max="3840">
                </div>
                <div id="vbm-adaptive-width-container" style="margin-bottom: 12px;${settings.get('captureMode') !== 'adaptive' ? ' display:none;' : ''}">
                    <label class="ckui-label">自适应宽度范围(px)</label>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                        <div>
                            <label style="font-size: 12px; color: var(--ckui-text-secondary); display: block; margin-bottom: 4px;">最小宽度</label>
                            <input type="number" class="ckui-input" id="vbm-min-width" value="${settings.get('minWidth')}" min="100" max="3840">
                        </div>
                        <div>
                            <label style="font-size: 12px; color: var(--ckui-text-secondary); display: block; margin-bottom: 4px;">最大宽度</label>
                            <input type="number" class="ckui-input" id="vbm-max-width" value="${settings.get('maxWidth')}" min="100" max="3840">
                        </div>
                    </div>
                    <div style="font-size: 11px; color: var(--ckui-text-muted); margin-top: 4px;">
                        第一张截图宽度在此范围内时使用原宽度,否则限制到边界
                    </div>
                </div>
                <div style="margin-bottom: 12px;">
                    <label class="ckui-label">预览图片宽度(px)</label>
                    <input type="number" class="ckui-input" id="vbm-preview-width" value="${settings.get('previewImageWidth')}" min="100" max="800">
                </div>
                <div style="margin-bottom: 12px;">
                    <label class="ckui-label">上部分截图范围</label>
                    <div style="display: flex; gap: 8px;">
                        <input type="number" class="ckui-input" id="vbm-top-range" value="${settings.get('topRange')}" min="1" style="flex: 1;">
                        <select class="ckui-select" id="vbm-top-range-unit" style="width: 100px;">
                            <option value="percent" ${settings.get('topRangeUnit') === 'percent' ? 'selected' : ''}>百分比%</option>
                            <option value="pixel" ${settings.get('topRangeUnit') === 'pixel' ? 'selected' : ''}>像素px</option>
                        </select>
                    </div>
                </div>
                <div style="margin-bottom: 0;">
                    <label class="ckui-label">下部分截图范围</label>
                    <div style="display: flex; gap: 8px;">
                        <input type="number" class="ckui-input" id="vbm-bottom-range" value="${settings.get('bottomRange')}" min="1" style="flex: 1;">
                        <select class="ckui-select" id="vbm-bottom-range-unit" style="width: 100px;">
                            <option value="percent" ${settings.get('bottomRangeUnit') === 'percent' ? 'selected' : ''}>百分比%</option>
                            <option value="pixel" ${settings.get('bottomRangeUnit') === 'pixel' ? 'selected' : ''}>像素px</option>
                        </select>
                    </div>
                </div>
            `;
            return div;
        }

        createSaveSettings() {
            const settings = this.settings;
            const div = document.createElement('div');
            div.style.cssText = 'padding: 12px;';
            div.innerHTML = `
                <div style="margin-bottom: 12px;">
                    <label class="ckui-label">图片格式</label>
                    <select class="ckui-select" id="vbm-save-format">
                        <option value="png" ${settings.get('saveFormat') === 'png' ? 'selected' : ''}>PNG</option>
                        <option value="jpeg" ${settings.get('saveFormat') === 'jpeg' ? 'selected' : ''}>JPEG</option>
                        <option value="webp" ${settings.get('saveFormat') === 'webp' ? 'selected' : ''}>WebP</option>
                    </select>
                </div>
                <div style="margin-bottom: 0;">
                    <label class="ckui-label">图片质量 (%)</label>
                    <input type="number" class="ckui-input" id="vbm-save-quality" value="${Math.round(settings.get('saveQuality') * 100)}" min="1" max="100" step="1">
                    <div style="font-size: 11px; color: var(--ckui-text-muted); margin-top: 4px;">
                        PNG 格式质量参数无效,JPEG 和 WebP 格式范围为 1-100
                    </div>
                </div>
            `;
            return div;
        }

        createExperimentalSettings() {
            const settings = this.settings;
            const div = document.createElement('div');
            div.style.cssText = 'padding: 12px;';
            div.innerHTML = `
                <div style="margin-bottom: 12px;">
                    <label class="ckui-label" style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
                        <input type="checkbox" id="vbm-use-layer-capture" ${settings.get('useLayerCapture') ? 'checked' : ''} style="cursor: pointer;">
                        <span>叠层截图模式(捕获浮层)</span>
                        <span style="font-size: 10px; padding: 2px 6px; background: var(--ckui-warning); color: white; border-radius: 3px; margin-left: 4px;">实验性</span>
                    </label>
                    <div style="font-size: 11px; color: var(--ckui-text-muted); margin-top: 4px; padding-left: 24px;">
                        启用后将使用屏幕捕获API,可以截取视频上的弹幕、控制栏等浮层内容。首次使用时需要授权。
                    </div>
                </div>
                <div id="vbm-manual-offset-container" style="margin-bottom: 0;${!settings.get('useLayerCapture') ? ' display:none;' : ''}">
                    <label class="ckui-label">DisplayMedia 手动偏移补偿</label>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                        <div>
                            <label style="font-size: 12px; color: var(--ckui-text-secondary); display: block; margin-bottom: 4px;">左偏移(px)</label>
                            <input type="number" class="ckui-input" id="vbm-offset-left" value="${settings.get('manualOffsetLeft')}" ${!settings.get('useLayerCapture') ? 'disabled' : ''}>
                        </div>
                        <div>
                            <label style="font-size: 12px; color: var(--ckui-text-secondary); display: block; margin-bottom: 4px;">上偏移(px)</label>
                            <input type="number" class="ckui-input" id="vbm-offset-top" value="${settings.get('manualOffsetTop')}" ${!settings.get('useLayerCapture') ? 'disabled' : ''}>
                        </div>
                    </div>
                    <div style="font-size: 11px; color: var(--ckui-text-muted); margin-top: 4px;">
                        手动设置偏移值以修正 DisplayMedia 截图位置偏差
                    </div>
                </div>
            `;
            return div;
        }

        createOtherSettings() {
            const settings = this.settings;
            const div = document.createElement('div');
            div.style.cssText = 'padding: 12px;';
            div.innerHTML = `
                <div style="margin-bottom: 12px;">
                    <label class="ckui-label" style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
                        <input type="checkbox" id="vbm-enable-float-button" ${settings.get('enableFloatButton') ? 'checked' : ''} style="cursor: pointer;">
                        <span>启用页面浮动按钮</span>
                    </label>
                    <div style="font-size: 11px; color: var(--ckui-text-muted); margin-top: 4px; padding-left: 24px;">
                        在页面上显示一个浮动按钮,方便快速打开工具
                    </div>
                </div>
                <div style="margin-bottom: 12px;">
                    <label class="ckui-label" style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
                        <input type="checkbox" id="vbm-show-image-info" ${settings.get('showImageInfo') ? 'checked' : ''} style="cursor: pointer;">
                        <span>显示截图信息</span>
                    </label>
                    <div style="font-size: 11px; color: var(--ckui-text-muted); margin-top: 4px; padding-left: 24px;">
                        显示图片信息按钮并计算截图大小和分辨率(关闭可提升截图速度)
                    </div>
                </div>
                <div style="margin-bottom: 0;">
                    <label class="ckui-label" style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
                        <input type="checkbox" id="vbm-enable-watermark" ${settings.get('enabled') ? 'checked' : ''} style="cursor: pointer;">
                        <span>启用文字水印</span>
                    </label>
                    <div style="font-size: 11px; color: var(--ckui-text-muted); margin-top: 4px; padding-left: 24px;">
                        在图片末尾添加自定义文字内容
                    </div>
                </div>
                <div id="vbm-watermark-settings" style="margin-top: 12px; padding: 12px; background: var(--ckui-bg-tertiary); border-radius: var(--ckui-radius); display: ${settings.get('enabled') ? 'block' : 'none'};">
                    <div style="margin-bottom: 12px;">
                        <label class="ckui-label">文本内容</label>
                        <textarea class="ckui-input" id="vbm-watermark-content" rows="3" style="resize: vertical; font-family: monospace;">${this.escapeHtml(settings.get('content'))}</textarea>
                    </div>
                    <div style="margin-bottom: 12px;">
                        <label class="ckui-label">何时附加水印</label>
                        <select class="ckui-select" id="vbm-watermark-apply-mode">
                            <option value="always" ${settings.get('watermarkApplyMode') === 'always' ? 'selected' : ''}>总是添加</option>
                            <option value="copy" ${settings.get('watermarkApplyMode') === 'copy' ? 'selected' : ''}>仅复制时</option>
                            <option value="save" ${settings.get('watermarkApplyMode') === 'save' ? 'selected' : ''}>仅保存时</option>
                        </select>
                        <div style="font-size: 11px; color: var(--ckui-text-muted); margin-top: 4px;">
                            选择在何种操作时添加水印到图片中
                        </div>
                    </div>
                    <div id="vbm-watermark-text-settings">
                        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px;">
                            <div>
                                <label class="ckui-label" style="font-size: 12px;">字体大小(px)</label>
                                <input type="number" class="ckui-input" id="vbm-watermark-fontsize" value="${settings.get('fontSize')}" min="8" max="100">
                            </div>
                            <div>
                                <label class="ckui-label" style="font-size: 12px;">文字颜色</label>
                                <input type="color" class="ckui-input" id="vbm-watermark-color" value="${settings.get('textColor')}" style="height: 36px;">
                            </div>
                        </div>
                        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px;">
                            <div>
                                <label class="ckui-label" style="font-size: 12px;">文字对齐</label>
                                <select class="ckui-select" id="vbm-watermark-align">
                                    <option value="left" ${settings.get('textAlign') === 'left' ? 'selected' : ''}>左对齐</option>
                                    <option value="center" ${settings.get('textAlign') === 'center' ? 'selected' : ''}>居中</option>
                                    <option value="right" ${settings.get('textAlign') === 'right' ? 'selected' : ''}>右对齐</option>
                                </select>
                            </div>
                            <div>
                                <label class="ckui-label" style="font-size: 12px;">背景颜色</label>
                                <input type="color" class="ckui-input" id="vbm-watermark-bgcolor" value="${settings.get('backgroundColor')}" style="height: 36px;">
                            </div>
                        </div>
                        <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; margin-bottom: 8px;">
                            <div>
                                <label class="ckui-label" style="font-size: 12px;">内边距(px)</label>
                                <input type="number" class="ckui-input" id="vbm-watermark-padding" value="${settings.get('padding')}" min="0" max="200">
                            </div>
                            <div>
                                <label class="ckui-label" style="font-size: 12px;">高度(px, 0=自动)</label>
                                <input type="number" class="ckui-input" id="vbm-watermark-height" value="${settings.get('containerHeight')}" min="0" max="1000">
                            </div>
                            <div>
                                <label class="ckui-label" style="font-size: 12px;">宽度(px, 0=100%)</label>
                                <input type="number" class="ckui-input" id="vbm-watermark-width" value="${settings.get('containerWidth')}" min="0" max="5000">
                            </div>
                        </div>
                    </div>
                    <div style="margin-top: 12px;">
                        <label class="ckui-label">预览效果</label>
                        <div id="vbm-watermark-preview-container" style="border: 1px solid var(--ckui-border-color); border-radius: var(--ckui-radius); padding: 8px; background: white; max-height: 300px; overflow: auto; min-height: 60px; display: flex; align-items: center; justify-content: center; color: var(--ckui-text-muted);">
                            水印预览区域
                        </div>
                    </div>
                </div>
            `;
            return div;
        }
        
        escapeHtml(text) {
            const div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }

        createSettingsPanel() {
            const tabs = Utils.ui.tabs({
                tabs: [
                    { label: '📷 截图', content: this.createCaptureSettings() },
                    { label: ' 保存', content: this.createSaveSettings() },
                    { label: '🧪 实验', content: this.createExperimentalSettings() },
                    { label: '⚙️ 其他', content: this.createOtherSettings() }
                ],
                style: 'pills'
            });
            
            const container = document.createElement('div');
            container.style.cssText = 'background: var(--ckui-bg-secondary); border-radius: var(--ckui-radius); margin-top: 8px;';
            container.appendChild(tabs.render());
            return container;
        }

        bindToolbarEvents(container) {
            const selectBtn = container.querySelector('#vbm-select-video');
            selectBtn?.addEventListener('click', () => this.startVideoSelection());
            const captureFull = container.querySelector('#vbm-capture-full');
            const captureBottom = container.querySelector('#vbm-capture-bottom');
            const captureTop = container.querySelector('#vbm-capture-top');

            captureFull?.addEventListener('click', () => this.captureVideo('full'));
            captureBottom?.addEventListener('click', () => this.captureVideo('bottom'));
            captureTop?.addEventListener('click', () => this.captureVideo('top'));
            const settingsToggle = container.querySelector('#vbm-settings-toggle');
            const settingsPanel = container.querySelector('#vbm-settings-panel');
            settingsToggle?.addEventListener('click', () => {
                this.settingsExpanded = !this.settingsExpanded;
                settingsPanel.style.display = this.settingsExpanded ? 'block' : 'none';
            });
            const captureModeSelect = container.querySelector('#vbm-capture-mode');
            const fixedWidthContainer = container.querySelector('#vbm-fixed-width-container');
            const adaptiveWidthContainer = container.querySelector('#vbm-adaptive-width-container');
            const fixedWidthInput = container.querySelector('#vbm-fixed-width');
            const minWidthInput = container.querySelector('#vbm-min-width');
            const maxWidthInput = container.querySelector('#vbm-max-width');
            const topRangeInput = container.querySelector('#vbm-top-range');
            const topRangeUnit = container.querySelector('#vbm-top-range-unit');
            const bottomRangeInput = container.querySelector('#vbm-bottom-range');
            const bottomRangeUnit = container.querySelector('#vbm-bottom-range-unit');

            captureModeSelect?.addEventListener('change', (e) => {
                this.settings.set('captureMode', e.target.value);
                fixedWidthContainer.style.display = e.target.value === 'fixed' ? 'block' : 'none';
                adaptiveWidthContainer.style.display = e.target.value === 'adaptive' ? 'block' : 'none';
            });

            fixedWidthInput?.addEventListener('change', (e) => {
                this.settings.set('fixedWidth', parseInt(e.target.value) || 1280);
            });

            minWidthInput?.addEventListener('change', (e) => {
                this.settings.set('minWidth', parseInt(e.target.value) || 640);
            });

            maxWidthInput?.addEventListener('change', (e) => {
                this.settings.set('maxWidth', parseInt(e.target.value) || 1920);
            });

            const previewWidthInput = container.querySelector('#vbm-preview-width');
            previewWidthInput?.addEventListener('change', (e) => {
                this.settings.set('previewImageWidth', parseInt(e.target.value) || 260);
                this.updatePreview(); // Update preview with new width
            });

            topRangeInput?.addEventListener('input', (e) => {
                this.settings.set('topRange', parseInt(e.target.value) || 50);
                this.showRangePreview('top');
            });

            topRangeUnit?.addEventListener('change', (e) => {
                this.settings.set('topRangeUnit', e.target.value);
                this.showRangePreview('top');
            });

            bottomRangeInput?.addEventListener('input', (e) => {
                this.settings.set('bottomRange', parseInt(e.target.value) || 50);
                this.showRangePreview('bottom');
            });

            bottomRangeUnit?.addEventListener('change', (e) => {
                this.settings.set('bottomRangeUnit', e.target.value);
                this.showRangePreview('bottom');
            });
            const layerCaptureCheckbox = container.querySelector('#vbm-use-layer-capture');
            const manualOffsetContainer = container.querySelector('#vbm-manual-offset-container');
            const offsetLeftInput = container.querySelector('#vbm-offset-left');
            const offsetTopInput = container.querySelector('#vbm-offset-top');
            
            layerCaptureCheckbox?.addEventListener('change', (e) => {
                const enabled = e.target.checked;
                if (enabled && !this.checkLayerCaptureSupport()) {
                    e.target.checked = false;
                    Utils.ui.notify({
                        type: 'error',
                        title: '不支持',
                        message: '您的浏览器不支持叠层截图功能,需要 Chrome/Edge 90+ 或 Firefox 90+',
                        shadow: true,
                        duration: 5000
                    });
                    return;
                }
                
                this.settings.set('useLayerCapture', enabled);
                if (manualOffsetContainer) {
                    manualOffsetContainer.style.display = enabled ? 'block' : 'none';
                }
                if (offsetLeftInput) {
                    offsetLeftInput.disabled = !enabled;
                }
                if (offsetTopInput) {
                    offsetTopInput.disabled = !enabled;
                }
                
                if (enabled) {
                    Utils.ui.notify({
                        type: 'info',
                        title: '叠层截图已启用',
                        message: '下次截图时会弹出授权窗口,请选择当前标签页',
                        shadow: true,
                        duration: 4000
                    });
                }
            });
            offsetLeftInput?.addEventListener('change', (e) => {
                this.settings.set('manualOffsetLeft', parseInt(e.target.value) || 0);
            });
            
            offsetTopInput?.addEventListener('change', (e) => {
                this.settings.set('manualOffsetTop', parseInt(e.target.value) || 0);
            });
            const floatButtonCheckbox = container.querySelector('#vbm-enable-float-button');
            floatButtonCheckbox?.addEventListener('change', (e) => {
                this.settings.set('enableFloatButton', e.target.checked);
                
                if (e.target.checked) {
                    Utils.ui.notify({
                        type: 'info',
                        title: '浮动按钮已启用',
                        message: '刷新页面后生效',
                        shadow: true,
                        duration: 3000
                    });
                } else {
                    Utils.ui.notify({
                        type: 'info',
                        title: '浮动按钮已禁用',
                        message: '刷新页面后生效',
                        shadow: true,
                        duration: 3000
                    });
                }
            });

            const showImageInfoCheckbox = container.querySelector('#vbm-show-image-info');
            showImageInfoCheckbox?.addEventListener('change', (e) => {
                this.settings.set('showImageInfo', e.target.checked);
                
                if (e.target.checked) {
                    Utils.ui.notify({
                        type: 'info',
                        title: '截图信息已启用',
                        message: '重新打开工具窗口后生效',
                        shadow: true,
                        duration: 3000
                    });
                } else {
                    Utils.ui.notify({
                        type: 'info',
                        title: '截图信息已禁用',
                        message: '重新打开工具窗口后生效',
                        shadow: true,
                        duration: 3000
                    });
                }
            });
            const watermarkCheckbox = container.querySelector('#vbm-enable-watermark');
            const watermarkSettings = container.querySelector('#vbm-watermark-settings');
            watermarkCheckbox?.addEventListener('change', (e) => {
                this.settings.set('enabled', e.target.checked);
                watermarkSettings.style.display = e.target.checked ? 'block' : 'none';
                if (e.target.checked) {
                    this.debouncedPreviewWatermark();
                }
            });

            const watermarkContent = container.querySelector('#vbm-watermark-content');
            watermarkContent?.addEventListener('input', (e) => {
                this.settings.set('content', e.target.value);
                this.debouncedPreviewWatermark();
            });

            const watermarkApplyMode = container.querySelector('#vbm-watermark-apply-mode');
            watermarkApplyMode?.addEventListener('change', (e) => {
                this.settings.set('watermarkApplyMode', e.target.value);
            });

            const fontSize = container.querySelector('#vbm-watermark-fontsize');
            fontSize?.addEventListener('input', (e) => {
                this.settings.set('fontSize', parseInt(e.target.value) || 16);
                this.debouncedPreviewWatermark();
            });

            const textColor = container.querySelector('#vbm-watermark-color');
            textColor?.addEventListener('input', (e) => {
                this.settings.set('textColor', e.target.value);
                this.debouncedPreviewWatermark();
            });

            const textAlign = container.querySelector('#vbm-watermark-align');
            textAlign?.addEventListener('change', (e) => {
                this.settings.set('textAlign', e.target.value);
                this.debouncedPreviewWatermark();
            });

            const bgColor = container.querySelector('#vbm-watermark-bgcolor');
            bgColor?.addEventListener('input', (e) => {
                this.settings.set('backgroundColor', e.target.value);
                this.debouncedPreviewWatermark();
            });

            const padding = container.querySelector('#vbm-watermark-padding');
            padding?.addEventListener('input', (e) => {
                this.settings.set('padding', parseInt(e.target.value) || 20);
                this.debouncedPreviewWatermark();
            });

            const height = container.querySelector('#vbm-watermark-height');
            height?.addEventListener('input', (e) => {
                this.settings.set('containerHeight', parseInt(e.target.value) || 0);
                this.debouncedPreviewWatermark();
            });

            const width = container.querySelector('#vbm-watermark-width');
            width?.addEventListener('input', (e) => {
                this.settings.set('containerWidth', parseInt(e.target.value) || 0);
                this.debouncedPreviewWatermark();
            });
            if (this.settings.get('enabled')) {
                setTimeout(() => this.previewWatermark(), 100);
            }

            const saveFormatSelect = container.querySelector('#vbm-save-format');
            saveFormatSelect?.addEventListener('change', (e) => {
                this.settings.set('saveFormat', e.target.value);
                if (this.settings.get('showImageInfo')) {
                    this.updateImageInfo();
                }
            });

            const saveQualityInput = container.querySelector('#vbm-save-quality');
            saveQualityInput?.addEventListener('change', (e) => {
                const quality = Math.max(1, Math.min(100, parseInt(e.target.value) || 95));
                e.target.value = quality;
                this.settings.set('saveQuality', quality / 100);
                if (this.settings.get('showImageInfo')) {
                    this.updateImageInfo();
                }
            });

            const undoBtn = container.querySelector('#vbm-undo');
            const redoBtn = container.querySelector('#vbm-redo');
            const copyBtn = container.querySelector('#vbm-copy');
            const saveBtn = container.querySelector('#vbm-save');
            const clearBtn = container.querySelector('#vbm-clear');

            undoBtn?.addEventListener('click', () => this.undo());
            redoBtn?.addEventListener('click', () => this.redo());
            copyBtn?.addEventListener('click', () => this.copyToClipboard());
            saveBtn?.addEventListener('click', () => this.saveToFile());
            clearBtn?.addEventListener('click', () => this.clearCanvas());

            const infoToggleBtn = container.querySelector('#vbm-info-toggle');
            infoToggleBtn?.addEventListener('click', () => {
                this.infoExpanded = !this.infoExpanded;
                const infoPanel = container.querySelector('#vbm-info-panel');
                if (infoPanel) {
                    infoPanel.style.display = this.infoExpanded ? 'block' : 'none';
                }
                if (this.infoExpanded && this.settings.get('showImageInfo')) {
                    this.updateImageInfo();
                }
            });
        }

        startVideoSelection() {
            if (this.isSelectingVideo) return;

            this.isSelectingVideo = true;
            Utils.ui.notify({
                type: 'info',
                title: '选择视频',
                message: '请将鼠标悬停在视频上,然后点击选择',
                shadow: true
            });

            this.createHighlightOverlay();
            
            document.addEventListener('mouseover', this.handleMouseOver);
            document.addEventListener('click', this.handleVideoClick, true);
        }

        handleMouseOver = (e) => {
            if (!this.isSelectingVideo) return;
            
            const target = e.target;
            if (target.tagName === 'VIDEO') {
                const rect = target.getBoundingClientRect();
                this.highlightOverlay.style.cssText = `
                    position: fixed;
                    left: ${rect.left}px;
                    top: ${rect.top}px;
                    width: ${rect.width}px;
                    height: ${rect.height}px;
                    border: 3px solid #3b82f6;
                    background: rgba(59, 130, 246, 0.1);
                    pointer-events: none;
                    z-index: 999999;
                    box-sizing: border-box;
                `;
            } else {
                this.highlightOverlay.style.display = 'none';
            }
        };

        handleVideoClick = (e) => {
            if (!this.isSelectingVideo) return;
            
            const target = e.target;
            if (target.tagName === 'VIDEO') {
                e.preventDefault();
                e.stopPropagation();
                
                this.selectedVideo = target;
                this.isSelectingVideo = false;
                
                document.removeEventListener('mouseover', this.handleMouseOver);
                document.removeEventListener('click', this.handleVideoClick, true);
                
                this.removeHighlightOverlay();
                if (this.toolbarContainer) {
                    const statusEl = this.toolbarContainer.querySelector('#vbm-video-status');
                    if (statusEl) {
                        statusEl.textContent = '✓ 已选择视频';
                        statusEl.style.color = 'var(--ckui-success)';
                    }
                    const captureSection = this.toolbarContainer.querySelector('#vbm-capture-section');
                    if (captureSection) {
                        captureSection.style.display = 'block';
                    }
                }

                Utils.ui.notify({
                    type: 'success',
                    title: '视频已选择',
                    message: '现在可以开始截图了',
                    shadow: true
                });
            }
        };

        createHighlightOverlay() {
            if (this.highlightOverlay) return;
            this.highlightOverlay = document.createElement('div');
            document.body.appendChild(this.highlightOverlay);
        }

        removeHighlightOverlay() {
            if (this.highlightOverlay) {
                this.highlightOverlay.remove();
                this.highlightOverlay = null;
            }
        }

        showRangePreview(type) {
            if (!this.selectedVideo) return;

            this.removeRangeOverlay();
            
            const video = this.selectedVideo;
            const rect = video.getBoundingClientRect();
            
            let rangeValue = this.settings.get(`${type}Range`);
            let rangeUnit = this.settings.get(`${type}RangeUnit`);
            
            let height;
            if (rangeUnit === 'percent') {
                height = rect.height * (rangeValue / 100);
            } else {
                height = rangeValue;
            }
            
            height = Math.min(height, rect.height);
            
            this.rangeOverlay = document.createElement('div');
            this.rangeOverlay.style.cssText = `
                position: fixed;
                left: ${rect.left}px;
                top: ${type === 'top' ? rect.top : rect.bottom - height}px;
                width: ${rect.width}px;
                height: ${height}px;
                background: rgba(59, 130, 246, 0.3);
                border: 2px solid #3b82f6;
                pointer-events: none;
                z-index: 999999;
                box-sizing: border-box;
            `;
            document.body.appendChild(this.rangeOverlay);
            
            setTimeout(() => this.removeRangeOverlay(), 1000);
        }

        removeRangeOverlay() {
            if (this.rangeOverlay) {
                this.rangeOverlay.remove();
                this.rangeOverlay = null;
            }
        }
        debouncedPreviewWatermark() {
            if (this.previewDebounceTimer) {
                clearTimeout(this.previewDebounceTimer);
            }
            this.previewDebounceTimer = setTimeout(() => {
                this.previewWatermark();
            }, 300);
        }
        async previewWatermark() {
            try {
                const previewContainer = this.toolbarContainer?.querySelector('#vbm-watermark-preview-container');
                
                if (!previewContainer) return;
                
                previewContainer.innerHTML = '';
                previewContainer.style.display = 'flex';
                previewContainer.style.alignItems = 'center';
                previewContainer.style.justifyContent = 'center';
                const canvasWidth = this.canvas.canvas?.width || this.canvas.firstWidth || 1280;
                const watermarkCanvas = this.drawTextWatermarkToCanvas(canvasWidth);
                const img = document.createElement('img');
                img.src = watermarkCanvas.toDataURL();
                img.style.cssText = 'width: 100%; height: auto; display: block;';
                previewContainer.appendChild(img);
            } catch (error) {
                logger.error('Preview watermark failed:', error);
                const previewContainer = this.toolbarContainer?.querySelector('#vbm-watermark-preview-container');
                if (previewContainer) {
                    previewContainer.innerHTML = '<span style="color: var(--ckui-error);">预览失败</span>';
                }
            }
        }
        async generateFinalCanvas(action = 'always') {
            if (!this.canvas.canvas) {
                throw new Error('No canvas available');
            }

            const enabled = this.settings.get('enabled');
            const watermarkApplyMode = this.settings.get('watermarkApplyMode');
            
            // Determine if watermark should be applied
            const shouldApplyWatermark = enabled && (
                watermarkApplyMode === 'always' || 
                (watermarkApplyMode === 'copy' && action === 'copy') ||
                (watermarkApplyMode === 'save' && action === 'save')
            );
            
            if (!shouldApplyWatermark) {
                return this.canvas.canvas;
            }

            try {
                const originalCanvas = this.canvas.canvas;
                const watermarkCanvas = this.drawTextWatermarkToCanvas(originalCanvas.width);
                const finalCanvas = document.createElement('canvas');
                finalCanvas.width = originalCanvas.width;
                finalCanvas.height = originalCanvas.height + watermarkCanvas.height;
                
                const ctx = finalCanvas.getContext('2d');
                ctx.drawImage(originalCanvas, 0, 0);
                ctx.drawImage(watermarkCanvas, 0, originalCanvas.height);
                
                return finalCanvas;
            } catch (error) {
                logger.error('Generate final canvas with watermark failed:', error);
                Utils.ui.notify({
                    type: 'warning',
                    title: '水印添加失败',
                    message: '将使用原图进行操作',
                    shadow: true
                });
                return this.canvas.canvas;
            }
        }
        drawTextWatermarkToCanvas(width) {
            const settings = this.settings;
            const content = settings.get('content');
            const fontSize = settings.get('fontSize');
            const textColor = settings.get('textColor');
            const textAlign = settings.get('textAlign');
            const backgroundColor = settings.get('backgroundColor');
            const padding = settings.get('padding');
            const containerWidth = settings.get('containerWidth') || width;
            const containerHeight = settings.get('containerHeight');
            const lines = content.split('\n');
            const tempCanvas = document.createElement('canvas');
            const tempCtx = tempCanvas.getContext('2d');
            tempCtx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`;
            const lineHeight = fontSize * 1.5; // 行高为字体大小的 1.5 倍
            let maxLineWidth = 0;
            const measuredLines = lines.map(line => {
                const metrics = tempCtx.measureText(line);
                maxLineWidth = Math.max(maxLineWidth, metrics.width);
                return { text: line, width: metrics.width };
            });
            const contentHeight = lines.length * lineHeight;
            const actualHeight = containerHeight || (contentHeight + padding * 2);
            const canvas = document.createElement('canvas');
            canvas.width = containerWidth;
            canvas.height = actualHeight;
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = backgroundColor;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`;
            ctx.fillStyle = textColor;
            ctx.textBaseline = 'top';
            let x;
            if (textAlign === 'left') {
                ctx.textAlign = 'left';
                x = padding;
            } else if (textAlign === 'center') {
                ctx.textAlign = 'center';
                x = canvas.width / 2;
            } else if (textAlign === 'right') {
                ctx.textAlign = 'right';
                x = canvas.width - padding;
            }
            const startY = padding;
            measuredLines.forEach((line, index) => {
                const y = startY + index * lineHeight;
                ctx.fillText(line.text, x, y);
            });
            
            return canvas;
        }

        async captureVideoWithLayers(mode) {
            const video = this.selectedVideo;
            const rect = video.getBoundingClientRect();
            const dpr = window.devicePixelRatio || 1;
            const uiOffset = this.detectBrowserUIOffset();

            try {
                const toolbarShowing = !!this.toolbarWindow;
                const previewShowing = !!this.previewWindow;
                
                if (this.toolbarWindow && this.toolbarWindow.hide) {
                    this.toolbarWindow.hide();
                }
                if (this.previewWindow && this.previewWindow.hide) {
                    this.previewWindow.hide();
                }
                await Utils.wait(500);
                const stream = await navigator.mediaDevices.getDisplayMedia({
                    video: {
                        displaySurface: "browser",
                        width: { ideal: 3840 },
                        height: { ideal: 2160 }
                    },
                    audio: false,
                    preferCurrentTab: true
                });

                const videoTrack = stream.getVideoTracks()[0];
                const imageCapture = new ImageCapture(videoTrack);
                const bitmap = await imageCapture.grabFrame();
                videoTrack.stop();
                stream.getTracks().forEach(track => track.stop());
                if (toolbarShowing && this.toolbarWindow && this.toolbarWindow.show) {
                    this.toolbarWindow.show();
                }
                if (previewShowing && this.previewWindow && this.previewWindow.show) {
                    this.previewWindow.show();
                }
                const fullCanvas = document.createElement('canvas');
                const fullCtx = fullCanvas.getContext('2d');
                fullCanvas.width = bitmap.width;
                fullCanvas.height = bitmap.height;
                fullCtx.drawImage(bitmap, 0, 0);
                let cropX = rect.left * dpr;
                let cropY = rect.top * dpr;
                const cropWidth = rect.width * dpr;
                const cropHeight = rect.height * dpr;
                if (uiOffset.hasSignificantOffset) {
                    logger.log('Applying UI offset compensation:', uiOffset);
                    cropX += uiOffset.left * dpr;
                    cropY += uiOffset.top * dpr;
                }
                if (cropX < 0 || cropY < 0 || 
                    cropX + cropWidth > fullCanvas.width || 
                    cropY + cropHeight > fullCanvas.height) {
                    logger.warn('Crop area out of bounds', {
                        cropX, cropY, cropWidth, cropHeight,
                        canvasWidth: fullCanvas.width,
                        canvasHeight: fullCanvas.height
                    });
                    if (toolbarShowing && this.toolbarWindow && this.toolbarWindow.show) {
                        this.toolbarWindow.show();
                    }
                    if (previewShowing && this.previewWindow && this.previewWindow.show) {
                        this.previewWindow.show();
                    }
                    
                    Utils.ui.notify({
                        type: 'error',
                        title: '截图失败',
                        message: '裁剪区域超出边界,请尝试调整偏移设置',
                        shadow: true,
                        duration: 5000
                    });
                    return;
                }
                let finalCropY = cropY;
                let finalCropHeight = cropHeight;

                if (mode === 'top') {
                    const rangeValue = this.settings.get('topRange');
                    const rangeUnit = this.settings.get('topRangeUnit');
                    
                    if (rangeUnit === 'percent') {
                        finalCropHeight = cropHeight * (rangeValue / 100);
                    } else {
                        finalCropHeight = Math.min(rangeValue * dpr, cropHeight);
                    }
                } else if (mode === 'bottom') {
                    const rangeValue = this.settings.get('bottomRange');
                    const rangeUnit = this.settings.get('bottomRangeUnit');
                    
                    let height;
                    if (rangeUnit === 'percent') {
                        height = cropHeight * (rangeValue / 100);
                    } else {
                        height = Math.min(rangeValue * dpr, cropHeight);
                    }
                    
                    finalCropY = cropY + cropHeight - height;
                    finalCropHeight = height;
                }
                const croppedCanvas = document.createElement('canvas');
                const croppedCtx = croppedCanvas.getContext('2d');
                croppedCanvas.width = cropWidth;
                croppedCanvas.height = finalCropHeight;
                croppedCtx.drawImage(
                    fullCanvas,
                    cropX, finalCropY, cropWidth, finalCropHeight,
                    0, 0, cropWidth, finalCropHeight
                );
                const imageData = croppedCtx.getImageData(0, 0, croppedCanvas.width, croppedCanvas.height);
                let targetWidth;
                const captureMode = this.settings.get('captureMode');
                if (captureMode === 'fixed') {
                    targetWidth = this.settings.get('fixedWidth');
                } else if (captureMode === 'adaptive') {
                    const firstWidth = this.canvas.firstWidth || imageData.width;
                    const minWidth = this.settings.get('minWidth');
                    const maxWidth = this.settings.get('maxWidth');
                    
                    if (firstWidth < minWidth) {
                        targetWidth = minWidth;
                    } else if (firstWidth > maxWidth) {
                        targetWidth = maxWidth;
                    } else {
                        targetWidth = firstWidth;
                    }
                } else {
                    targetWidth = this.canvas.firstWidth || imageData.width;
                }

                this.canvas.appendImage(imageData, targetWidth);
                if (this.toolbarContainer) {
                    const actionsSection = this.toolbarContainer.querySelector('#vbm-actions-section');
                    if (actionsSection) {
                        actionsSection.style.display = 'block';
                    }
                    if (this.settings.get('showImageInfo')) {
                        const infoBtn = this.toolbarContainer.querySelector('#vbm-info-toggle');
                        if (infoBtn) {
                            infoBtn.style.display = 'block';
                        }
                    }
                }

                this.updatePreview();
                this.updateActionButtons();
                this.scrollPreviewToBottom();

            } catch (error) {
                if (this.toolbarWindow && this.toolbarWindow.show) {
                    this.toolbarWindow.show();
                }
                if (this.previewWindow && this.previewWindow.show) {
                    this.previewWindow.show();
                }

                logger.error('Layer capture failed:', error);
                
                if (error.name === 'NotAllowedError') {
                    Utils.ui.notify({
                        type: 'warning',
                        title: '未授权',
                        message: '您拒绝了屏幕捕获授权,已切换回普通截图模式',
                        shadow: true,
                        duration: 4000
                    });
                    this.settings.set('useLayerCapture', false);
                    if (this.toolbarContainer) {
                        const checkbox = this.toolbarContainer.querySelector('#vbm-use-layer-capture');
                        if (checkbox) checkbox.checked = false;
                    }
                } else if (error.name === 'NotSupportedError') {
                    Utils.ui.notify({
                        type: 'error',
                        title: '不支持',
                        message: '您的浏览器不支持此功能',
                        shadow: true
                    });
                } else {
                    Utils.ui.notify({
                        type: 'error',
                        title: '截图失败',
                        message: `发生错误: ${error.message}`,
                        shadow: true,
                        duration: 5000
                    });
                }
            }
        }

        async captureVideo(mode) {
            if (!this.selectedVideo) {
                Utils.ui.notify({
                    type: 'error',
                    title: '错误',
                    message: '请先选择视频',
                    shadow: true
                });
                return;
            }

            try {
                if (this.settings.get('useLayerCapture')) {
                    await this.captureVideoWithLayers(mode);
                    return;
                }
                const video = this.selectedVideo;
                const tempCanvas = document.createElement('canvas');
                const tempCtx = tempCanvas.getContext('2d');
                
                tempCanvas.width = video.videoWidth || video.clientWidth;
                tempCanvas.height = video.videoHeight || video.clientHeight;
                
                tempCtx.drawImage(video, 0, 0, tempCanvas.width, tempCanvas.height);
                
                let imageData;
                
                if (mode === 'full') {
                    imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
                } else if (mode === 'top') {
                    let height;
                    const rangeValue = this.settings.get('topRange');
                    const rangeUnit = this.settings.get('topRangeUnit');
                    
                    if (rangeUnit === 'percent') {
                        height = Math.floor(tempCanvas.height * (rangeValue / 100));
                    } else {
                        height = Math.min(rangeValue, tempCanvas.height);
                    }
                    
                    imageData = tempCtx.getImageData(0, 0, tempCanvas.width, height);
                } else if (mode === 'bottom') {
                    let height;
                    const rangeValue = this.settings.get('bottomRange');
                    const rangeUnit = this.settings.get('bottomRangeUnit');
                    
                    if (rangeUnit === 'percent') {
                        height = Math.floor(tempCanvas.height * (rangeValue / 100));
                    } else {
                        height = Math.min(rangeValue, tempCanvas.height);
                    }
                    
                    const startY = tempCanvas.height - height;
                    imageData = tempCtx.getImageData(0, startY, tempCanvas.width, height);
                }
                let targetWidth;
                const captureMode = this.settings.get('captureMode');
                if (captureMode === 'fixed') {
                    targetWidth = this.settings.get('fixedWidth');
                } else if (captureMode === 'adaptive') {
                    const firstWidth = this.canvas.firstWidth || imageData.width;
                    const minWidth = this.settings.get('minWidth');
                    const maxWidth = this.settings.get('maxWidth');
                    
                    if (firstWidth < minWidth) {
                        targetWidth = minWidth;
                    } else if (firstWidth > maxWidth) {
                        targetWidth = maxWidth;
                    } else {
                        targetWidth = firstWidth;
                    }
                } else {
                    targetWidth = this.canvas.firstWidth || imageData.width;
                }
                
                this.canvas.appendImage(imageData, targetWidth);
                if (this.toolbarContainer) {
                    const actionsSection = this.toolbarContainer.querySelector('#vbm-actions-section');
                    if (actionsSection) {
                        actionsSection.style.display = 'block';
                    }
                    if (this.settings.get('showImageInfo')) {
                        const infoBtn = this.toolbarContainer.querySelector('#vbm-info-toggle');
                        if (infoBtn) {
                            infoBtn.style.display = 'block';
                        }
                    }
                }
                
                this.updatePreview();
                this.updateActionButtons();
                this.scrollPreviewToBottom();
                
            } catch (error) {
                logger.error('Capture failed:', error);
                Utils.ui.notify({
                    type: 'error',
                    title: '截图失败',
                    message: error.message,
                    shadow: true
                });
            }
        }

        updatePreview() {
            if (!this.previewWindow && this.canvas.canvas) {
                this.createPreviewWindow();
            }
            
            if (this.previewContainer && this.canvas.canvas) {
                this.previewContainer.innerHTML = '';
                const img = document.createElement('img');
                img.src = this.canvas.toDataURL();
                const previewWidth = this.settings.get('previewImageWidth');
                img.style.cssText = `width: ${previewWidth}px; height: auto; display: block;`;
                this.previewContainer.appendChild(img);
                
                this.scrollPreviewToBottom();
                if (this.settings.get('showImageInfo')) {
                    this.updateImageInfo();
                }
            }
        }

        scrollPreviewToBottom() {
            if (this.previewContainer) {
                setTimeout(() => {
                    this.previewContainer.scrollTop = this.previewContainer.scrollHeight;
                }, 50);
            }
        }

        async updateImageInfo() {
            if (!this.canvas.canvas || !this.infoExpanded) return;
            
            const info = this.canvas.getImageInfo();
            if (!info) return;
            
            const saveFormat = this.settings.get('saveFormat');
            const saveQuality = this.settings.get('saveQuality');
            
            const memorySize = await this.canvas.calculateSize('png', 1.0);
            const copySize = await this.canvas.calculateSize('png', 1.0);
            const saveSize = await this.canvas.calculateSize(saveFormat, saveQuality);
            
            this.imageInfo = {
                width: info.width,
                height: info.height,
                memorySize,
                copySize,
                saveSize
            };
            
            if (this.toolbarContainer) {
                const dimensionsEl = this.toolbarContainer.querySelector('#vbm-info-dimensions');
                const memoryEl = this.toolbarContainer.querySelector('#vbm-info-memory');
                const copyEl = this.toolbarContainer.querySelector('#vbm-info-copy');
                const saveEl = this.toolbarContainer.querySelector('#vbm-info-save');
                const saveFormatEl = this.toolbarContainer.querySelector('#vbm-info-save-format');
                
                if (dimensionsEl) dimensionsEl.textContent = `${info.width} × ${info.height}`;
                if (memoryEl) memoryEl.textContent = this.formatFileSize(memorySize);
                if (copyEl) copyEl.textContent = this.formatFileSize(copySize);
                if (saveEl) saveEl.textContent = this.formatFileSize(saveSize);
                if (saveFormatEl) saveFormatEl.textContent = `${saveFormat.toUpperCase()}${saveFormat !== 'png' ? ` (${Math.round(saveQuality * 100)}%)` : ''}`;
            }
        }

        formatFileSize(bytes) {
            if (bytes === 0) return '0 B';
            const k = 1024;
            const sizes = ['B', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
        }

        createPreviewWindow() {
            this.previewContainer = document.createElement('div');
            this.previewContainer.id = 'vbm-preview-container';
            this.previewContainer.style.cssText = 'max-height: 80vh; overflow-y: auto; display: flex; flex-direction: column; align-items: center;';
            
            this.previewWindow = Utils.ui.floatWindow({
                title: '预览',
                content: this.previewContainer,
                width: 280,
                position: { x: 520, y: 100 },
                shadow: true,
                onClose: () => {
                    this.previewWindow = null;
                    this.previewContainer = null;
                }
            });
            this.previewWindow.show();
        }

        updateActionButtons() {
            if (!this.toolbarContainer) return;
            
            const undoBtn = this.toolbarContainer.querySelector('#vbm-undo');
            const redoBtn = this.toolbarContainer.querySelector('#vbm-redo');
            const undoCount = this.toolbarContainer.querySelector('#vbm-undo-count');
            const redoCount = this.toolbarContainer.querySelector('#vbm-redo-count');
            const copyBtn = this.toolbarContainer.querySelector('#vbm-copy');
            const saveBtn = this.toolbarContainer.querySelector('#vbm-save');
            const clearBtn = this.toolbarContainer.querySelector('#vbm-clear');
            
            const hasCanvas = !!this.canvas.canvas;
            const canUndoSteps = this.canvas.historyIndex;
            const canRedoSteps = this.canvas.history.length - 1 - this.canvas.historyIndex;
            
            if (undoBtn) {
                undoBtn.disabled = !this.canvas.canUndo();
            }
            if (redoBtn) {
                redoBtn.disabled = !this.canvas.canRedo();
            }
            if (undoCount) {
                undoCount.textContent = canUndoSteps > 0 ? canUndoSteps : '';
                undoCount.style.color = canUndoSteps > 0 ? 'var(--ckui-text-secondary)' : 'var(--ckui-text-muted)';
            }
            if (redoCount) {
                redoCount.textContent = canRedoSteps > 0 ? canRedoSteps : '';
                redoCount.style.color = canRedoSteps > 0 ? 'var(--ckui-text-secondary)' : 'var(--ckui-text-muted)';
            }
            if (copyBtn) copyBtn.disabled = !hasCanvas;
            if (saveBtn) saveBtn.disabled = !hasCanvas;
            if (clearBtn) clearBtn.disabled = !hasCanvas;
        }

        undo() {
            if (this.canvas.undo()) {
                this.updatePreview();
                this.updateActionButtons();
                this.scrollPreviewToBottom();
            }
        }

        redo() {
            if (this.canvas.redo()) {
                this.updatePreview();
                this.updateActionButtons();
                this.scrollPreviewToBottom();
            }
        }

        async copyToClipboard() {
            try {
                const finalCanvas = await this.generateFinalCanvas('copy');
                const blob = await new Promise((resolve) => {
                    finalCanvas.toBlob((blob) => {
                        resolve(blob);
                    }, 'image/png', 1.0);
                });
                
                const mimeType = 'image/png';
                
                await navigator.clipboard.write([
                    new ClipboardItem({ [mimeType]: blob })
                ]);
                
                Utils.ui.notify({
                    type: 'success',
                    title: '复制成功',
                    message: '图片已复制到剪贴板 (PNG)',
                    shadow: true
                });
            } catch (error) {
                logger.error('Copy failed:', error);
                Utils.ui.notify({
                    type: 'error',
                    title: '复制失败',
                    message: error.message,
                    shadow: true
                });
            }
        }

        async saveToFile() {
            try {
                const format = this.settings.get('saveFormat');
                const quality = this.settings.get('saveQuality');
                const finalCanvas = await this.generateFinalCanvas('save');
                const blob = await new Promise((resolve) => {
                    finalCanvas.toBlob((blob) => {
                        resolve(blob);
                    }, `image/${format}`, quality);
                });
                
                const filename = `video-barpic-${Date.now()}.${format}`;
                Utils.downloadBlob(filename, blob);
                
                Utils.ui.notify({
                    type: 'success',
                    title: '保存成功',
                    message: `文件已保存: ${filename}`,
                    shadow: true
                });
            } catch (error) {
                logger.error('Save failed:', error);
                Utils.ui.notify({
                    type: 'error',
                    title: '保存失败',
                    message: error.message,
                    shadow: true
                });
            }
        }

        async clearCanvas() {
            const confirmed = await Utils.ui.confirm({
                title: '确认清空',
                content: '确定要清空当前的所有截图吗?此操作不可恢复。',
                shadow: true
            });
            
            if (!confirmed) {
                return;
            }
            this.canvas.clear();
            if (this.previewWindow) {
                this.previewWindow.close();
                this.previewWindow = null;
                this.previewContainer = null;
            }
            if (this.toolbarContainer) {
                const actionsSection = this.toolbarContainer.querySelector('#vbm-actions-section');
                if (actionsSection) {
                    actionsSection.style.display = 'none';
                }
                const infoBtn = this.toolbarContainer.querySelector('#vbm-info-toggle');
                if (infoBtn) {
                    infoBtn.style.display = 'none';
                }
                const infoPanel = this.toolbarContainer.querySelector('#vbm-info-panel');
                if (infoPanel) {
                    infoPanel.style.display = 'none';
                }
            }
            this.infoExpanded = false;
            this.imageInfo = { memorySize: 0, copySize: 0, saveSize: 0, width: 0, height: 0 };
            this.updateActionButtons();
            logger.log('Canvas cleared successfully', {
                canvasExists: !!this.canvas.canvas,
                historyLength: this.canvas.history.length,
                historyIndex: this.canvas.historyIndex
            });
            
            Utils.ui.notify({
                type: 'success',
                title: '已清空',
                message: '画布已清空,可以重新开始截图',
                shadow: true
            });
        }

        cleanup() {
            this.removeHighlightOverlay();
            this.removeRangeOverlay();
            this.isSelectingVideo = false;
            document.removeEventListener('mouseover', this.handleMouseOver);
            document.removeEventListener('click', this.handleVideoClick, true);
            if (this.displayMediaStream) {
                this.displayMediaStream.getTracks().forEach(track => track.stop());
                this.displayMediaStream = null;
            }
        }
    }
    const app = new VideoBarpicMaker();
    app.init();

})(unsafeWindow, document);