YouTube: Floating Chat Window on Fullscreen

To make floating chat window on fullscreen

// ==UserScript==
// @name            YouTube: Floating Chat Window on Fullscreen
// @namespace       UserScript
// @version         0.5.4
// @license         MIT License
// @author          CY Fung
// @match           https://www.youtube.com/*
// @exclude         /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
// @description     To make floating chat window on fullscreen
// @require         https://cdn.jsdelivr.net/gh/cyfung1031/userscript-supports@8fac46500c5a916e6ed21149f6c25f8d1c56a6a3/library/ytZara.js
// @run-at          document-start
// @grant           none
// @unwrap
// @allFrames       true
// @inject-into     page
// ==/UserScript==


((__CONTEXT__) => {

    let activeStyle = false;

    let _lastStyleText = null;
    let tvc = 0;

    const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);

    let c27 = 0;
    let mouseDownActiveElement = null;

    document.addEventListener('click', function (evt) {

        if (!document.fullscreenElement) return;

        let byPass = false;

        if (Date.now() - c27 < 40) {
            byPass = true;
        } else {
            return;
        }

        if (evt.target && evt.target.id === 'chat' && evt.target.nodeName.toLowerCase() === 'ytd-live-chat-frame') byPass = false;
        else if (evt.target && evt.target.nodeName.toLowerCase() === 'iframe') byPass = false;

        if (byPass) {
            evt.stopPropagation();
            evt.stopImmediatePropagation();
            c27 = Date.now();
        }
        c27 = 0;

    }, { capture: true, passive: false });

    document.addEventListener('mousedown', function (evt) {

        if (!document.fullscreenElement) return;
        let byPass = false;
        const activeElement = document.activeElement || 0;
        mouseDownActiveElement = null;
        if (activeElement.nodeName === 'IFRAME') {
            if (activeElement.matches('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) iframe')) {
                byPass = true;
                mouseDownActiveElement = activeElement;
            }
        }

        if (evt.target && evt.target.id === 'chat' && evt.target.nodeName.toLowerCase() === 'ytd-live-chat-frame') byPass = false;
        else if (evt.target && evt.target.nodeName.toLowerCase() === 'iframe') byPass = false;


        if (byPass) {

            evt.stopPropagation();
            evt.stopImmediatePropagation();
            c27 = Date.now();
        } else {
            mouseDownActiveElement = null;
        }
        c27 = 0;



    }, { capture: true, passive: false });
    document.addEventListener('mouseup', function (evt) {

        if (!document.fullscreenElement) return;
        let mde = mouseDownActiveElement;
        mouseDownActiveElement = null;

        if (!mde) return;

        let byPass = false;
        const activeElement = mde || 0;
        if (activeElement.nodeName === 'IFRAME') {
            if (activeElement.matches('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) iframe')) {
                byPass = true;
            }
        }

        // if(Date.now()-c27 < 40 ) byPass = true;
        c27 = 0;

        if (evt.target && evt.target.id === 'chat' && evt.target.nodeName.toLowerCase() === 'ytd-live-chat-frame') byPass = false;
        else if (evt.target && evt.target.nodeName.toLowerCase() === 'iframe') byPass = false;

        if (byPass) {
            evt.stopPropagation();
            evt.stopImmediatePropagation();
            c27 = Date.now();
        }

    }, { capture: true, passive: false });


    const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : (this instanceof Window ? this : window);

    const hkey_script = 'vdnvorrwsksy';
    if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting
    win[hkey_script] = true;

    /** @type {globalThis.PromiseConstructor} */
    const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.

    const svgDefs = () => `
        <svg version="1.1" xmlns="//www.w3.org/2000/svg" xmlns:xlink="//www.w3.org/1999/xlink" style="display:none;">
            <defs>
            <filter id="stroke-text-svg-filter-03">

                <feColorMatrix type="matrix" in="SourceGraphic" values="0 0 0 0 1   0 0 0 0 1   0 0 0 0 1   0 0 0 1 0" result="white-text"/>
                <feMorphology in="white-text" result="DILATED" operator="dilate" radius="2"></feMorphology>
                <feFlood flood-color="transparent" flood-opacity="1" result="PINK" id="floodColor-03"></feFlood>
                <feComposite in="PINK" in2="DILATED" operator="in" result="OUTLINE"></feComposite>
                <feMerge>
                <feMergeNode in="OUTLINE" />
                <feMergeNode in="SourceGraphic" />
                </feMerge>
            </filter>
                <filter id="stroke-text-svg-filter-04">
                    <feMorphology operator="dilate" radius="2"></feMorphology>
                    <feComposite operator="xor" in="SourceGraphic"/>
                </filter>
            </defs>

        </svg>
    `.trim();


    const createStyleTextForTopWin = () => `
        [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) {
        position:fixed !important;
            top: var(--f3-top, 5px) !important;
            left: var(--f3-left, calc(60vw + 100px)) !important;
            height: var(--f3-h, 60vh) !important;
            width: var(--f3-w, 320px) !important;
            display:flex !important;
            flex-direction: column !important;
            padding: 4px;
            cursor: all-scroll;
            z-index:9999;
            box-sizing: border-box !important;
            margin:0 !important;
            opacity: var(--floating-window-opacity, 1.0) !important;
            background: transparent;
            background-color: rgba(0, 0, 0, 0.5);
            transition: background-color 300ms;
        }
    
        [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]):hover {
            background-color: rgba(0, 0, 0, 0.85);
    
        }
    
        .no-floating[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) {
            top: -300vh !important;
            left: -300vh !important;
        }
    
        [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) #show-hide-button[class]{
            flex-grow: 0;
            flex-shrink:0;
            position:static;
            cursor: all-scroll;
        }
        [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) #show-hide-button[class] *[class]{
            cursor: inherit;
        }
        [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) iframe[class]{
            flex-grow: 100;
            flex-shrink:0;
            height: 0;
            position:static;
        }
    
        html{
            --fc7-handle-color: #0cb8da;
        }
        html[dark]{
            --fc7-handle-color: #0c74e4;
        }
    
        :fullscreen .resize-handle {
    
            position: absolute !important;
            top: 0;
            left: 0;
            bottom: 0;
            background: transparent;
            right: 0;
            z-index: 999 !important;
            border-radius: inherit !important;
            box-sizing: border-box !important;
            pointer-events:none !important;
            visibility: collapse;
            border: 4px solid transparent;
            border-color: transparent;
            transition: border-color 300ms;
        }
    
        [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]):hover .resize-handle {
    
            visibility: visible;
    
            border-color: var(--fc7-handle-color);
        }
    
        [moving] {
            cursor: all-scroll;
            --pointer-events:initial;
        }
    
        [moving] body {
            --pointer-events:none;
        }
    
        [moving] ytd-live-chat-frame#chat{
            --pointer-events:initial;
        }
    
        [moving] ytd-live-chat-frame#chat iframe {
            --pointer-events:none;
        }
    
        [moving="move"]  ytd-live-chat-frame#chat {
            background-color: var(--yt-spec-general-background-a);
        }
    
        [moving="move"] ytd-live-chat-frame#chat iframe {
            visibility: collapse;
        }
    
        [moving] * {
            pointer-events:var(--pointer-events) !important;
    
        }
        [moving] *, [moving] [class] {
            user-select: none !important;
        }
    
        :fullscreen tyt-iframe-popup-btn {
            display: none !important;
        }
    
        [moving] tyt-iframe-popup-btn {
            display: none !important;
        }
    
        [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) #show-hide-button.ytd-live-chat-frame>ytd-toggle-button-renderer.ytd-live-chat-frame {
            background: transparent;
        }

        :fullscreen ytd-live-chat-frame#chat:not([collapsed]) {
            --chat-show-button-display: block;
            --chat-show-text-display: none;
            --chat-show-btn-text: 'ϞϞϞϞϞϞϞϞϞϞϞ';
        }
    
        :fullscreen ytd-live-chat-frame#chat:not([collapsed]) [is-show-button] [role="text"] {
            display: var(--chat-show-text-display);
        }
    
        :fullscreen ytd-live-chat-frame#chat:not([collapsed]) [is-show-button] button::before {
            content: var(--chat-show-btn-text);
        }

        :fullscreen ytd-live-chat-frame#chat:not([collapsed]) [is-show-button][hidden] {
            display: var(--chat-show-button-display) !important;
        }
    `;

    const createStyleTextForIframe = () => `
        .youtube-floating-chat-iframe yt-live-chat-docked-message#docked-messages.style-scope.yt-live-chat-item-list-renderer {
            margin-top:var(--fc7-top-banner-mt);
            transition: margin-top 180ms;
        }
    
        .youtube-floating-chat-iframe yt-live-chat-banner-manager#live-chat-banner.style-scope.yt-live-chat-item-list-renderer {
            margin-top:var(--fc7-top-banner-mt);
            transition: margin-top 180ms;
        }
    
        .youtube-floating-chat-iframe #action-panel.style-scope.yt-live-chat-renderer {
            position: fixed;
            top: 50%;
            transform: translateY(-50%);
        }
    
        .youtube-floating-chat-iframe yt-live-chat-header-renderer.style-scope.yt-live-chat-renderer {
            position: relative;
            z-index: 8;
            background: rgb(0,0,0);
            visibility: var(--fc7-panel-visibility);
        }
    
        .youtube-floating-chat-iframe #chat-messages.style-scope.yt-live-chat-renderer.iron-selected > #contents.style-scope.yt-live-chat-renderer {
            position: fixed;
            z-index: 4;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
        }
    
        .youtube-floating-chat-iframe #right-arrow-container.yt-live-chat-ticker-renderer,
        .youtube-floating-chat-iframe #left-arrow-container.yt-live-chat-ticker-renderer
        {
            background: transparent;
        }
    
        .youtube-floating-chat-iframe yt-live-chat-renderer.yt-live-chat-app {
            --yt-live-chat-background-color: transparent;
            --yt-live-chat-action-panel-background-color: rgba(0, 0, 0, 0.08);
            --yt-live-chat-header-background-color: rgba(0, 0, 0, 0.18);
            --yt-spec-static-overlay-background-medium: rgba(0, 0, 0, 0.08);
            --yt-live-chat-banner-gradient-scrim: transparent;
        }
    
        .youtube-floating-chat-iframe{
            --fc7-top-banner-mt: 0px;
            --fc7-banner-opacity: 0.86;
            --fc7-system-message-opacity: 0.66;
            --fc7-system-message-opacity2: 0.66;
            --fc7-panel-display: none;
            --fc7-panel-visibility: collapse;
            --fc7-panel-position: absolute;
        }

        .youtube-floating-chat-iframe:focus-within,
        html:focus-within,
        body:focus-within,
        yt-live-chat-app:focus-within,
        yt-live-chat-renderer.yt-live-chat-app:focus-within
        {
            --fc7-top-banner-mt: 56px;
            --fc7-banner-opacity: 1.0;
            --fc7-system-message-opacity: 1.0;
            --fc7-system-message-opacity2: 1.00;
            --fc7-panel-display: invalid;
            --fc7-panel-visibility: invalid;
            --fc7-panel-position: absolute;
        }
    
        .youtube-floating-chat-iframe yt-live-chat-app:hover {
            --fc7-top-banner-mt: 56px;
            --fc7-banner-opacity: 1.0;
            --fc7-system-message-opacity: 1.0;
            --fc7-system-message-opacity2: 1.00;
            --fc7-panel-display: invalid;
            --fc7-panel-visibility: invalid;
            --fc7-panel-position: absolute;
        }
    
    
        .youtube-floating-chat-iframe yt-live-chat-renderer.yt-live-chat-app #visible-banners > yt-live-chat-banner-renderer {
            opacity: var(--fc7-banner-opacity) !important;
        }
    
    
        .youtube-floating-chat-iframe yt-live-chat-renderer.yt-live-chat-app yt-live-chat-viewer-engagement-message-renderer {
            opacity: var(--fc7-system-message-opacity) !important;
        }
    
    
        .youtube-floating-chat-iframe yt-live-chat-app yt-live-chat-renderer.yt-live-chat-app yt-live-chat-message-input-renderer {
        visibility: var(--fc7-panel-visibility);
        position: var(--fc7-panel-position);
    
            transform: translateY(-100%);
            left: 0;
            right: 0;
            opacity: 1;
            background: rgba(0,0,0,0.86);
        }
    
    
        /* hide message with input panel hidden */
        .youtube-floating-chat-iframe yt-live-chat-app > tp-yt-iron-dropdown.yt-live-chat-app yt-tooltip-renderer[slot="dropdown-content"][position-type="OPEN_POPUP_POSITION_TOP"].yt-live-chat-app {
            visibility: var(--fc7-panel-visibility);
        }
    
    
    
    
        [dark].youtube-floating-chat-iframe yt-live-chat-app ::-webkit-scrollbar-track,
        [dark].youtube-floating-chat-iframe yt-live-chat-kevlar-container ::-webkit-scrollbar-track {
            background-color: var(--ytd-searchbox-legacy-button-color);
        }
    
        .youtube-floating-chat-iframe yt-live-chat-app ::-webkit-scrollbar-track,
        .youtube-floating-chat-iframe yt-live-chat-kevlar-container ::-webkit-scrollbar-track {
            background-color: #fcfcfc;
        }
    
    
        [dark].youtube-floating-chat-iframe yt-live-chat-app ::-webkit-scrollbar-thumb,
        [dark].youtube-floating-chat-iframe yt-live-chat-kevlar-container ::-webkit-scrollbar-thumb{
    
            background-color: var(--ytd-searchbox-legacy-button-color);
            border: 2px solid var(--ytd-searchbox-legacy-button-color);
    
        }
    
    
    
        .youtube-floating-chat-iframe yt-live-chat-renderer[has-action-panel-renderer] #action-panel.yt-live-chat-renderer {
            --yt-live-chat-action-panel-gradient-scrim: transparent;
        }
    
    
        .youtube-floating-chat-iframe yt-live-chat-renderer[has-action-panel-renderer] #action-panel.yt-live-chat-renderer yt-live-chat-action-panel-renderer {
            opacity: var(--fc7-system-message-opacity2) !important;
        }
    
    `;

    const { isIframe, isTopFrame } = (() => {

        let isIframe = false, isTopFrame = false;
        try {
            isIframe = window.document !== top.document
        } catch (e) { }

        try {
            isTopFrame = window.document === top.document
        } catch (e) { }

        return { isIframe, isTopFrame };

    })();

    if (isIframe ^ isTopFrame) { } else return;

    const addCSS = (createStyleText) => {
        let text = createStyleText();
        let style = document.createElement('style');
        style.id = 'rvZ0t';
        style.textContent = text;
        document.head.appendChild(style);
    }

    const cleanContext = async (win) => {
        const waitFn = requestAnimationFrame; // shall have been binded to window
        try {
            let mx = 16; // MAX TRIAL
            const frameId = 'vanillajs-iframe-v1'
            let frame = document.getElementById(frameId);
            let removeIframeFn = null;
            if (!frame) {
                frame = document.createElement('iframe');
                frame.id = 'vanillajs-iframe-v1';
                frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
                let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
                n.appendChild(frame);
                while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
                const root = document.documentElement;
                root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
                removeIframeFn = (setTimeout) => {
                    const removeIframeOnDocumentReady = (e) => {
                        e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
                        win = null;
                        setTimeout(() => {
                            n.remove();
                            n = null;
                        }, 200);
                    }
                    if (document.readyState !== 'loading') {
                        removeIframeOnDocumentReady();
                    } else {
                        win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
                    }
                }
            }
            while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
            const fc = frame.contentWindow;
            if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
            const { requestAnimationFrame, setTimeout } = fc;
            const res = { requestAnimationFrame, setTimeout };
            for (let k in res) res[k] = res[k].bind(win); // necessary
            if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
            return res;
        } catch (e) {
            console.warn(e);
            return null;
        }
    };

    isTopFrame && cleanContext(win).then(__CONTEXT__ => {
        if (!__CONTEXT__) return null;

        const { requestAnimationFrame } = __CONTEXT__;

        let chatWindowWR = null;
        let showHideButtonWR = null;
        let showButtonWR = null;

        /* globals WeakRef:false */

        /** @type {(o: Object | null) => WeakRef | null} */
        const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'

        /** @type {(wr: Object | null) => Object | null} */
        const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);

        let rafPromise = null;

        const getRafPromise = () => rafPromise || (rafPromise = new Promise(resolve => {
            requestAnimationFrame(hRes => {
                rafPromise = null;
                resolve(hRes);
            });
        }));

        let startX;
        let startY;
        let startWidth;
        let startHeight;

        let edge = 0;

        let initialLeft;
        let initialTop;

        let stopResize;
        let stopMove;

        function filteroutHidden(el) {
            if (!el) return el;
            if (el.closest('[hidden]')) return null;
            return el;
        }

        const getXY = (e) => {
            let rect = e.target.getBoundingClientRect();
            let x = e.clientX - rect.left; //x position within the element.
            let y = e.clientY - rect.top;  //y position within the element.
            return { x, y };
        }

        let beforeEvent = null;

        function resizeWindow(e) {


            const chatWindow = kRef(chatWindowWR);
            if (chatWindow) {

                const mEdge = edge;

                if (mEdge == 4 || mEdge == 1) {
                } else if (mEdge == 8 || mEdge == 16) {
                } else {
                    return;
                }

                Promise.resolve(chatWindow).then(chatWindow => {
                    let rect;

                    if (mEdge == 4 || mEdge == 1 || mEdge == 16) {

                        let newWidth = startWidth + (startX - e.pageX);
                        let newLeft = initialLeft + startWidth - newWidth;
                        chatWindow.style.setProperty('--f3-w', newWidth + "px");
                        chatWindow.style.setProperty('--f3-left', newLeft + "px");

                        let newHeight = startHeight + (startY - e.pageY);
                        let newTop = initialTop + startHeight - newHeight;
                        chatWindow.style.setProperty('--f3-h', newHeight + "px");
                        chatWindow.style.setProperty('--f3-top', newTop + "px");

                        rect = {
                            x: newLeft,
                            y: newTop,
                            w: newWidth,
                            h: newHeight,
                        };

                    } else if (mEdge == 8) {

                        let newWidth = startWidth + e.pageX - startX;
                        let newHeight = startHeight + e.pageY - startY;
                        chatWindow.style.setProperty('--f3-w', newWidth + "px");
                        chatWindow.style.setProperty('--f3-h', newHeight + "px");

                        rect = {
                            x: initialLeft,
                            y: initialTop,
                            w: newWidth,
                            h: newHeight,
                        };

                    }

                    updateOpacity(chatWindow, rect, screen);

                })

                e.stopImmediatePropagation();
                e.stopPropagation();
                e.preventDefault();


            }

        }

        let isMoved = false;

        function moveWindow(e) {

            const chatWindow = kRef(chatWindowWR);
            if (chatWindow) {

                Promise.resolve(chatWindow).then(chatWindow => {

                    let newX = initialLeft + e.pageX - startX;
                    let newY = initialTop + e.pageY - startY;

                    if (Math.abs(e.pageX - startX) > 10 || Math.abs(e.pageY - startY) > 10) isMoved = true;

                    chatWindow.style.setProperty('--f3-left', newX + "px");
                    chatWindow.style.setProperty('--f3-top', newY + "px");

                    updateOpacity(chatWindow, {
                        x: newX,
                        y: newY,
                        w: startWidth,
                        h: startHeight,
                    }, screen);

                });

                e.stopImmediatePropagation();
                e.stopPropagation();
                e.preventDefault();

            }
        }


        function initializeResize(e) {

            if (!document.fullscreenElement) return;

            if (!document.querySelector('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed])')) return;

            if (e.target.id !== 'chat') return;

            const { x, y } = getXY(e);
            edge = 0;
            if (x < 16 && y < 16) { edge = 16; }
            else if (x < 16) edge = 4;
            else if (y < 16) edge = 1;
            else edge = 8;

            if (edge <= 0) return;

            startX = e.pageX;
            startY = e.pageY;

            const chatWindow = kRef(chatWindowWR);
            if (chatWindow) {
                Promise.resolve(chatWindow).then(chatWindow => {

                    let rect = chatWindow.getBoundingClientRect();
                    initialLeft = rect.x;
                    initialTop = rect.y;

                    startWidth = rect.width;
                    startHeight = rect.height;

                    chatWindow.style.setProperty('--f3-left', initialLeft + "px");
                    chatWindow.style.setProperty('--f3-top', initialTop + "px");
                    chatWindow.style.setProperty('--f3-w', startWidth + "px");
                    chatWindow.style.setProperty('--f3-h', startHeight + "px");

                });
            }

            document.documentElement.setAttribute('moving', 'resize');

            document.documentElement.removeEventListener("mousemove", resizeWindow, false);
            document.documentElement.removeEventListener("mousemove", moveWindow, false);
            document.documentElement.removeEventListener("mouseup", stopResize, false);
            document.documentElement.removeEventListener("mouseup", stopMove, false);

            isMoved = false;
            document.documentElement.addEventListener("mousemove", resizeWindow);
            document.documentElement.addEventListener("mouseup", stopResize);

        }


        let updateOpacityRid = 0;

        function updateOpacity(chatWindow, rect, screen) {

            const tid = ++updateOpacityRid;

            getRafPromise().then(() => {

                if (tid !== updateOpacityRid) return;

                const { x, y, w, h } = rect;
                const [left, top, right, bottom] = [x, y, x + w, y + h];

                const opacityW = (Math.min(right, screen.width) - Math.max(0, left)) / w;
                const opacityH = (Math.min(bottom, screen.height) - Math.max(0, top)) / h;

                const opacity = Math.min(opacityW, opacityH);

                chatWindow.style.setProperty('--floating-window-opacity', Math.round(opacity * 100 * 5, 0) / 5 / 100);

            });

        }

        function initializeMove(e) {

            if (!document.fullscreenElement) return;
            if (!document.querySelector('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed])')) return;

            const chatWindow = kRef(chatWindowWR);

            startX = e.pageX;
            startY = e.pageY;

            if (chatWindow) {

                Promise.resolve(chatWindow).then(chatWindow => {

                    let rect = chatWindow.getBoundingClientRect();
                    initialLeft = rect.x;
                    initialTop = rect.y;

                    startWidth = rect.width;
                    startHeight = rect.height;

                    chatWindow.style.setProperty('--f3-left', initialLeft + "px");
                    chatWindow.style.setProperty('--f3-top', initialTop + "px");
                    chatWindow.style.setProperty('--f3-w', startWidth + "px");
                    chatWindow.style.setProperty('--f3-h', startHeight + "px");

                });

            }

            document.documentElement.setAttribute('moving', 'move');

            document.documentElement.removeEventListener("mousemove", resizeWindow, false);
            document.documentElement.removeEventListener("mousemove", moveWindow, false);
            document.documentElement.removeEventListener("mouseup", stopResize, false);
            document.documentElement.removeEventListener("mouseup", stopMove, false);
            isMoved = false;

            document.documentElement.addEventListener("mousemove", moveWindow, false);
            document.documentElement.addEventListener("mouseup", stopMove, false);

            e.stopImmediatePropagation();
            e.stopPropagation();
            e.preventDefault();

            beforeEvent = e;

        }


        function checkClick(beforeEvent, currentEvent) {

            const d = currentEvent.timeStamp - beforeEvent.timeStamp;
            if (d < 300 && d > 30 && !isMoved) {
                document.documentElement.classList.add('no-floating');
            }

        }

        stopResize = (e) => {

            document.documentElement.removeEventListener("mousemove", resizeWindow, false);
            document.documentElement.removeEventListener("mousemove", moveWindow, false);
            document.documentElement.removeEventListener("mouseup", stopResize, false);
            document.documentElement.removeEventListener("mouseup", stopMove, false);

            document.documentElement.removeAttribute('moving');

            e.stopImmediatePropagation();
            e.stopPropagation();

        }

        stopMove = (e) => {

            document.documentElement.removeEventListener("mousemove", resizeWindow, false);
            document.documentElement.removeEventListener("mousemove", moveWindow, false);
            document.documentElement.removeEventListener("mouseup", stopResize, false);
            document.documentElement.removeEventListener("mouseup", stopMove, false);

            document.documentElement.removeAttribute('moving');
            beforeEvent && checkClick(beforeEvent, e);
            beforeEvent = null;

            e.stopImmediatePropagation();
            e.stopPropagation();

        }

        function reset() {

            document.documentElement.removeAttribute('moving');
            document.documentElement.removeEventListener("mousemove", resizeWindow, false);
            document.documentElement.removeEventListener("mousemove", moveWindow, false);
            document.documentElement.removeEventListener("mouseup", stopResize, false);
            document.documentElement.removeEventListener("mouseup", stopMove, false);

            startX = 0;
            startY = 0;
            startWidth = 0;
            startHeight = 0;

            edge = 0;

            initialLeft = 0;
            initialTop = 0;

            beforeEvent = null;

        }

        function iframeFullscreenChanged() {
            const iframeDoc = this;


            _lastStyleText = null;

            if (!document.fullscreenElement) {
                activeStyle = false;
                iframeDoc.documentElement.classList.remove('youtube-floating-chat-iframe');
            } else {
                activeStyle = true;
                iframeDoc.documentElement.classList.add('youtube-floating-chat-iframe');

            }

        }

        let iframeFullscreenChangedBinded = null;

        function onMessage(evt) {
            if (evt.data === hkey_script) {

                const iframeWin = evt.source;
                if (!iframeWin) return;
                const iframeDoc = iframeWin.document;

                function onReady() {

                    iframeDoc.head.appendChild(document.createElement('style')).textContent = createStyleTextForIframe();

                    const tm = document.createElement('template');
                    tm.innerHTML = svgDefs();
                    iframeDoc.body.appendChild(tm.content)

                    if (iframeFullscreenChangedBinded) document.removeEventListener('fullscreenchange', iframeFullscreenChangedBinded, false);
                    iframeFullscreenChangedBinded = iframeFullscreenChanged.bind(iframeDoc);
                    document.addEventListener('fullscreenchange', iframeFullscreenChangedBinded, false);

                    iframeFullscreenChangedBinded();

                    setInterval(() => {

                        if (!activeStyle) return;

                        let xpathExpression = "//style[text()[contains(., 'userscript-control[floating-chat-iframe]')]]";

                        // Evaluating the XPath expression and getting string value directly
                        let result = iframeDoc.evaluate(xpathExpression, iframeDoc, null, XPathResult.STRING_TYPE, null);

                        let newText = result && result.stringValue ? result.stringValue : null;

                        if (newText !== _lastStyleText) {
                            _lastStyleText = newText;
                            // console.log(123)

                            let tid = ++tvc;

                            getRafPromise().then(() => {

                                if (tid !== tvc) return;

                                let style = iframeWin.getComputedStyle(iframeDoc.documentElement);

                                let fc = style.getPropertyValue('--floodcolor');
                                if (fc) {

                                    let floodColor03 = iframeDoc.querySelector('#floodColor-03');
                                    floodColor03 && floodColor03.setAttribute('flood-color', fc);

                                    let floodColor04 = iframeDoc.querySelector('#floodColor-04');
                                    floodColor04 && floodColor04.setAttribute('flood-color', fc);

                                    iframeDoc.documentElement.setAttribute('hpkns', '')
                                } else {
                                    iframeDoc.documentElement.removeAttribute('hpkns')

                                }

                            });

                        }

                    }, 100);

                }

                Promise.resolve().then(() => {

                    if (iframeDoc.readyState !== 'loading') {
                        onReady();
                    } else {
                        iframeWin.addEventListener("DOMContentLoaded", onReady, false);
                    }

                });

            }

        }

        let moInt = 0;
        const mutationObserverFn = () => {
            if (moInt > 1e9) moInt = 9;
            const t = ++moInt;
            getRafPromise().then(() => {
                if (t !== moInt) return;

                const chatWindow = chatWindowWR ? kRef(chatWindowWR) : null;

                if (!(chatWindow instanceof Element)) return;
                if (chatWindow.hasAttribute('collapsed')) return;
                const chatWindowCnt = insp(chatWindow);

                if (!chatWindowCnt) return;

                const btn = HTMLElement.prototype.querySelector.call(chatWindow, '#show-hide-button[hidden]')
                if (!btn) return;

                if (btn && filteroutHidden(chatWindow)) {
                    const liveChatRenderer = ((chatWindowCnt || 0).data || 0).liveChatRenderer || 0;
                    if (liveChatRenderer && liveChatRenderer.showButton && !liveChatRenderer.showHideButton) {
                        btn.setAttribute('is-show-button', '')
                    } else {
                        btn.removeAttribute('is-show-button')
                    }
                }

            });

        };
        const mutationObserver = new MutationObserver(mutationObserverFn);

        function setChat(chat) {

            if (!(chat instanceof Element)) return;
            let resizeHandle = HTMLElement.prototype.querySelector.call(chat, '.resize-handle')
            if (resizeHandle) return;

            const chatDollar = insp(chat).$ || chat.$ || 0;

            let cw = (() => {
                try {
                    const { head, body } = chatDollar.chatframe.contentWindow.document;
                    return { head, body }

                } catch (e) { return null; }
            })();

            if (!cw) return;

            window.removeEventListener('message', onMessage, false);
            window.addEventListener('message', onMessage, false);



            let script = document.getElementById('rvZ0t') || (document.evaluate("//div[contains(text(), 'userscript-control[enable-customized-floating-window]')]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null) || 0).singleNodeValue;
            if (!script) addCSS(createStyleTextForTopWin);
            /*
            const tm = document.createElement('template');
            tm.innerHTML=svgDefs();
            document.body.appendChild(tm.content)
            */

            if (!document.documentElement.hasAttribute('floating-chat-window')) document.documentElement.setAttribute('floating-chat-window', '');


            chat.setAttribute('allowtransparency', 'true');



            resizeHandle = document.createElement("div");
            resizeHandle.className = "resize-handle";
            chat.appendChild(resizeHandle);
            resizeHandle = null;

            let chatWindow;
            let showHideButton;
            let showButton;

            chatWindow = kRef(chatWindowWR);
            showHideButton = kRef(showHideButtonWR);
            showButton = kRef(showButtonWR);



            if (chatWindow) chatWindow.removeEventListener("mousedown", initializeResize, false);
            if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);
            else if (showButton) showButton.removeEventListener("mousedown", initializeMove, false);

            if (chat) {
                mutationObserver.observe(chat, { attributes: true, attributeFilter: ['collapsed', 'hidden'] });
                mutationObserverFn();
            }


            chatWindow = chat;
            showHideButton = (HTMLElement.prototype.querySelector.call(chat, '#show-hide-button'));
            showButton = (HTMLElement.prototype.querySelector.call(chat, '#show-button'));
            chatWindowWR = mWeakRef(chat)
            showHideButtonWR = mWeakRef(showHideButton);
            showButtonWR = mWeakRef(showButton);



            if (chatWindow) chatWindow.addEventListener("mousedown", initializeResize, false);
            if (showHideButton) showHideButton.addEventListener("mousedown", initializeMove, false);
            else if (showButton) showButton.addEventListener("mousedown", initializeMove, false);

            reset();

        }


        const fullscreenchangePageFn = () => {
            if (!document.fullscreenElement) {
                document.documentElement.classList.remove('no-floating')
            }
        };


        function noChat(chat) {

            if (!(chat instanceof Element)) return;

            let chatWindow;
            let showHideButton;
            let showButton;

            chatWindow = kRef(chatWindowWR);
            showHideButton = kRef(showHideButtonWR);
            showButton = kRef(showButtonWR);



            if (chatWindow) chatWindow.removeEventListener("mousedown", initializeResize, false);
            if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);
            else if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);


            let resizeHandle = HTMLElement.prototype.querySelector.call(chat, '.resize-handle')
            if (resizeHandle) {
                resizeHandle.remove();
            }

            chat.removeEventListener("mousedown", initializeResize, false);


            showHideButton = (HTMLElement.prototype.querySelector.call(chat, '#show-hide-button'));
            showButton = (HTMLElement.prototype.querySelector.call(chat, '#show-button'));

            if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);
            else if (showButton) showButton.removeEventListener("mousedown", initializeMove, false);


            reset();
        }


        document.removeEventListener('fullscreenchange', fullscreenchangePageFn, false);
        document.addEventListener('fullscreenchange', fullscreenchangePageFn, false);
        fullscreenchangePageFn();

        ytZara.ytProtoAsync("ytd-live-chat-frame").then((proto) => {

            proto.attached = ((attached) => (function () { Promise.resolve(this.hostElement || this).then(setChat).catch(console.warn); return attached.apply(this, arguments) }))(proto.attached);

            proto.detached = ((detached) => (function () { Promise.resolve(this.hostElement || this).then(noChat).catch(console.warn); return detached.apply(this, arguments) }))(proto.detached);

            let chat = document.querySelector('ytd-live-chat-frame');
            if (chat) Promise.resolve(chat).then(setChat).catch(console.warn);

        });


    });

    (!isTopFrame && isIframe && top === parent) && top.postMessage(hkey_script, `${location.protocol}//${location.hostname}`);



})({ requestAnimationFrame });