YouTube Subtitle Fix

Improves YouTube subtitles with smarter line wrapping, readable styling, customizable settings panel, and YouTube header icon.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         YouTube Subtitle Fix
// @namespace    https://github.com/SDavid33
// @version      1.2.10
// @description  Improves YouTube subtitles with smarter line wrapping, readable styling, customizable settings panel, and YouTube header icon.
// @author       David33
// @match        https://www.youtube.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_notification
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    const THEME = {
        accent: '#ffb330',
        activeText: '#111111'
    };

    const DEFAULT_SETTINGS = {
        enabled: true,

        textColor: '#ffb330',
        backgroundColor: '0, 0, 0',
        backgroundOpacity: 1,

        fontSizeNormal: 32,
        fontSizeFullscreen: 42,
        subtitleSizeMode: 'script',
        preferYouTubeSizeInSmallPlayers: true,
        customFontSizeNormal: 32,
        customFontSizeFullscreen: 42,

        lineHeight: 1.35,
        maxWidthPercent: 75,
        enableAutoLineBreaks: true,
        preserveAutoGeneratedCaptions: true,
        maxCharsPerLine: 45,
        perLineBackground: true,

        paddingY: 0.20,
        paddingX: 0.40,
        borderRadius: 4,

        offsetNormal: 50,
        offsetFullscreen: 80,
        extraPerLine: 5,

        positionNormal: 0,
        positionFullscreen: 0,

        textShadow: '0 0 2px rgba(0,0,0,1), 0 0 4px rgba(0,0,0,1)'
    };

    const SETTINGS_STORAGE_KEY = 'ytSubtitleFixSettings';
    const SETTINGS = {
        ...DEFAULT_SETTINGS,
        ...loadSavedSettings()
    };

    const STYLE_ID = 'yt-subtitle-full-control-style';
    const LINE_BACKGROUND_CLASS = 'yt-sub-fix-line-background';

    const IDS = {
        topButton: 'yt-sub-fix-top-button',
        fallbackButton: 'yt-sub-fix-floating-button',
        panel: 'yt-sub-fix-settings-panel',
        uiStyle: 'yt-sub-fix-ui-style',
        subtitleStyle: STYLE_ID
    };

    const KOFI = {
        url: 'https://ko-fi.com/N4N0XO52O',
        label: 'Support me on Ko-fi',
        color: THEME.accent
    };

    let playerObserver = null;
    let captionObserver = null;
    let rafId = null;
    let stylesInjected = false;
    let observersPaused = false;
    let observerResumeId = null;
    let clickLock = false;

    function loadSavedSettings() {
        try {
            if (typeof GM_getValue === 'function') {
                const raw = GM_getValue(SETTINGS_STORAGE_KEY, '{}');
                return JSON.parse(raw || '{}');
            }
        } catch (error) {
            console.warn('YouTube Subtitle Fix: failed to load saved settings.', error);
        }

        return {};
    }

    function saveSettings(partialSettings) {
        if (typeof GM_setValue !== 'function') return false;

        Object.assign(SETTINGS, partialSettings);
        GM_setValue(SETTINGS_STORAGE_KEY, JSON.stringify(SETTINGS));

        return true;
    }

    function clampNumber(value, min, max, fallback) {
        if (!Number.isFinite(value)) return fallback;
        return Math.min(max, Math.max(min, value));
    }

    function sanitizeHexColor(value, fallback) {
        const raw = String(value || '').trim();

        if (/^#[0-9a-f]{6}$/i.test(raw)) return raw.toLowerCase();
        if (/^[0-9a-f]{6}$/i.test(raw)) return `#${raw.toLowerCase()}`;

        return fallback;
    }

    function rgbStringToHex(rgb) {
        const parts = String(rgb || '0, 0, 0')
            .split(',')
            .map(v => Math.max(0, Math.min(255, Number(v.trim()) || 0)));

        const [r, g, b] = parts;

        return '#' + [r, g, b]
            .map(v => v.toString(16).padStart(2, '0'))
            .join('');
    }

    function hexToRgbString(hex) {
        const clean = sanitizeHexColor(hex, '#000000').replace('#', '');
        const r = parseInt(clean.slice(0, 2), 16);
        const g = parseInt(clean.slice(2, 4), 16);
        const b = parseInt(clean.slice(4, 6), 16);

        return `${r}, ${g}, ${b}`;
    }

    function promptNumber(message, currentValue) {
        const input = window.prompt(message, String(currentValue));
        if (input === null) return null;

        const value = Number(input.trim());
        if (!Number.isFinite(value) || value <= 0) {
            window.alert('Please enter a valid positive number.');
            return null;
        }

        return value;
    }

    function notify(message) {
        console.log('[YouTube Subtitle Fix]', message);

        try {
            GM_notification({
                title: 'YouTube Subtitle Fix',
                text: message,
                timeout: 2200
            });
        } catch {
            console.log(message);
        }
    }

    function injectUiStyles() {
        if (document.getElementById(IDS.uiStyle)) return;

        const style = document.createElement('style');
        style.id = IDS.uiStyle;
        style.textContent = `
            #${IDS.topButton} {
                display: inline-flex !important;
                align-items: center !important;
                justify-content: center !important;
                height: 36px !important;
                padding: 0 14px !important;
                margin-left: 0 !important;
                margin-right: 10px !important;
                border-radius: 18px !important;
                background: var(--yt-spec-badge-chip-background, rgba(255,255,255,.1)) !important;
                color: var(--yt-spec-text-primary, #fff) !important;
                font-family: Roboto, Arial, sans-serif !important;
                font-size: 14px !important;
                font-weight: 600 !important;
                cursor: pointer !important;
                user-select: none !important;
                white-space: nowrap !important;
            }

            #${IDS.topButton}:hover {
                background: var(--yt-spec-button-chip-background-hover, rgba(255,255,255,.18)) !important;
            }

            #${IDS.fallbackButton} {
                position: fixed !important;
                right: 18px !important;
                bottom: 18px !important;
                z-index: 2147483647 !important;
                padding: 10px 13px !important;
                border-radius: 999px !important;
                background: ${THEME.accent} !important;
                color: ${THEME.activeText} !important;
                font-family: Roboto, Arial, sans-serif !important;
                font-size: 13px !important;
                font-weight: 800 !important;
                cursor: pointer !important;
                box-shadow: 0 8px 24px rgba(0,0,0,.45) !important;
                user-select: none !important;
                pointer-events: auto !important;
            }

            #${IDS.panel} {
                position: fixed !important;
                right: 18px !important;
                top: 62px !important;
                bottom: auto !important;
                z-index: 2147483647 !important;
                width: 455px !important;
                max-height: calc(100vh - 82px) !important;
                overflow-y: auto !important;
                padding: 14px !important;
                border-radius: 16px !important;
                background: rgba(22, 22, 22, .98) !important;
                border: 1px solid rgba(255,255,255,.18) !important;
                color: #fff !important;
                font-family: Roboto, Arial, sans-serif !important;
                box-shadow: 0 10px 34px rgba(0,0,0,.55) !important;
                box-sizing: border-box !important;
                pointer-events: auto !important;
            }

            #${IDS.panel} .yt-sf-header {
                display: flex !important;
                justify-content: space-between !important;
                align-items: center !important;
                margin-bottom: 12px !important;
            }

            #${IDS.panel} .yt-sf-title {
                font-size: 15px !important;
                font-weight: 800 !important;
            }

            #${IDS.panel} .yt-sf-close {
                width: 28px !important;
                height: 28px !important;
                border-radius: 999px !important;
                border: none !important;
                background: rgba(255,255,255,.12) !important;
                color: #fff !important;
                cursor: pointer !important;
                font-size: 16px !important;
            }

            #${IDS.panel} .yt-sf-section {
                margin-top: 12px !important;
                padding-top: 12px !important;
                border-top: 1px solid rgba(255,255,255,.1) !important;
            }

            #${IDS.panel} .yt-sf-section.no-border {
                border-top: none !important;
                padding-top: 0 !important;
                margin-top: 0 !important;
            }

            #${IDS.panel} .yt-sf-label {
                font-size: 12px !important;
                color: #aaa !important;
                margin-bottom: 7px !important;
                font-weight: 700 !important;
            }

            #${IDS.panel} .yt-sf-row {
                display: flex !important;
                flex-wrap: wrap !important;
                gap: 7px !important;
                align-items: center !important;
            }

            #${IDS.panel} button {
                border: none !important;
                border-radius: 999px !important;
                padding: 8px 11px !important;
                background: rgba(255,255,255,.13) !important;
                color: #fff !important;
                cursor: pointer !important;
                font-size: 12px !important;
                font-weight: 650 !important;
            }

            #${IDS.panel} button.active {
                background: ${THEME.accent} !important;
                color: ${THEME.activeText} !important;
            }

            #${IDS.panel} button.danger {
                background: rgba(255, 70, 70, .18) !important;
            }

            #${IDS.panel} .yt-sf-color-wrap {
                display: inline-flex !important;
                align-items: center !important;
                gap: 7px !important;
                flex: 0 0 auto !important;
                padding: 6px 8px !important;
                border-radius: 999px !important;
                background: rgba(255,255,255,.13) !important;
                color: #fff !important;
                font-size: 12px !important;
                font-weight: 650 !important;
            }

            #${IDS.panel} input[type="color"] {
                width: 28px !important;
                height: 24px !important;
                padding: 0 !important;
                border: none !important;
                background: transparent !important;
                cursor: pointer !important;
            }

            #${IDS.panel} .yt-sf-range-wrap {
                display: flex !important;
                align-items: center !important;
                gap: 8px !important;
                flex: 0 1 190px !important;
                min-width: 190px !important;
                padding: 8px 10px !important;
                border-radius: 12px !important;
                background: rgba(255,255,255,.08) !important;
                box-sizing: border-box !important;
            }

            #${IDS.panel} .yt-sf-range-wrap span:first-child {
                white-space: nowrap !important;
            }

            #${IDS.panel} input[type="range"] {
                flex: 1 !important;
                min-width: 0 !important;
                width: 80px !important;
                accent-color: ${THEME.accent} !important;
            }

            #${IDS.panel} .yt-sf-range-value {
                min-width: 34px !important;
                text-align: right !important;
                color: #ddd !important;
                font-size: 12px !important;
                font-weight: 700 !important;
            }

            #${IDS.panel} .yt-sf-status {
                margin-top: 12px !important;
                padding: 8px 10px !important;
                border-radius: 10px !important;
                background: rgba(255,255,255,.08) !important;
                font-size: 12px !important;
                color: #bbb !important;
                line-height: 1.35 !important;
            }

            #${IDS.panel} .yt-sf-kofi {
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
                margin-top: 10px !important;
                padding: 9px 12px !important;
                border-radius: 12px !important;
                background: ${KOFI.color} !important;
                color: ${THEME.activeText} !important;
                font-size: 13px !important;
                font-weight: 800 !important;
                text-decoration: none !important;
                text-align: center !important;
            }

            #${IDS.panel} .yt-sf-kofi:hover {
                filter: brightness(1.08) !important;
            }
        `;

        document.head.appendChild(style);
    }

    function makeButton(label, action, extraClass = '') {
        const button = document.createElement('button');
        button.type = 'button';
        button.textContent = label;
        button.dataset.action = action;

        if (extraClass) button.className = extraClass;

        return button;
    }

    function makeSection(label, children, noBorder = false) {
        const section = document.createElement('div');
        section.className = noBorder ? 'yt-sf-section no-border' : 'yt-sf-section';

        const labelEl = document.createElement('div');
        labelEl.className = 'yt-sf-label';
        labelEl.textContent = label;

        const row = document.createElement('div');
        row.className = 'yt-sf-row';

        children.forEach(child => row.appendChild(child));

        section.appendChild(labelEl);
        section.appendChild(row);

        return section;
    }

    function makeColorPicker(label, key, value) {
        const wrap = document.createElement('label');
        wrap.className = 'yt-sf-color-wrap';

        const text = document.createElement('span');
        text.textContent = label;

        const input = document.createElement('input');
        input.type = 'color';
        input.value = key === 'backgroundColor' ? rgbStringToHex(value) : sanitizeHexColor(value, '#ffffff');
        input.dataset.settingKey = key;

        wrap.appendChild(text);
        wrap.appendChild(input);

        return wrap;
    }

    function makeOpacitySlider(value) {
        const wrap = document.createElement('div');
        wrap.className = 'yt-sf-range-wrap';

        const label = document.createElement('span');
        label.textContent = 'BG opacity';
        label.style.fontSize = '12px';
        label.style.fontWeight = '700';
        label.style.color = '#ddd';

        const input = document.createElement('input');
        input.type = 'range';
        input.min = '0';
        input.max = '1';
        input.step = '0.05';
        input.value = String(value);
        input.dataset.settingKey = 'backgroundOpacity';

        const valueEl = document.createElement('span');
        valueEl.className = 'yt-sf-range-value';
        valueEl.textContent = `${Math.round(value * 100)}%`;

        wrap.appendChild(label);
        wrap.appendChild(input);
        wrap.appendChild(valueEl);

        return wrap;
    }

    function shouldShowSettingsButton() {
        if (window.self !== window.top) return false;
        if (location.pathname.startsWith('/live_chat')) return false;
        if (location.pathname.startsWith('/embed')) return false;
        if (location.pathname.startsWith('/watch_chat')) return false;

        return true;
    }

    function ensureTopButton() {
        if (!shouldShowSettingsButton()) {
            document.getElementById(IDS.topButton)?.remove();
            document.getElementById(IDS.fallbackButton)?.remove();
            document.getElementById(IDS.panel)?.remove();
            return;
        }

        injectUiStyles();

        const mastheadEnd =
            document.querySelector('ytd-masthead #end') ||
            document.querySelector('#end.ytd-masthead') ||
            document.querySelector('ytd-masthead');

        let topButton = document.getElementById(IDS.topButton);

        if (mastheadEnd) {
            if (!topButton) {
                topButton = document.createElement('div');
                topButton.id = IDS.topButton;
                topButton.textContent = 'YT Sub Fix';
                topButton.title = 'YouTube Subtitle Fix';
                topButton.setAttribute('role', 'button');
                topButton.setAttribute('tabindex', '0');

                mastheadEnd.insertBefore(topButton, mastheadEnd.firstChild);
            }

            document.getElementById(IDS.fallbackButton)?.remove();
        } else {
            let fallbackButton = document.getElementById(IDS.fallbackButton);

            if (!fallbackButton) {
                fallbackButton = document.createElement('div');
                fallbackButton.id = IDS.fallbackButton;
                fallbackButton.textContent = 'YT Sub Fix';
                fallbackButton.title = 'YouTube Subtitle Fix';
                fallbackButton.setAttribute('role', 'button');
                fallbackButton.setAttribute('tabindex', '0');
                document.body.appendChild(fallbackButton);
            }
        }
    }

    function toggleSettingsPanel() {
        if (!shouldShowSettingsButton()) return;

        const existing = document.getElementById(IDS.panel);

        if (existing) {
            existing.remove();
            return;
        }

        createSettingsPanel();
    }

    function createSettingsPanel() {
        injectUiStyles();

        document.getElementById(IDS.panel)?.remove();

        const panel = document.createElement('div');
        panel.id = IDS.panel;

        const header = document.createElement('div');
        header.className = 'yt-sf-header';

        const title = document.createElement('div');
        title.className = 'yt-sf-title';
        title.textContent = 'YouTube Subtitle Fix';

        const close = makeButton('×', 'close', 'yt-sf-close');

        header.appendChild(title);
        header.appendChild(close);

        const enabledOn = makeButton('ON', 'enabled-on', SETTINGS.enabled ? 'active' : '');
        const enabledOff = makeButton('OFF', 'enabled-off', !SETTINGS.enabled ? 'active' : '');

        const scopeMain = makeButton('Main video only', 'scope-main', SETTINGS.preferYouTubeSizeInSmallPlayers ? 'active' : '');
        const scopeAll = makeButton('All players', 'scope-all', !SETTINGS.preferYouTubeSizeInSmallPlayers ? 'active' : '');

        const modeYoutube = makeButton('YouTube size', 'mode-youtube', SETTINGS.subtitleSizeMode === 'default' ? 'active' : '');
        const modeScript = makeButton('Script size', 'mode-script', SETTINGS.subtitleSizeMode === 'script' ? 'active' : '');
        const modeCustom = makeButton('Custom size', 'mode-custom', SETTINGS.subtitleSizeMode === 'custom' ? 'active' : '');

        const setCustomSizes = makeButton('Set custom sizes', 'set-custom-sizes');
        const normalMinus = makeButton(`Normal - (${SETTINGS.customFontSizeNormal}px)`, 'normal-minus');
        const normalPlus = makeButton(`Normal + (${SETTINGS.customFontSizeNormal}px)`, 'normal-plus');
        const fullMinus = makeButton(`Fullscreen - (${SETTINGS.customFontSizeFullscreen}px)`, 'full-minus');
        const fullPlus = makeButton(`Fullscreen + (${SETTINGS.customFontSizeFullscreen}px)`, 'full-plus');
        const textColor = makeColorPicker('Text', 'textColor', SETTINGS.textColor);
        const bgColor = makeColorPicker('BG', 'backgroundColor', SETTINGS.backgroundColor);
        const opacitySlider = makeOpacitySlider(SETTINGS.backgroundOpacity);
        const bgModeLine = makeButton('Line BG', 'bg-mode-line', SETTINGS.perLineBackground ? 'active' : '');
        const bgModeBox = makeButton('Box BG', 'bg-mode-box', !SETTINGS.perLineBackground ? 'active' : '');

        const wrapOn = makeButton('Wrap ON', 'wrap-on', SETTINGS.enableAutoLineBreaks ? 'active' : '');
        const wrapOff = makeButton('Wrap OFF', 'wrap-off', !SETTINGS.enableAutoLineBreaks ? 'active' : '');
        const lineShorter = makeButton(`Line shorter (${SETTINGS.maxCharsPerLine})`, 'line-shorter');
        const lineLonger = makeButton(`Line longer (${SETTINGS.maxCharsPerLine})`, 'line-longer');

        const normalUp = makeButton(`Normal up (${SETTINGS.positionNormal}px)`, 'position-normal-up');
        const normalDown = makeButton(`Normal down (${SETTINGS.positionNormal}px)`, 'position-normal-down');
        const fullUp = makeButton(`Fullscreen up (${SETTINGS.positionFullscreen}px)`, 'position-full-up');
        const fullDown = makeButton(`Fullscreen down (${SETTINGS.positionFullscreen}px)`, 'position-full-down');
        const resetPosition = makeButton('Reset position', 'position-reset');

        const reset = makeButton('Reset settings', 'reset-settings', 'danger');

        const status = document.createElement('div');
        status.className = 'yt-sf-status';

        const kofi = document.createElement('a');
        kofi.className = 'yt-sf-kofi';
        kofi.href = KOFI.url;
        kofi.target = '_blank';
        kofi.rel = 'noopener noreferrer';
        kofi.textContent = KOFI.label;

        panel.appendChild(header);
        panel.appendChild(makeSection('Subtitle Fix', [enabledOn, enabledOff], true));
        panel.appendChild(makeSection('Custom size scope', [scopeMain, scopeAll]));
        panel.appendChild(makeSection('Subtitle size mode', [modeYoutube, modeScript, modeCustom]));
        panel.appendChild(makeSection('Customization', [
            setCustomSizes,
            normalMinus,
            normalPlus,
            fullPlus,
            fullMinus,
            textColor,
            bgColor,
            bgModeLine,
            bgModeBox,
            opacitySlider
        ]));
        panel.appendChild(makeSection('Smart line wrapping', [
            wrapOn,
            wrapOff,
            lineShorter,
            lineLonger
        ]));
        panel.appendChild(makeSection('Subtitle position', [
            normalUp,
            normalDown,
            fullUp,
            fullDown,
            resetPosition
        ]));
        panel.appendChild(makeSection('Reset', [reset]));
        panel.appendChild(status);
        panel.appendChild(kofi);

        document.body.appendChild(panel);
        updateStatus();
    }

    function updateStatus() {
        const status = document.querySelector(`#${IDS.panel} .yt-sf-status`);
        if (!status) return;

        const normalSize = SETTINGS.subtitleSizeMode === 'default' ? 'YouTube default' : `${SETTINGS.customFontSizeNormal}px`;
        const fullscreenSize = SETTINGS.subtitleSizeMode === 'default' ? 'YouTube default' : `${SETTINGS.customFontSizeFullscreen}px`;

        status.textContent =
            `Enabled: ${SETTINGS.enabled ? 'ON' : 'OFF'} | ` +
            `Size scope: ${SETTINGS.preferYouTubeSizeInSmallPlayers ? 'main only' : 'all players'} | ` +
            `Mode: ${SETTINGS.subtitleSizeMode} | ` +
            `Normal: ${normalSize} | ` +
            `Fullscreen: ${fullscreenSize} | ` +
            `Text: ${SETTINGS.textColor} | ` +
            `BG: ${rgbStringToHex(SETTINGS.backgroundColor)} / ${Math.round(SETTINGS.backgroundOpacity * 100)}% | ` +
            `BG mode: ${SETTINGS.perLineBackground ? 'line' : 'box'} | ` +
            `Wrap: ${SETTINGS.enableAutoLineBreaks ? 'ON' : 'OFF'} | ` +
            `Line: ${SETTINGS.maxCharsPerLine} chars | ` +
            `Position: normal ${SETTINGS.positionNormal}px / fullscreen ${SETTINGS.positionFullscreen}px`;
    }

    function refreshAfterSettingChange(rebuildPanel = true) {
        stylesInjected = false;
        injectStyles();
        scheduleProcess();

        if (rebuildPanel && document.getElementById(IDS.panel)) createSettingsPanel();
        else updateStatus();
    }

    function resetSettings() {
        Object.assign(SETTINGS, DEFAULT_SETTINGS);
        GM_setValue(SETTINGS_STORAGE_KEY, JSON.stringify(SETTINGS));

        stylesInjected = false;
        injectStyles();
        scheduleProcess();

        if (document.getElementById(IDS.panel)) createSettingsPanel();

        notify('Subtitle settings reset.');
    }

    function setCustomSizesWithPrompt() {
        const normalInput = promptNumber('Normal subtitle size in px:', SETTINGS.customFontSizeNormal);
        if (normalInput === null) return;

        const fullscreenInput = promptNumber('Fullscreen subtitle size in px:', SETTINGS.customFontSizeFullscreen);
        if (fullscreenInput === null) return;

        saveSettings({
            customFontSizeNormal: Math.round(normalInput),
            customFontSizeFullscreen: Math.round(fullscreenInput),
            subtitleSizeMode: 'custom'
        });

        refreshAfterSettingChange(true);
    }

    function handlePanelAction(action) {
        if (action === 'close') {
            document.getElementById(IDS.panel)?.remove();
            return;
        }

        if (action === 'enabled-on') {
            saveSettings({ enabled: true });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'enabled-off') {
            saveSettings({ enabled: false });
            removeSubtitleStyles();
            clearCurrentCaptionInlineStyles();
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'scope-main') {
            saveSettings({ preferYouTubeSizeInSmallPlayers: true });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'scope-all') {
            saveSettings({ preferYouTubeSizeInSmallPlayers: false });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'mode-youtube') {
            saveSettings({ subtitleSizeMode: 'default' });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'mode-script') {
            saveSettings({ subtitleSizeMode: 'script' });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'mode-custom') {
            saveSettings({ subtitleSizeMode: 'custom' });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'set-custom-sizes') {
            setCustomSizesWithPrompt();
            return;
        }

        if (action === 'normal-minus') {
            saveSettings({
                subtitleSizeMode: 'custom',
                customFontSizeNormal: Math.max(10, SETTINGS.customFontSizeNormal - 1)
            });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'normal-plus') {
            saveSettings({
                subtitleSizeMode: 'custom',
                customFontSizeNormal: Math.min(120, SETTINGS.customFontSizeNormal + 1)
            });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'full-minus') {
            saveSettings({
                subtitleSizeMode: 'custom',
                customFontSizeFullscreen: Math.max(10, SETTINGS.customFontSizeFullscreen - 1)
            });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'full-plus') {
            saveSettings({
                subtitleSizeMode: 'custom',
                customFontSizeFullscreen: Math.min(160, SETTINGS.customFontSizeFullscreen + 1)
            });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'bg-mode-line') {
            saveSettings({ perLineBackground: true });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'bg-mode-box') {
            saveSettings({ perLineBackground: false });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'wrap-on') {
            saveSettings({ enableAutoLineBreaks: true });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'wrap-off') {
            saveSettings({ enableAutoLineBreaks: false });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'line-shorter') {
            saveSettings({
                maxCharsPerLine: clampNumber(SETTINGS.maxCharsPerLine - 1, 24, 70, DEFAULT_SETTINGS.maxCharsPerLine)
            });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'line-longer') {
            saveSettings({
                maxCharsPerLine: clampNumber(SETTINGS.maxCharsPerLine + 1, 24, 70, DEFAULT_SETTINGS.maxCharsPerLine)
            });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'position-normal-up') {
            saveSettings({
                positionNormal: clampNumber(SETTINGS.positionNormal + 1, -300, 300, 0)
            });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'position-normal-down') {
            saveSettings({
                positionNormal: clampNumber(SETTINGS.positionNormal - 1, -300, 300, 0)
            });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'position-full-up') {
            saveSettings({
                positionFullscreen: clampNumber(SETTINGS.positionFullscreen + 1, -300, 300, 0)
            });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'position-full-down') {
            saveSettings({
                positionFullscreen: clampNumber(SETTINGS.positionFullscreen - 1, -300, 300, 0)
            });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'position-reset') {
            saveSettings({
                positionNormal: 0,
                positionFullscreen: 0
            });
            refreshAfterSettingChange(true);
            return;
        }

        if (action === 'reset-settings') {
            resetSettings();
        }
    }

    function handlePanelInput(event) {
        const target = event.target;
        if (!target || !target.dataset || !target.dataset.settingKey) return;

        const key = target.dataset.settingKey;

        if (key === 'textColor') {
            saveSettings({
                textColor: sanitizeHexColor(target.value, DEFAULT_SETTINGS.textColor)
            });
            refreshAfterSettingChange(false);
            return;
        }

        if (key === 'backgroundColor') {
            saveSettings({
                backgroundColor: hexToRgbString(target.value)
            });
            refreshAfterSettingChange(false);
            return;
        }

        if (key === 'backgroundOpacity') {
            const value = clampNumber(Number(target.value), 0, 1, DEFAULT_SETTINGS.backgroundOpacity);
            saveSettings({ backgroundOpacity: value });

            const wrap = target.closest('.yt-sf-range-wrap');
            const valueEl = wrap?.querySelector('.yt-sf-range-value');
            if (valueEl) valueEl.textContent = `${Math.round(value * 100)}%`;

            refreshAfterSettingChange(false);
        }
    }

    function globalClickHandler(event) {
        const panel = document.getElementById(IDS.panel);

        const subFixButton =
            event.target.closest(`#${IDS.topButton}`) ||
            event.target.closest(`#${IDS.fallbackButton}`);

        const panelButton = event.target.closest(`#${IDS.panel} button[data-action]`);
        const clickedInsidePanel = panel && panel.contains(event.target);

        if (panel && !clickedInsidePanel && !subFixButton) {
            panel.remove();
            return;
        }

        if (!subFixButton && !panelButton) return;

        event.preventDefault();
        event.stopPropagation();
        event.stopImmediatePropagation();

        if (subFixButton) {
            if (clickLock) return;
            clickLock = true;

            setTimeout(() => {
                clickLock = false;
            }, 250);

            toggleSettingsPanel();
            return;
        }

        if (panelButton) handlePanelAction(panelButton.dataset.action);
    }

    function isSmallPlayerContext(player) {
        if (!player || isFullscreen(player)) return false;

        const rect = player.getBoundingClientRect();
        const width = rect.width || player.clientWidth || 0;
        const height = rect.height || player.clientHeight || 0;

        if (width > 0 && width <= 520) return true;
        if (height > 0 && height <= 320) return true;
        if (!location.pathname.startsWith('/watch')) return true;
        if (player.closest('ytd-miniplayer')) return true;
        if (player.closest('ytd-rich-grid-media')) return true;
        if (player.closest('ytd-compact-video-renderer')) return true;
        if (player.closest('ytd-video-preview')) return true;

        return false;
    }

    function getConfiguredFontSize(full, player) {
        if (SETTINGS.preferYouTubeSizeInSmallPlayers && isSmallPlayerContext(player)) return null;
        if (SETTINGS.subtitleSizeMode === 'default') return null;
        if (SETTINGS.subtitleSizeMode === 'custom') {
            return full ? SETTINGS.customFontSizeFullscreen : SETTINGS.customFontSizeNormal;
        }

        return full ? SETTINGS.fontSizeFullscreen : SETTINGS.fontSizeNormal;
    }

    function injectStyles() {
        if (!SETTINGS.enabled) {
            removeSubtitleStyles();
            return;
        }

        if (stylesInjected && document.getElementById(STYLE_ID)) return;

        let style = document.getElementById(STYLE_ID);

        if (!style) {
            style = document.createElement('style');
            style.id = STYLE_ID;
            document.head.appendChild(style);
        }

        const normalRaise = SETTINGS.offsetNormal + SETTINGS.positionNormal;
        const fullscreenRaise = SETTINGS.offsetFullscreen + SETTINGS.positionFullscreen;
        const playerSelector = '#movie_player.html5-video-player';
        const previewSelector = '.html5-video-player:not(#movie_player)';

        style.textContent = `
            ${previewSelector} .caption-window {
                background: ${SETTINGS.perLineBackground ? 'transparent' : `rgba(${SETTINGS.backgroundColor}, ${SETTINGS.backgroundOpacity})`} !important;
                border-radius: ${SETTINGS.perLineBackground ? '0' : `${SETTINGS.borderRadius}px`} !important;
                padding: ${SETTINGS.perLineBackground ? '0' : `${SETTINGS.paddingY}em ${SETTINGS.paddingX}em`} !important;
            }

            ${previewSelector} .captions-text,
            ${previewSelector} .caption-visual-line,
            ${previewSelector} .ytp-caption-segment,
            ${previewSelector} .captions-text span,
            ${previewSelector} .caption-window span {
                color: ${SETTINGS.textColor} !important;
                text-shadow: ${SETTINGS.textShadow} !important;
                line-height: ${SETTINGS.lineHeight} !important;
            }

            ${previewSelector} .ytp-caption-segment,
            ${previewSelector} .caption-visual-line,
            ${previewSelector} .captions-text span,
            ${previewSelector} .caption-window span {
                background: ${SETTINGS.perLineBackground ? `rgba(${SETTINGS.backgroundColor}, ${SETTINGS.backgroundOpacity})` : 'transparent'} !important;
                padding: ${SETTINGS.perLineBackground ? `${SETTINGS.paddingY}em ${SETTINGS.paddingX}em` : '0'} !important;
                border-radius: ${SETTINGS.perLineBackground ? '0' : '0'} !important;
            }

            ${playerSelector} .ytp-caption-window-container,
            ${playerSelector} .caption-window,
            ${playerSelector} .captions-text,
            ${playerSelector} .caption-visual-line,
            ${playerSelector} .ytp-caption-segment,
            ${playerSelector} .captions-text span,
            ${playerSelector} .caption-window span {
                color: ${SETTINGS.textColor} !important;
                text-shadow: ${SETTINGS.textShadow} !important;
                line-height: ${SETTINGS.lineHeight} !important;
            }

            ${playerSelector} .ytp-caption-window-container {
                text-align: center !important;
                pointer-events: none !important;
                position: absolute !important;
                left: 0 !important;
                right: 0 !important;
                width: 100% !important;
                display: flex !important;
                flex-direction: column !important;
                align-items: center !important;
                justify-content: flex-end !important;
                transform: none !important;
                translate: none !important;
            }

            ${playerSelector} .caption-window {
                background: ${SETTINGS.perLineBackground ? 'transparent' : `rgba(${SETTINGS.backgroundColor}, ${SETTINGS.backgroundOpacity})`} !important;
                border-radius: ${SETTINGS.perLineBackground ? '0' : `${SETTINGS.borderRadius}px`} !important;
                padding: ${SETTINGS.perLineBackground ? '0' : `${SETTINGS.paddingY}em ${SETTINGS.paddingX}em`} !important;
                text-align: center !important;
                max-width: ${SETTINGS.maxWidthPercent}% !important;
                left: auto !important;
                right: auto !important;
                top: auto !important;
                bottom: auto !important;
                margin-left: auto !important;
                margin-right: auto !important;
                align-self: center !important;
                position: relative !important;
                margin: 0 auto !important;
                width: fit-content !important;
                display: table !important;
                transform: translateY(-${normalRaise}px) !important;
                translate: none !important;
            }

            ${playerSelector}.ytp-fullscreen .caption-window {
                transform: translateY(-${fullscreenRaise}px) !important;
            }

            ${playerSelector} .captions-text {
                text-align: center !important;
                left: auto !important;
                right: auto !important;
                top: auto !important;
                bottom: auto !important;
                transform: none !important;
                translate: none !important;
                white-space: pre-wrap !important;
                word-break: normal !important;
                overflow-wrap: break-word !important;
                background: transparent !important;
                padding: 0 !important;
                border-radius: 0 !important;
            }

            ${playerSelector} .ytp-caption-segment,
            ${playerSelector} .caption-visual-line,
            ${playerSelector} .captions-text span,
            ${playerSelector} .caption-window span {
                left: auto !important;
                right: auto !important;
                top: auto !important;
                bottom: auto !important;
                transform: none !important;
                translate: none !important;
                background: transparent !important;
                padding: 0 !important;
                border-radius: 0 !important;
            }
        `;

        stylesInjected = true;
    }

    function removeSubtitleStyles() {
        document.getElementById(STYLE_ID)?.remove();
        stylesInjected = false;
    }

    function clearCurrentCaptionInlineStyles() {
        if (rafId) {
            cancelAnimationFrame(rafId);
            rafId = null;
        }

        const captionElements = document.querySelectorAll(
            '.ytp-caption-window-container, .caption-window, .captions-text, .caption-visual-line, .ytp-caption-segment, .captions-text span, .caption-window span'
        );

        for (const el of captionElements) {
            el.removeAttribute('style');
        }
    }

    function getPlayer() {
        return document.querySelector('#movie_player.html5-video-player');
    }

    function getContainer(player) {
        return player?.querySelector('.ytp-caption-window-container') || null;
    }

    function getCaption(player) {
        if (!player) return null;

        hideCaptionInfoWindows(player);

        return Array.from(player.querySelectorAll('.caption-window'))
            .find(caption => !isCaptionInfoWindow(caption)) || null;
    }

    function getCaptionsText(caption) {
        return caption?.querySelector('.captions-text') || null;
    }

    function getCaptionText(caption) {
        return String(caption?.innerText || caption?.textContent || '').replace(/\s+/g, ' ').trim();
    }

    function isCaptionInfoWindow(caption) {
        const text = getCaptionText(caption).toLowerCase();
        if (!text) return false;

        if (text.includes('click for settings') || text.includes('for settings')) return true;

        return (
            text.includes('auto-generated') && (
                text.includes('click') ||
                text.includes('settings') ||
                text.includes('for settings')
            )
        );
    }

    function hideCaptionInfoWindows(player) {
        if (!player) return;

        const captions = player.querySelectorAll('.caption-window');
        for (const caption of captions) {
            if (!isCaptionInfoWindow(caption)) continue;

            caption.dataset.ytSubFixHiddenInfo = 'true';
            caption.style.setProperty('display', 'none', 'important');
            caption.style.setProperty('visibility', 'hidden', 'important');
            caption.setAttribute('aria-hidden', 'true');
        }
    }

    function isFullscreen(player) {
        return !!player?.classList.contains('ytp-fullscreen');
    }

    function isAutoGeneratedCaptionActive() {
        const video = document.querySelector('video');
        const tracks = Array.from(video?.textTracks || []);

        return tracks.some(track => {
            const label = `${track.label || ''} ${track.language || ''}`.toLowerCase();
            return track.mode === 'showing' && /auto-generated|automatic|asr/.test(label);
        });
    }

    function countVisualLines(caption) {
        if (!caption) return 1;

        const textNode = getCaptionsText(caption);
        if (textNode) {
            const txt = (textNode.innerText || '').trim();
            if (txt) {
                const lines = txt.split('\n').map(v => v.trim()).filter(Boolean);
                if (lines.length > 0) return lines.length;
            }
        }

        const visualLines = caption.querySelectorAll('.caption-visual-line');
        if (visualLines.length > 0) return visualLines.length;

        const segs = caption.querySelectorAll('.ytp-caption-segment');
        return Math.max(segs.length, 1);
    }

    function computeRaise(full, lineCount) {
        const base = full ? SETTINGS.offsetFullscreen : SETTINGS.offsetNormal;
        const position = full ? SETTINGS.positionFullscreen : SETTINGS.positionNormal;

        return base + position + (lineCount - 1) * SETTINGS.extraPerLine;
    }

    function joinWords(words, start, end) {
        return words.slice(start, end).join(' ').trim();
    }

    function tokenizePreservingNotes(line) {
        return line.match(/\[[^\]]+\]|\S+/g) || [];
    }

    function isTranslatorNoteLine(line) {
        return /^\[[\s\S]+\]$/.test(String(line || '').trim());
    }

    function isWeakLineStart(word) {
        return /^(and|or|but|so|yet|for|nor|to|of|in|on|at|by|with|from|into|onto|than|that|who|which|because)$/i.test(word);
    }

    function isWeakLineEnd(word) {
        return /^(a|an|the|and|or|but|so|yet|for|nor|to|of|in|on|at|by|with|from|into|onto|than|that)$/i.test(word);
    }

    function getLinePenalty(text, softMax) {
        if (!text) return 100000;

        const words = text.split(/\s+/).filter(Boolean);
        const firstWord = words[0] || '';
        const lastWord = words[words.length - 1] || '';
        let penalty = Math.abs(text.length - softMax) * 0.35;

        if (text.length > softMax) penalty += (text.length - softMax) * 1.6;
        if (text.length < Math.max(10, Math.floor(softMax * 0.4))) penalty += 25;
        if (isWeakLineStart(firstWord)) penalty += 18;
        if (isWeakLineEnd(lastWord)) penalty += 12;
        if (/^[)\],.!?:;]/.test(firstWord)) penalty += 30;
        if (/[(\[{'"-]$/.test(lastWord)) penalty += 18;

        return penalty;
    }

    function chooseBreaks(words, lineCount, softMax) {
        const totalWords = words.length;
        const memo = new Map();

        function solve(startIndex, linesLeft) {
            const key = `${startIndex}:${linesLeft}`;
            if (memo.has(key)) return memo.get(key);

            if (linesLeft === 1) {
                const text = joinWords(words, startIndex, totalWords);
                const result = {
                    score: getLinePenalty(text, softMax),
                    lines: [text]
                };
                memo.set(key, result);
                return result;
            }

            let best = null;
            const minBreak = startIndex + 1;
            const maxBreak = totalWords - (linesLeft - 1);

            for (let breakIndex = minBreak; breakIndex <= maxBreak; breakIndex++) {
                const current = joinWords(words, startIndex, breakIndex);
                const remaining = solve(breakIndex, linesLeft - 1);
                if (!current || !remaining.lines.length) continue;

                const lengths = [current.length, ...remaining.lines.map(line => line.length)];
                const balancePenalty = Math.max(...lengths) - Math.min(...lengths);
                const score = getLinePenalty(current, softMax) + remaining.score + balancePenalty * 0.75;

                if (!best || score < best.score) {
                    best = {
                        score,
                        lines: [current, ...remaining.lines]
                    };
                }
            }

            const fallback = best || {
                score: 100000,
                lines: [joinWords(words, startIndex, totalWords)]
            };

            memo.set(key, fallback);
            return fallback;
        }

        return solve(0, lineCount).lines.filter(Boolean);
    }

    function wrapLine(line, maxChars) {
        const trimmed = line.trim().replace(/\s+/g, ' ');
        if (!trimmed || trimmed.length <= maxChars) return [trimmed];

        const words = tokenizePreservingNotes(trimmed);
        if (words.length < 2) return [trimmed];

        const softTwoLineLimit = Math.round(maxChars * 2.35);
        const preferredLines = trimmed.length <= softTwoLineLimit ? 2 : Math.min(3, Math.max(2, Math.ceil(trimmed.length / maxChars)));
        const wrapped = chooseBreaks(words, preferredLines, maxChars);

        return wrapped.length ? wrapped : [trimmed];
    }

    function buildWrappedText(text) {
        const maxChars = SETTINGS.maxCharsPerLine;
        const sourceLines = String(text || '')
            .replace(/\r/g, '')
            .split('\n')
            .map(v => v.trim())
            .filter(Boolean);

        if (!sourceLines.length) return '';

        const hasStandaloneTranslatorNote = sourceLines.some(isTranslatorNoteLine);
        const preserveExistingTwoLineLayout = hasStandaloneTranslatorNote && sourceLines.length >= 2;

        return sourceLines
            .flatMap(line => {
                if (!SETTINGS.enableAutoLineBreaks) return [line];
                if (preserveExistingTwoLineLayout) return [line];
                return wrapLine(line, maxChars);
            })
            .join('\n');
    }

    function getDisplayText(caption) {
        const textNode = getCaptionsText(caption);
        if (!textNode) return '';

        const visualLines = Array.from(caption.querySelectorAll('.caption-visual-line'))
            .map(line => (line.innerText || '').trim())
            .filter(Boolean);

        if (visualLines.length > 0) {
            return buildWrappedText(visualLines.join('\n'));
        }

        return buildWrappedText(textNode.innerText || textNode.textContent || '');
    }

    function renderPerLineBackgroundText(textNode, text) {
        const lines = String(text || '')
            .split('\n')
            .map(line => line.trim())
            .filter(Boolean);

        const renderKey = lines.join('\n');
        if (!renderKey) return;

        if (textNode.dataset.ytSubFixRenderedText === renderKey && textNode.querySelector(`.${LINE_BACKGROUND_CLASS}`)) {
            return;
        }

        textNode.replaceChildren();

        lines.forEach((line, index) => {
            if (index > 0) textNode.appendChild(document.createElement('br'));

            const lineEl = document.createElement('span');
            lineEl.className = LINE_BACKGROUND_CLASS;
            lineEl.textContent = line;
            textNode.appendChild(lineEl);
        });

        textNode.dataset.ytSubFixRenderedText = renderKey;
    }

    function applyContainerStyles(container) {
        container.style.position = 'absolute';
        container.style.left = '0';
        container.style.right = '0';
        container.style.width = '100%';
        container.style.display = 'flex';
        container.style.flexDirection = 'column';
        container.style.justifyContent = 'center';
        container.style.alignItems = 'flex-end';
        container.style.pointerEvents = 'none';
        container.style.textAlign = 'center';
        container.style.bottom = '0';
        container.style.margin = '0';
        container.style.padding = '0';
        container.style.transform = 'none';
        container.style.translate = 'none';
        container.style.setProperty('align-items', 'center', 'important');
        container.style.setProperty('justify-content', 'flex-end', 'important');
        container.style.setProperty('transform', 'none', 'important');
        container.style.setProperty('translate', 'none', 'important');
    }

    function applyCaptionStyles(caption, raisePx) {
        caption.style.position = 'relative';
        caption.style.setProperty('left', 'auto', 'important');
        caption.style.setProperty('right', 'auto', 'important');
        caption.style.setProperty('top', 'auto', 'important');
        caption.style.setProperty('bottom', 'auto', 'important');
        caption.style.margin = '0 auto';
        caption.style.textAlign = 'center';
        caption.style.alignSelf = 'center';
        caption.style.width = 'fit-content';
        caption.style.maxWidth = `${SETTINGS.maxWidthPercent}%`;
        caption.style.display = 'table';
        caption.style.transform = `translateY(-${raisePx}px)`;
        caption.style.translate = 'none';
        caption.style.setProperty('background', SETTINGS.perLineBackground ? 'transparent' : `rgba(${SETTINGS.backgroundColor}, ${SETTINGS.backgroundOpacity})`, 'important');
        caption.style.setProperty('padding', SETTINGS.perLineBackground ? '0' : `${SETTINGS.paddingY}em ${SETTINGS.paddingX}em`, 'important');
        caption.style.setProperty('border-radius', SETTINGS.perLineBackground ? '0' : `${SETTINGS.borderRadius}px`, 'important');
    }

    function applyTextStyles(caption, full) {
        const fontSize = getConfiguredFontSize(full, getPlayer());
        const textNode = getCaptionsText(caption);
        const preserveOriginalText = SETTINGS.preserveAutoGeneratedCaptions && isAutoGeneratedCaptionActive();

        if (textNode) {
            if (!preserveOriginalText) {
                const displayText = getDisplayText(caption);
                if (displayText && SETTINGS.perLineBackground) {
                    renderPerLineBackgroundText(textNode, displayText);
                } else if (displayText && textNode.textContent !== displayText) {
                    textNode.textContent = displayText;
                    delete textNode.dataset.ytSubFixRenderedText;
                }
            } else if (textNode.dataset.ytSubFixRenderedText) {
                textNode.textContent = textNode.innerText || textNode.textContent || '';
                delete textNode.dataset.ytSubFixRenderedText;
            }

            textNode.style.display = 'block';
            textNode.style.textAlign = 'center';
            textNode.style.setProperty('left', 'auto', 'important');
            textNode.style.setProperty('right', 'auto', 'important');
            textNode.style.setProperty('top', 'auto', 'important');
            textNode.style.setProperty('bottom', 'auto', 'important');
            textNode.style.setProperty('transform', 'none', 'important');
            textNode.style.setProperty('translate', 'none', 'important');
            textNode.style.whiteSpace = 'pre-wrap';
            textNode.style.wordBreak = 'normal';
            textNode.style.overflowWrap = 'break-word';
            if (fontSize === null) {
                textNode.style.removeProperty('font-size');
            } else {
                textNode.style.fontSize = `${fontSize}px`;
            }
            textNode.style.lineHeight = String(SETTINGS.lineHeight);
            textNode.style.color = SETTINGS.textColor;
            textNode.style.textShadow = SETTINGS.textShadow;
            textNode.style.setProperty('background', 'transparent', 'important');
            textNode.style.setProperty('padding', '0', 'important');
            textNode.style.setProperty('border-radius', '0', 'important');
            textNode.style.margin = '0 auto';
        }

        const all = caption.querySelectorAll('.ytp-caption-segment, .caption-visual-line, .captions-text span, .caption-window span');
        for (const el of all) {
            if (el === textNode) continue;

            if (fontSize === null) {
                el.style.removeProperty('font-size');
            } else {
                el.style.fontSize = `${fontSize}px`;
            }
            el.style.lineHeight = String(SETTINGS.lineHeight);
            el.style.color = SETTINGS.textColor;
            el.style.textShadow = SETTINGS.textShadow;
            el.style.textAlign = 'center';

            if (el.classList.contains(LINE_BACKGROUND_CLASS)) {
                el.style.setProperty('display', 'inline-block', 'important');
                el.style.setProperty('background', `rgba(${SETTINGS.backgroundColor}, ${SETTINGS.backgroundOpacity})`, 'important');
                el.style.setProperty('padding', `${SETTINGS.paddingY}em ${SETTINGS.paddingX}em`, 'important');
                el.style.setProperty('border-radius', '0', 'important');
                el.style.setProperty('margin', '0 auto', 'important');
                continue;
            }

            el.style.setProperty('left', 'auto', 'important');
            el.style.setProperty('right', 'auto', 'important');
            el.style.setProperty('top', 'auto', 'important');
            el.style.setProperty('bottom', 'auto', 'important');
            el.style.setProperty('transform', 'none', 'important');
            el.style.setProperty('translate', 'none', 'important');
            el.style.background = 'transparent';
            el.style.padding = '0';
            el.style.borderRadius = '0';
        }
    }

    function processSubtitles() {
        if (!SETTINGS.enabled) return;

        const player = getPlayer();
        if (!player) return;

        hideCaptionInfoWindows(player);

        const container = getContainer(player);
        const caption = getCaption(player);
        if (!container || !caption) return;

        observersPaused = true;
        if (observerResumeId) clearTimeout(observerResumeId);

        const full = isFullscreen(player);
        applyTextStyles(caption, full);
        const lineCount = countVisualLines(caption);
        const raisePx = computeRaise(full, lineCount);

        applyContainerStyles(container);
        applyCaptionStyles(caption, raisePx);

        observerResumeId = setTimeout(() => {
            observersPaused = false;
            observerResumeId = null;
        }, 0);
    }

    function scheduleProcess() {
        if (observersPaused) return;

        processSubtitles();

        if (rafId) cancelAnimationFrame(rafId);
        rafId = requestAnimationFrame(() => {
            rafId = null;
            processSubtitles();
        });
    }

    function attachCaptionObserver() {
        const player = getPlayer();
        if (!player) return;

        const container = getContainer(player);
        if (!container) return;

        if (captionObserver) captionObserver.disconnect();

        captionObserver = new MutationObserver(() => {
            if (observersPaused) return;
            scheduleProcess();
        });

        captionObserver.observe(container, {
            childList: true,
            subtree: true,
            characterData: true
        });

        scheduleProcess();
    }

    function init() {
        ensureTopButton();
        injectStyles();

        const player = getPlayer();
        if (!player) return false;

        if (playerObserver) playerObserver.disconnect();

        playerObserver = new MutationObserver(() => {
            if (observersPaused) return;
            attachCaptionObserver();
            scheduleProcess();
        });

        playerObserver.observe(player, {
            childList: true,
            subtree: true,
            attributes: false
        });

        attachCaptionObserver();
        scheduleProcess();
        return true;
    }

    function hookEvents() {
        document.addEventListener('click', globalClickHandler, true);
        document.addEventListener('pointerdown', globalClickHandler, true);
        document.addEventListener('input', handlePanelInput, true);
        document.addEventListener('change', handlePanelInput, true);

        document.addEventListener('fullscreenchange', () => {
            scheduleProcess();
            setTimeout(scheduleProcess, 150);
        });

        window.addEventListener('yt-navigate-finish', () => {
            stylesInjected = false;
            injectStyles();
            setTimeout(ensureTopButton, 200);
            setTimeout(init, 200);
            setTimeout(scheduleProcess, 500);
        });

        setInterval(() => {
            ensureTopButton();
            updateStatus();
        }, 1500);
    }

    injectUiStyles();
    ensureTopButton();
    injectStyles();
    hookEvents();

    const wait = setInterval(() => {
        if (init()) clearInterval(wait);
    }, 500);
})();