YouTube Speed Controller Pro

YouTube persistent speed slider overlay, minimize toggle. Pro Features: Automatic 1x speed for Music/Movies, intelligent chapter navigation, and high-fidelity Pitch Shift toggle.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         YouTube Speed Controller Pro
// @namespace    http://tampermonkey.net/
// @version      3.2.68
// @description  YouTube persistent speed slider overlay, minimize toggle. Pro Features: Automatic 1x speed for Music/Movies, intelligent chapter navigation, and high-fidelity Pitch Shift toggle.
// @author       AI-generated with assistance from Gemini 3 Flash based on concepts by RickZabel
// @license      CC-BY-NC-SA-4.0
// @copyright    2026, RickZabel (https://greasyfork.org/en/users/5920-rickzabel)
// @icon         data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHdpZHRoPSIxMDI0IiBoZWlnaHQ9IjEwMjQiIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmMDAwMCIgZD0iTSA1MTIgNzQ1IGMgMCAwIDIxMiAwIDI2NCAtMTQgMjkgLTcgNTEgLTMwIDYwIC01OSAxNCAtNTEgMTQgLTE2MCAxNCAtMTYwIDAgMCAwIC0xMDggLTE0IC0xNjAgLTggLTI5IDMwIC01MSAtNjAgLTU5IC01MiAtMTQgLTI2NCAtMTQgLTI2NCAtMTQgMCAwIC0yMTEgMCAtMjY0IDE0IC0yOSA3IC01MSAzMCAtNjAgNTkgLTE0IDUyIC0xNCAxNjAgLTE0IDE2MCAwIDAgMCAxMDkgMTQgMTYwIDggMjkgMzAgNTEgNjAgNTkgNTIgMTQgMjY0IDE0IDI2NCAxNCB6Ii8+PHBhdGggZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9Ijg0IiBkPSJNIDY4MyA1MTIgbCAtMjcgLTE1IHYgMzEgei BNIDUwNiA1MTIgbCAtMjcgLTE1IHYgMzEgeiIvPjxwYXRoIGZpbGw9IiNmZmZmZmYiIGQ9Ik0gMzMwIDQyMSBoIDQxIHYgMTgyIGggLTQxIHogTSAyNTUgNDIxIGggNDEgdiAxODIgaCAtNDEgeiIvPjwvc3ZnPg==
// @match        *://*.youtube.com/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

/**
 * CHROME USERS: If this script does not appear to load:
 * 1. Go to chrome://extensions/
 * 2. Ensure "Developer mode" (top right) is ON.
 * 3. Click "Details" on the Tampermonkey extension.
 * 4. Scroll down and ensure "Allow user scripts" is ON.
 * 5. Ensure "Allow access to file URLs" is ON.
 */

/*
  AI-generated with assistance from Gemini 3 Flash based on concepts by RickZabel

  MASTER PROMPT / REBUILD RECIPE (v3.2.67):
  "Create a Tampermonkey script for YouTube that adds a persistent, draggable speed control overlay with a License/Pro system.

  Core Features & UI:
  - Precision Controls: Granular slider (0.05 increments) and presets (Pause to 2x).
  - Custom Logo: Must use specific SVG paths (lp1, lp2, lp3) with Transformation Matrix (6.201...) and colors (#ff0000, #ffffff). Acts as a minimize toggle with hold-to-manage license logic.
  - Transparency: 25% step toggle using a dynamic SVG pie-chart icon; persists via localStorage.
  - Smart Tooltips: 0.8s transition delay, 11px normal weight text, and 'Smart Boundary' logic to auto-flip orientation at window edges.
  - Color Palette: Primary text/icons #888888; Slider thumb dark red (#880000) switching to bright red (#ff0000) on hover. Pro 'Override' state uses green (#00ff00).

  Layout & Structure:
  - Main Container: Flexbox row alignment with 8px gap. Background: rgba(0,0,0,0.8) with 8px border-radius.
  - Minimized State: When minimized, the container shrinks to only the Logo. A dedicated drag handle (dots icon) must appear to the left of the logo only when minimized to allow repositioning.
  - Pro Button Group: A dedicated sub-container (.pro-btns) using 'display: flex; gap: 3px; flex: 1; overflow: visible !important;' to house Pro toggles.

  Pro Features (License Key Required):
  - Chapter Navigation: « and » arrows with fixed -2.1px top offset and 18px font size.
  - Auto-Speed Logic: Context-aware 1x speed override for 'Music', 'Movies', and 'Shows' categories.
  - High-Fidelity Audio: 'P' toggle to disable YouTube's pitch correction (preservesPitch = false).
  - Analytics: Lifetime 'Time Saved' tracker based on playback rate vs. actual duration.

  Logic & Correctness Constraints:
  - Vertical Centering: Pause button text wrapped in a span with -1.8px top nudge for visual balance.
  - Playback Preservation: Toggling Pro buttons (Music/Movies/P) while paused must never trigger auto-resume.
  - License Guard: checkLicense() must immediately disable all Pro logic and reset audio fidelity if the key is removed.
  - Persistence: LocalStorage must remember speed, coordinates, opacity, settings, and minimized state."
*/


(function() {
    'use strict';

    const GREASYFORK_URL = 'https://greasyfork.org/en/users/5920-rickzabel';
    const VALID_KEY = '1234';

    let isProUser = false;
    let lastActiveSpeed = parseFloat(localStorage.getItem('ytPersistentSpeed')) || 1.0;
    let prePauseSpeed = lastActiveSpeed;
    let isMinimized = localStorage.getItem('ytSpeedMinimized') === 'true';
    let opacityLevel = parseFloat(localStorage.getItem('ytSpeedOpacity')) || 1.0;
    let pitchShiftEnabled = localStorage.getItem('ytPitchShiftEnabled') === 'true';
    let totalSecondsSaved = parseFloat(localStorage.getItem('ytTotalTimeSaved')) || 0;
    let lastTimestamp = 0;
    let autoSpeedActive = false;

    let proSettings = JSON.parse(localStorage.getItem('ytProSettings')) || { musicOneX: false, movieOneX: false };
    const categoryMap = { "1": "Film & Animation", "10": "Music", "30": "Movies", "43": "Shows" };

    function updateStateFromStorage() {
        isProUser = localStorage.getItem('ytSpeed_ProKey') === VALID_KEY;
        pitchShiftEnabled = isProUser && localStorage.getItem('ytPitchShiftEnabled') === 'true';
    }

    function checkLicense() {
            const key = localStorage.getItem('ytSpeed_ProKey');
            const wasPro = isProUser;
            isProUser = (key === VALID_KEY);

            // MANDATORY: If license is no longer valid, kill all Pro logic
            if (!isProUser) {
                pitchShiftEnabled = false;
                proSettings.musicOneX = false;
                proSettings.movieOneX = false;
                autoSpeedActive = false;
                localStorage.setItem('ytPitchShiftEnabled', 'false');
                saveProSettings();

                const v = document.querySelector('video');
                if (v) v.preservesPitch = true; // Restore default high-quality audio
            }

            // Refresh the overlay to hide/show Pro sections based on the new state
            if (document.getElementById('yt-speed-overlay')) redrawOverlay();
        }
    function saveProSettings() { if (isProUser) localStorage.setItem('ytProSettings', JSON.stringify(proSettings)); }
    function isDarkMode() { return document.documentElement.getAttribute('dark') !== null || window.matchMedia('(prefers-color-scheme: dark)').matches; }

    function trackTimeSaved() {
        if (!isProUser) return;
        const video = document.querySelector('video');
        if (!video || video.paused || video.playbackRate <= 1.0) { lastTimestamp = 0; return; }
        if (lastTimestamp === 0) { lastTimestamp = video.currentTime; return; }
        const actualTimePassed = video.currentTime - lastTimestamp;
        if (actualTimePassed > 0 && actualTimePassed < 1) {
            const saved = actualTimePassed - (actualTimePassed / video.playbackRate);
            totalSecondsSaved += saved;
            localStorage.setItem('ytTotalTimeSaved', totalSecondsSaved);
        }
        lastTimestamp = video.currentTime;
    }

    function updateSavedDisplay() {
        const valEl = document.getElementById('time-saved-stat');
        if (!valEl || !isProUser) return;
        let s = totalSecondsSaved;
        const years = Math.floor(s / 31536000), days = Math.floor((s % 31536000) / 86400), hrs = Math.floor((s % 86400) / 3600), mins = Math.floor((s % 3600) / 60), secs = Math.floor(s % 60);
        let parts = [];
        if (years > 0) parts.push(years + "y"); if (days > 0) parts.push(days + "d"); if (hrs > 0) parts.push(hrs + "h"); if (mins > 0) parts.push(mins + "m"); if (secs > 0 || parts.length === 0) parts.push(secs + "s");
        valEl.textContent = "Time Saved: " + parts.join(" ");
    }

    function getCategoryInfo() {
        try {
            let id = window.ytInitialPlayerResponse?.videoDetails?.categoryId;
            let name = categoryMap[id];
            if (!id || !name) {
                const catStr = window.ytInitialPlayerResponse?.microformat?.playerMicroformatRenderer?.category;
                if (catStr) { name = catStr; if (catStr === "Music") id = "10"; else if (catStr === "Movies" || catStr === "Film & Animation") id = "30"; else if (catStr === "Shows") id = "43"; }
            }
            return { id: id ? String(id) : "N/A", name: name || "Not Detected" };
        } catch (e) { }
        return { id: "N/A", name: "Not Detected" };
    }

    function checkCategoryAndAdjustSpeed() {
        if (!isProUser) return;
        const info = getCategoryInfo();
        const isMusicActive = (info.id === "10" || info.name === "Music") && proSettings.musicOneX;
        const isMovieActive = (["30", "43", "1"].includes(info.id) || ["Movies", "Shows", "Film & Animation"].includes(info.name)) && proSettings.movieOneX;
        autoSpeedActive = (isMusicActive || isMovieActive);
        const target = autoSpeedActive ? 1.0 : lastActiveSpeed;
        applySpeedToVideo(target);
        updateUI(target);
    }

    function navigateChapter(direction) {
        const video = document.querySelector('video'); if (!video) return;
        const chapters = getChapters(), currentTime = video.currentTime;
        if (chapters.length === 0) return;
        if (direction === 'next') { const next = chapters.find(t => t > currentTime + 5); if (next !== undefined) video.currentTime = next; }
        else { const prevIndex = chapters.findLastIndex(t => t < currentTime - 3); video.currentTime = prevIndex !== -1 ? chapters[prevIndex] : 0; }
    }

    function getChapters() {
        const video = document.querySelector('video'); if (!video || isNaN(video.duration)) return [];
        let chapters = new Set();
        document.querySelectorAll('.ytp-chapter-hover-container, .ytp-chapter-marker').forEach(el => {
            const leftPct = parseFloat(el.style.left);
            if (!isNaN(leftPct)) chapters.add((leftPct / 100) * video.duration);
        });
        let sorted = Array.from(chapters).sort((a, b) => a - b);
        if (sorted.length > 0 && sorted[0] > 2) sorted.unshift(0);
        return sorted;
    }

    function checkTooltipPos(e) {
        const el = e.currentTarget;
        el.classList.remove('tt-left', 'tt-right', 'tt-down');
        const rect = el.getBoundingClientRect();
        if (rect.left < 60) el.classList.add('tt-left');
        else if (window.innerWidth - rect.right < 60) el.classList.add('tt-right');
        if (rect.top < 60) el.classList.add('tt-down');
    }

    function ensureInView() {
        const container = document.getElementById('yt-speed-overlay');
        if (!container) return;
        const rect = container.getBoundingClientRect();
        const isOut = (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth);
        if (isOut) performReset();
    }

    function performReset() {
        localStorage.setItem('ytSpeedPos', JSON.stringify({ top: '60px', left: '20px' }));
        redrawOverlay();
    }

    let logoHoldTimer, countdownInterval, holdSecondsRemaining = 3, isLicenseMode = false;

    function redrawOverlay() {
        updateStateFromStorage();
        const e1 = document.getElementById('yt-speed-overlay'), e2 = document.getElementById('yt-speed-styles');
        if (e1) e1.remove(); if (e2) e2.remove();
        setTimeout(inject, 0);
    }

    function getTransparencyPath(opacity) {
        if (opacity === 0.25) return "M50,50 L50,0 A50,50 0 0,1 100,50 Z";
        if (opacity === 0.50) return "M50,50 L50,0 A50,50 0 0,1 100,50 A50,50 0 0,1 50,100 Z";
        if (opacity === 0.75) return "M50,50 L50,0 A50,50 0 0,1 100,50 A50,50 0 0,1 50,100 A50,50 0 0,1 0,50 Z";
        return "M50,50 m-50,0 a50,50 0 1,0 100,0 a50,50 0 1,0 -100,0";
    }

    function inject() {
        if (document.getElementById('yt-speed-overlay')) return;
        updateStateFromStorage();
        if (!document.body) return;

        const dark = isDarkMode();
        const colors = {
            //usage name: check ? 'false' : 'true'
            bg: dark ? 'rgba(15, 15, 15, 0.95)' : 'rgba(255, 255, 255, 0.95)',
            text: dark ? '#ffffff' : '#000000',
            accentBright: '#ff0000',
            accentDark: '#880000',
            muted: '#888888',
            border: dark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)',
            //btnBg: dark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)',
            btnBg: dark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)',
            btnHover: dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.1)',
            btnText: dark ? '#ffffff' : '#000000',
            tooltipBg: '#111111',
            activeGreen: '#2ecc71',
        };

        const style = document.createElement('style');
        style.id = 'yt-speed-styles';
        style.textContent = `

        #yt-speed-overlay {
            position: fixed;
            bottom: 150px;
            left: 20px;
            width: 330px;
            background: ${colors.bg} !important;
            color: ${colors.text} !important;
            padding: 6px;
            border-radius: 12px;
            backdrop-filter: blur(12px);
            border: 1px solid ${colors.border};
            z-index: 2147483647 !important;
            font-family: "Segoe UI", Roboto, sans-serif;
            font-size: 0px !important;
            user-select: none;
            opacity: ${opacityLevel};
            transition: opacity 0.3s, width 0.3s, height 0.3s;
            /*box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);*/
            touch-action: none;
            /*gap: 12px;*/
            display: block;
            box-sizing: border-box !important;
            line-height: normal !important;
        }


        #yt-speed-overlay.minimized {
            width: 42px !important;
            height: 42px !important;
            padding: 0 !important;
            overflow: hidden;
            align-items: center;
            justify-content: center;
        }

        #yt-speed-overlay.minimized .header-row,
        #yt-speed-overlay.minimized .chapter-nav,
        #yt-speed-overlay.minimized .main-content-area,
        #yt-speed-overlay.minimized .center-group,
        #yt-speed-overlay.minimized .trans-toggle,
        #yt-speed-overlay.minimized .reset-btn,
        #yt-speed-overlay.minimized .right-group {
            display: none !important;
        }

        #yt-speed-overlay.minimized .top-bar {
            margin: 0;
            width: 100%;
            height: 100%;
            display: flex;
            flex-direction: column;
            justify-content: flex-start;
            align-items: center;
        }

        #yt-speed-overlay.minimized .min-drag-dots {
            display: block !important;
            width: 100%;
            text-align: center;
            font-size: 8px;
            color: ${colors.muted};
            cursor: move;
            line-height: 8px;
            margin-top: 1px;
        }

        #yt-speed-overlay.minimized .left-group {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 100%;
        }

        #yt-speed-overlay:hover {
            opacity: 1 !important;
        }

        .header-row {
            display: flex;
            align-items: center;
            height: 12px;
            margin-bottom: 2px;
            position: relative;
        }

        .script-title {
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            font-size: 10.5px;
            color: ${colors.muted};
            text-transform: uppercase;
            letter-spacing: 0.5px;
            opacity: 0.8;
            white-space: nowrap;
            cursor: move;
        }

        .top-bar {
            display: flex;
            align-items: center;
            height: 14px;
            margin-bottom: 0px;
            position: relative;
            padding-top: 2px;
            padding-bottom: 2px;
        }

        .left-group {
            display: flex;
            align-items: center;
            flex: 0 0 40px;
        }

        .center-group {
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            align-items: center;
            justify-content: center;
            pointer-events: none;
        }

        .right-group {
            display: flex;
            align-items: center;
            gap: 3px;
            margin-left: auto;
        }

        .drag-handle {
            display: flex;
            justify-content: center;
            align-items: center;
            cursor: move;
            font-size: 10px;
            color: ${colors.text};
            opacity: 0.7;
            padding: 0 2px;
        }

        .min-drag-dots {
            display: none;
        }

        [data-tooltip] {
            position: relative;
        }

        /*[data-tooltip]:hover:before { opacity: 1; visibility: visible; }*/

        [data-tooltip].tt-left:before {
            left: 0;
            transform: translateX(0);
        }

        [data-tooltip].tt-right:before {
            left: auto;
            right: 0;
            transform: translateX(0);
        }


        [data-tooltip]:before {
            content: attr(data-tooltip);
            position: absolute !important;
            /* Prevents button squishing */
            bottom: 100%;
            left: 50%;
            transform: translateX(-50%);
            padding: 5px 9px;
            background: #111111 !important;
            color: #ffffff !important;
            font-size: 11px !important;
            /* Uniform font size */
            font-weight: normal !important;
            /* Uniform normal weight */
            border-radius: 4px;
            white-space: nowrap;
            opacity: 0;
            visibility: hidden;
            transition: opacity 0.2s, transform 0.2s;
            transition-delay: 1.8s;
            z-index: 2147483649;
            pointer-events: none;
            border: none !important;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
        }

        [data-tooltip]:hover:before {
            opacity: 1;
            visibility: visible;
        }

        /* Smart Boundary Fixes: Flips tips inward when near screen edges */
        #yt-speed-overlay.edge-right [data-tooltip]:before {
            left: auto !important;
            right: 0 !important;
            transform: translateX(0) !important;
        }

        #yt-speed-overlay.edge-left [data-tooltip]:before {
            left: 0 !important;
            right: auto !important;
            transform: translateX(0) !important;
        }

        .pro-btns [data-tooltip]:before,
        .logo-container [data-tooltip]:before,
        .chapter-nav [data-tooltip]:before {
            bottom: 100% !important;
        }

        /*[data-tooltip].tt-down:before { bottom: auto; top: 100%; }*/

        [data-tooltip].tt-down:before {
            bottom: auto !important;
            top: 140% !important;
        }


        .pro-area,
        .pro-btns {
            overflow: visible !important;
        }

        .pro-area {
            overflow: visible !important;
        }

        .logo-container {
            cursor: pointer;
            transition: transform 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
            transform: translate(-4px, -10px);
        }

        .logo-container:hover {
            transform: translate(-4px, -10px) scale(1.1);
        }

        #yt-speed-overlay.minimized .logo-container {
            transform: translate(0px, 0px);
        }

        .chapter-nav {
            display: flex;
            align-items: center;
            gap: 6px;
            font-size: 18px;
            color: ${colors.muted};
        }

        .chapter-nav span:hover {
            color: #ff0033;
            cursor: pointer;
        }

        .trans-toggle {
            cursor: pointer;
            display: flex;
            align-items: center;
            padding: 0 2px;
            /*opacity: 0.6;*/
            transform: translate(-0px, 1.4px);
        }

        .trans-toggle svg {
            width: 12px;
            height: 12px;
            fill: ${colors.muted};
        }

        .trans-toggle:hover {
            opacity: 1;
        }

        #speed-val {
            font-weight: bold;
            font-size: 9px !important;
            color: ${colors.muted};
            text-align: center;
            pointer-events: auto;
        }

        #speed-val.override {
            color: ${colors.activeGreen} !important;
        }

        .gf-link {
            margin-left: auto;
            display: flex;
            align-items: center;
        }

        .gf-link svg {
            width: 10px;
            height: 10px;
            fill: ${colors.muted};
            transition: fill 0.2s;
        }

        .gf-link:hover svg {
            fill: #ff0033;
        }


        /*f2f2f2 hover e5e5e5*/
        .reset-btn {
            cursor: pointer;
            font-size: 16px;
            color: ${colors.muted};
            transition: color 0.2s;
        }

        .reset-btn:hover {
            color: #ff0033;
        }

        .preset-btn {
            flex: 1;
            /*background: ${colors.btnBg};*/
            background: ${colors.btnBg} !important;
            border: none;
            color: ${colors.btnText};
            padding: 7.5px 0;
            cursor: pointer;
            font-size: 12px;
            border-radius: 4px;
            transition: all 0.2s;
            height: 33px;
            min-width: 36px;
            border-radius: 18px; /* Makes them pill-shaped like YouTube buttons */
        }

        .preset-btn:hover {
            /*background: rgba(255, 255, 255, 0.15) !important;*/
            background: ${colors.btnHover} !important;
            /*color: #ff0033 !important;*/
        }

        .preset-btn.active {
            color: #ff0033 !important;
            font-weight: bold;
        }

        .preset-btn.override {
            color: ${colors.activeGreen} !important;
        }

        .preset-btn.active.pro-toggle {
            color: ${colors.activeGreen} !important;
        }

        #speed-slider {
            width: 100%;
            height: 4px;
            background: #444;
            -webkit-appearance: none;
            border-radius: 2px;
            margin: 4px 0 8px 0;
            cursor: pointer;
        }

        #speed-slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            height: 10px;
            width: 10px;
            border-radius: 50%;
            background: #ff0033;
            transition: background 0.2s;
        }

        #yt-speed-overlay.pro-active #speed-slider::-webkit-slider-thumb {
            background: ${colors.activeGreen} !important;
        }

        #yt-speed-overlay.is-paused #speed-slider::-webkit-slider-thumb {
            background: #ff0033 !important;
        }


        #speed-slider.override::-webkit-slider-thumb {
            background: #00ff00 !important; /* Pro Green */
        }


        .pro-area {
            display: flex;
            flex-direction: column;
            gap: 3px;
            margin-top: 6px;
            border-top: 1px solid ${colors.border};
        }

        .pro-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        .pro-btns {
            display: flex;
            gap: 3px;
            flex: 1;
            overflow: visible !important;
        }

        #time-saved-stat {
            font-size: 12px;
            color: ${colors.muted};
            white-space: nowrap;
            margin-left: 5px;
        }

        .chapter-nav {
            flex: 1;
            background: transparent !important;
            border: none;
            color: ${colors.muted};
            padding: 0 6px 0 0;
            cursor: pointer;
            font-size: 27px;
            transition: color 0.2s;
            display: inline-flex;
            align-items: center;
            justify-content: flex-end;
            position: relative;
            top: -3.15px;
        }

        .chapter-nav.data-tooltip:hover {
            color: #ffffff !important;
            background: transparent !important;
        }

        .stats-row {
            font-size: 9px;
            color: ${colors.muted};
            text-align: left;
            display: flex;
            align-items: center;
            justify-content: space-between;
            height: 15px;
        }

        .license-mgr {
            display: flex;
            flex-direction: column;
            gap: 8px;
            padding: 10px;
            background: rgba(0, 0, 0, 0.3);
            border-radius: 6px;
        }

        .license-mgr input {
            background: #222;
            border: 1px solid #444;
            color: #fff;
            padding: 6px;
            border-radius: 4px;
            font-size: 11px;
            width: 100%;
            box-sizing: border-box;
        }

        .license-mgr button {
            background: ${colors.activeGreen};
            border: none;
            color: #000;
            padding: 6px;
            border-radius: 4px;
            font-weight: bold;
            cursor: pointer;
        }

        `;
        document.head.appendChild(style);

        const container = document.createElement('div'); container.id = 'yt-speed-overlay';
        if (isMinimized) container.classList.add('minimized');

        const svgNS = "http://www.w3.org/2000/svg", headerRow = document.createElement('div'); headerRow.className = 'header-row';
        const title = document.createElement('div'); title.className = 'script-title'; title.textContent = 'YouTube Speed Controller Pro';

        const gfLink = document.createElement('a'); gfLink.className = 'gf-link'; gfLink.href = GREASYFORK_URL; gfLink.target = '_blank'; gfLink.setAttribute('data-tooltip', "Rick's Scripts"); gfLink.onmouseover = checkTooltipPos;
        const gfSvg = document.createElementNS(svgNS, "svg"); gfSvg.setAttribute("viewBox", "0 0 24 24");
        const gfPath = document.createElementNS(svgNS, "path"); gfPath.setAttribute("d", "M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z");
        gfSvg.appendChild(gfPath); gfLink.appendChild(gfSvg);

        const titleDragHandle = document.createElement('div'); titleDragHandle.className = 'drag-handle'; titleDragHandle.textContent = '••••';

        headerRow.appendChild(title); headerRow.appendChild(gfLink); headerRow.appendChild(titleDragHandle);

        const topBar = document.createElement('div'); topBar.className = 'top-bar';
        const minDrag = document.createElement('div'); minDrag.className = 'min-drag-dots'; minDrag.textContent = '••••';
        const leftGroup = document.createElement('div'); leftGroup.className = 'left-group';
        const logoWrap = document.createElement('div'); logoWrap.className = 'logo-container';
        logoWrap.onmouseover = checkTooltipPos;
        logoWrap.onmousedown = () => {
            if (isLicenseMode) return;
            holdSecondsRemaining = 3; logoWrap.setAttribute('data-tooltip', `Hold: ${holdSecondsRemaining}s...`);
            countdownInterval = setInterval(() => { holdSecondsRemaining--; logoWrap.setAttribute('data-tooltip', `Hold: ${holdSecondsRemaining}s...`); if (holdSecondsRemaining <= 0) clearInterval(countdownInterval); }, 1000);
            logoHoldTimer = setTimeout(() => { isLicenseMode = true; isMinimized = false; redrawOverlay(); }, 3000);
        };
        logoWrap.onmouseup = logoWrap.onmouseleave = () => { clearTimeout(logoHoldTimer); clearInterval(countdownInterval); logoWrap.setAttribute('data-tooltip', 'Hold 3s to Manage License'); };
        logoWrap.onclick = () => { if (holdSecondsRemaining >= 1 && !isLicenseMode) { isMinimized = !isMinimized; localStorage.setItem('ytSpeedMinimized', isMinimized); redrawOverlay(); } };

        const logoSvg = document.createElementNS(svgNS, "svg"); logoSvg.setAttribute("viewBox", "0 0 1024 1024");
        logoSvg.style.width = isMinimized ? "32px" : "40px"; logoSvg.style.height = isMinimized ? "32px" : "40px";
        const lp1 = document.createElementNS(svgNS, "path"); lp1.setAttribute("fill", "#ff0033"); lp1.setAttribute("d", "M 511.65 745.41 c 0 0 212.05 0 264.64 -14.01 29.62 -7.94 51.89 -30.81 59.7 -59.05 14.45 -51.82 14.45 -160.82 14.45 -160.82 0 0 0 -108.3 -14.45 -159.65 C 828.18 322.94 805.9 300.53 776.29 292.83 723.69 278.59 511.65 278.59 511.65 278.59 c 0 0 -211.57 0 -263.94 14.24 -29.14 7.7 -51.89 30.11 -60.18 59.05 -13.98 51.35 -13.98 159.65 -13.98 159.65 0 0 0 109 13.98 160.82 8.29 28.24 31.04 51.12 60.18 59.05 52.36 14.01 263.94 14.01 263.94 14.01 z");
        const lp2 = document.createElementNS(svgNS, "path"); lp2.setAttribute("fill", "#ffffff"); lp2.setAttribute("stroke", "#ffffff"); lp2.setAttribute("stroke-width", "84"); lp2.setAttribute("d", "M 683 512 l -27 -15 v 31 z M 506 512 l -27 -15 v 31 z");
        const lp3 = document.createElementNS(svgNS, "path"); lp3.setAttribute("fill", "#ffffff"); lp3.setAttribute("d", "M 330 421 h 41 v 182 h -41 z M 255 421 h 41 v 182 h -41 z");
        logoSvg.appendChild(lp1); logoSvg.appendChild(lp2); logoSvg.appendChild(lp3); logoWrap.appendChild(logoSvg);
        leftGroup.appendChild(logoWrap);

        const centerGroup = document.createElement('div'); centerGroup.className = 'center-group';
        const speedVal = document.createElement('span'); speedVal.id = 'speed-val';
        centerGroup.appendChild(speedVal);

        const rightGroup = document.createElement('div'); rightGroup.className = 'right-group';

        const transTog = document.createElement('div');
        transTog.className = 'trans-toggle';
        transTog.setAttribute('data-tooltip', `Transparency: ${opacityLevel * 100}%`);
        transTog.onmouseover = checkTooltipPos;
        const transSvg = document.createElementNS(svgNS, "svg");
        transSvg.setAttribute("viewBox", "0 0 100 100");
        const transCircle = document.createElementNS(svgNS, "circle");
        transCircle.setAttribute("cx", "50");
        transCircle.setAttribute("cy", "50");
        transCircle.setAttribute("r", "45");
        transCircle.setAttribute("fill", colors.btnBg);
        transCircle.setAttribute("stroke", colors.btnHover);
        transCircle.setAttribute("stroke-width", "5");
        const transPath = document.createElementNS(svgNS, "path");
        transPath.setAttribute("fill", colors.btnHover);
        transPath.setAttribute("d", getTransparencyPath(opacityLevel));
        transSvg.appendChild(transCircle);
        transSvg.appendChild(transPath);
        transTog.appendChild(transSvg);

        transTog.onclick = () => {
            opacityLevel = opacityLevel >= 1.0 ? 0.25 : opacityLevel + 0.25;
            localStorage.setItem('ytSpeedOpacity', opacityLevel);
            container.style.opacity = opacityLevel;
            transTog.setAttribute('data-tooltip', `Transparency: ${opacityLevel * 100}%`);
            transPath.setAttribute("d", getTransparencyPath(opacityLevel));
        };

        const resetBtn = document.createElement('div'); resetBtn.className = 'reset-btn'; resetBtn.textContent = '↺'; resetBtn.setAttribute('data-tooltip', 'Reset Position'); resetBtn.onmouseover = checkTooltipPos;
        resetBtn.onclick = performReset;

        rightGroup.appendChild(transTog); rightGroup.appendChild(resetBtn);
        topBar.appendChild(minDrag); topBar.appendChild(leftGroup); topBar.appendChild(centerGroup); topBar.appendChild(rightGroup);

        const mainContent = document.createElement('div'); mainContent.className = 'main-content-area';
        if (isLicenseMode) {
            const lm = document.createElement('div'); lm.className = 'license-mgr';
            const input = document.createElement('input'); input.placeholder = 'License Key...'; input.value = localStorage.getItem('ytSpeed_ProKey') || "";

            const ok = document.createElement('button'); ok.textContent = 'OK';
            //ok.onclick = () => { localStorage.setItem('ytSpeed_ProKey', input.value.trim()); isLicenseMode = false; redrawOverlay(); };
            ok.onclick = () => {
                localStorage.setItem('ytSpeed_ProKey', input.value.trim());
                isLicenseMode = false;
                checkLicense(); // Triggers the shutdown if the key is wrong
            };
            lm.appendChild(input); lm.appendChild(ok); mainContent.appendChild(lm);
        } else {
            const slider = document.createElement('input'); slider.type = 'range'; slider.id = 'speed-slider'; slider.min = '0'; slider.max = '2.0'; slider.step = '0.05'; slider.oninput = (e) => updateSpeed(e.target.value, true);
            const btnRow = document.createElement('div'); btnRow.style.display="flex"; btnRow.style.gap="3px";
            const ps = [{t:'⏸', type:'pause'}, {t:'.25', v:0.25}, {t:'.75', v:0.75}, {t:'1x', v:1}, {t:'1.25', v:1.25}, {t:'1.5', v:1.5}, {t:'2x', v:2}];
            ps.forEach(item => {
                //const b = document.createElement('button'); b.className = 'preset-btn'; b.textContent = item.t;
                const b = document.createElement('button');
                b.className = 'preset-btn';
                if (item.type === 'pause') {
                    b.id = 'pause-toggle-btn';
                    // Wrap text in a span so we can move just the text in CSS
                    const textSpan = document.createElement('span');
                    textSpan.textContent = item.t;
                    textSpan.style.position = 'relative';
                    textSpan.style.top = '-2.7px'; // Exact match to your chapter nav
                    b.appendChild(textSpan);
                    b.onclick = () => togglePause();
                } else {
                    b.textContent = item.t;
                    b.onclick = () => updateSpeed(item.v, true);
                }
                if (item.type === 'pause') { b.id = 'pause-toggle-btn'; b.onclick = () => togglePause(); } else b.onclick = () => updateSpeed(item.v, true);
                btnRow.appendChild(b);
            });
            mainContent.appendChild(slider); mainContent.appendChild(btnRow);
            if (isProUser) {
                const proArea = document.createElement('div'); proArea.className = 'pro-area';
                const stats = document.createElement('div'); stats.className = 'stats-row';
                const timeSpan = document.createElement('span'); timeSpan.id = 'time-saved-stat';

                const chapNav = document.createElement('div'); chapNav.className = 'chapter-nav';
                const prevC = document.createElement('span'); prevC.textContent = '«'; prevC.setAttribute('data-tooltip', 'Prev Chapter'); prevC.onmouseover = checkTooltipPos; prevC.onclick = (e) => { e.stopPropagation(); navigateChapter('prev'); };
                const nextC = document.createElement('span'); nextC.textContent = '»'; nextC.setAttribute('data-tooltip', 'Next Chapter'); nextC.onmouseover = checkTooltipPos; nextC.onclick = (e) => { e.stopPropagation(); navigateChapter('next'); };
                chapNav.appendChild(prevC); chapNav.appendChild(nextC);

                stats.appendChild(timeSpan); stats.appendChild(chapNav);

                const proBtns = document.createElement('div'); proBtns.className = 'pro-btns';

                const mBtn = document.createElement('button');
                mBtn.className = 'preset-btn';
                mBtn.textContent = 'Music';
                mBtn.setAttribute('data-tooltip', 'Music Mode (1x)'); // Corrected
                mBtn.onmouseover = checkTooltipPos; // Corrected
                mBtn.onclick = () => {
                    proSettings.musicOneX = !proSettings.musicOneX;
                    saveProSettings();
                    const info = getCategoryInfo();
                    const isNowMusic = (info.id === "10" || info.name === "Music") && proSettings.musicOneX;

                    const v = document.querySelector('video');
                    if (v && !v.paused) {
                        checkCategoryAndAdjustSpeed();
                    } else {
                        autoSpeedActive = isNowMusic; // Update state without triggering video play
                        updateUI(0);
                    }
                };

                const vBtn = document.createElement('button');
                vBtn.className = 'preset-btn';
                vBtn.textContent = 'Movies';
                vBtn.setAttribute('data-tooltip', 'Movie Mode (1x)'); // Corrected
                vBtn.onmouseover = checkTooltipPos; // Corrected
                vBtn.onclick = () => {
                    proSettings.movieOneX = !proSettings.movieOneX;
                    saveProSettings();
                    const info = getCategoryInfo();
                    const isNowMovie = (["30", "43", "1"].includes(info.id) || ["Movies", "Shows", "Film & Animation"].includes(info.name)) && proSettings.movieOneX;

                    const v = document.querySelector('video');
                    if (v && !v.paused) {
                        checkCategoryAndAdjustSpeed();
                    } else {
                        autoSpeedActive = isNowMovie; // Update state without triggering video play
                        updateUI(0);
                    }
                };

                const pBtn = document.createElement('button');
                pBtn.className = 'preset-btn';
                pBtn.textContent = 'P';
                pBtn.setAttribute('data-tooltip', 'Toggle Pitch Shift'); // Corrected
                pBtn.onmouseover = checkTooltipPos; // Corrected
                pBtn.onclick = () => togglePitch();



               proBtns.appendChild(mBtn); proBtns.appendChild(vBtn); proBtns.appendChild(pBtn);

                proArea.appendChild(stats); proArea.appendChild(proBtns); mainContent.appendChild(proArea); updateSavedDisplay();
            }
        }
        container.appendChild(headerRow); container.appendChild(topBar); container.appendChild(mainContent); document.body.appendChild(container);

        [headerRow, titleDragHandle, minDrag].forEach(el => {
            el.onmousedown = (e) => {
                if (e.target.closest('.gf-link')) return;
                const rect = container.getBoundingClientRect(); let sx = e.clientX - rect.left, sy = e.clientY - rect.top;
                //function move(me) { container.style.left = (me.clientX - sx) + 'px'; container.style.top = (me.clientY - sy) + 'px'; container.style.bottom = 'auto'; }
                function move(me) {
                    container.style.left = (me.clientX - sx) + 'px';
                    container.style.top = (me.clientY - sy) + 'px';
                    container.style.bottom = 'auto';

                    // Boundary Check for Tooltips
                    const rect = container.getBoundingClientRect();
                    if (rect.right > window.innerWidth - 100) {
                        container.classList.add('edge-right');
                        container.classList.remove('edge-left');
                    } else if (rect.left < 100) {
                        container.classList.add('edge-left');
                        container.classList.remove('edge-right');
                    } else {
                        container.classList.remove('edge-right', 'edge-left');
                    }
                }

                function stop() { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', stop); localStorage.setItem('ytSpeedPos', JSON.stringify({ top: container.style.top, left: container.style.left })); }
                document.addEventListener('mousemove', move); document.addEventListener('mouseup', stop);
            };
        });
        const savedPos = JSON.parse(localStorage.getItem('ytSpeedPos'));
        if (savedPos) { container.style.left = savedPos.left; container.style.top = savedPos.top; container.style.bottom = 'auto'; }
        updateUI(autoSpeedActive ? 1.0 : lastActiveSpeed);
        setInterval(ensureInView, 3000);
    }

    function togglePitch() {
        if (!isProUser) return;
        pitchShiftEnabled = !pitchShiftEnabled;
        localStorage.setItem('ytPitchShiftEnabled', pitchShiftEnabled);
        const v = document.querySelector('video');
        if (v) {
            v.preservesPitch = !pitchShiftEnabled;
            // Only trigger playback rate change if not paused
            if (!v.paused) {
                const target = autoSpeedActive ? 1.0 : lastActiveSpeed;
                v.playbackRate = target;
            }
        }
        updateUI(v && v.paused ? 0 : (autoSpeedActive ? 1.0 : lastActiveSpeed));
    }
    function togglePause() { const v = document.querySelector('video'); if (!v) return; if (!v.paused && v.playbackRate > 0) { prePauseSpeed = v.playbackRate; updateSpeed(0, false); } else { const res = autoSpeedActive ? 1.0 : (prePauseSpeed || lastActiveSpeed || 1.0); applySpeedToVideo(res); updateUI(res); } }

    function updateSpeed(val, isUser) {
        let n = parseFloat(val);
        if (isNaN(n)) n = 1.0;
        if (isUser && n > 0 && autoSpeedActive) {
            const info = getCategoryInfo();
            if ((info.name === "Music") && proSettings.musicOneX) proSettings.musicOneX = false;
            else if (["Movies", "Shows", "Film & Animation"].includes(info.name) && proSettings.movieOneX) proSettings.movieOneX = false;
            saveProSettings(); autoSpeedActive = false;
        }
        if (n > 0 && !autoSpeedActive) { lastActiveSpeed = n; localStorage.setItem('ytPersistentSpeed', n); }
        applySpeedToVideo(n); updateUI(n);
    }

    function applySpeedToVideo(n) { const v = document.querySelector('video'); if (v) { v.preservesPitch = !pitchShiftEnabled; if (n === 0) v.pause(); else { v.playbackRate = n; if (v.paused) v.play().catch(e => { }); } } }

function updateUI(n) {
    const container = document.getElementById('yt-speed-overlay');
    const sld = document.getElementById('speed-slider');
    const val = document.getElementById('speed-val');
    if (!container) return;

    // 1. Handle Slider Color Logic (Classes for CSS)
    const isProFeatureOn = (autoSpeedActive || pitchShiftEnabled);
    container.classList.toggle('pro-active', isProFeatureOn);
    container.classList.toggle('is-paused', n === 0);

    // 2. Update Slider Value & Text Display
    //if (sld) sld.value = n;
    if (sld) {
        sld.value = n;
        /** * FIX: Slider thumb logic
         * Ensures it reverts to standard red when at 1x speed.
         */
        sld.classList.toggle('override', isProFeatureOn && n > 0 && n !== 1);
    }

    if (val) {
        // Update the text content (PAUSED or Speed)
        val.textContent = n > 0 ? n.toFixed(2) + 'x' : 'PAUSED';
        val.classList.toggle('override', isProFeatureOn && n > 0 && n !== 1);
    }

    // 3. Update All Button Active States
    document.querySelectorAll('.preset-btn').forEach(b => {
        const txt = b.textContent;

        // A. Speed Presets (.25, 1x, 2x, etc.)
        // We exclude Music, Movies, P, and ⏸ from the numeric comparison
        if (!['Music', 'Movies', 'P', '⏸'].includes(txt)) {
            const isCurrentSpeed = parseFloat(txt) === n || (txt === '1x' && n === 1);
            b.classList.toggle('active', isCurrentSpeed);
        }

        // B. Pro Toggle Buttons (Music, Movies, P)
        if (txt === 'Music') b.classList.toggle('active', proSettings.musicOneX);
        if (txt === 'Movies') b.classList.toggle('active', proSettings.movieOneX);
        if (txt === 'P') b.classList.toggle('active', pitchShiftEnabled);

        // C. Pause Button (Optional: stays active while paused)
        if (txt === '⏸') b.classList.toggle('active', n === 0);
    });

    // 4. Update the "Time Saved" display
    updateSavedDisplay();
}

    function updateUIClasses(rate) {
        const video = document.querySelector('video'), info = getCategoryInfo();
        const curRate = rate !== undefined ? rate : (video ? (video.paused ? 0 : video.playbackRate) : 1.0);
        const isPaused = video ? (video.paused || curRate === 0) : (curRate === 0);
        document.querySelectorAll('.preset-btn').forEach(btn => {
            btn.classList.remove('active', 'override');
            if (btn.id === 'pause-toggle-btn') { if (isPaused) btn.classList.add('active'); }
            else { const val = parseFloat(btn.textContent); if (!isPaused && (val === curRate || (btn.textContent === '1x' && curRate === 1))) btn.classList.add('active'); }
            if (btn.textContent === 'P' && isProUser && pitchShiftEnabled) { btn.classList.add('active'); if (curRate !== 1.0 && !isPaused) btn.classList.add('override'); }
            if (btn.textContent === 'Music' && isProUser && proSettings.musicOneX) { btn.classList.add('active'); if (info.name === "Music" && !isPaused) btn.classList.add('override'); }
            if (btn.textContent === 'Movies' && isProUser && proSettings.movieOneX) { btn.classList.add('active'); if (["Movies","Shows","Film & Animation"].includes(info.name) && !isPaused) btn.classList.add('override'); }
        });
    }

    function syncLoop() {
        if (!document.getElementById('yt-speed-overlay')) redrawOverlay();
        const v = document.querySelector('video'); if (isProUser) updateSavedDisplay();
        if (v) {
            if (!v.dataset.trackingAttached) { v.addEventListener('timeupdate', trackTimeSaved); v.dataset.trackingAttached = "true"; }
            const target = autoSpeedActive ? 1.0 : lastActiveSpeed;
            if (v.paused) updateUI(0);
            else { if (Math.abs(v.playbackRate - target) > 0.01 && !isLicenseMode) v.playbackRate = target; updateUI(v.playbackRate); }
            updateUIClasses();
        }
        requestAnimationFrame(syncLoop);
    }

    window.addEventListener('yt-navigate-finish', () => { setTimeout(checkCategoryAndAdjustSpeed, 1200); });
    updateStateFromStorage(); syncLoop();
})();