BongaCams Unchained

Removes annoying UI elements, adds PiP, fullscreen mode, remote control and a bookmarks base.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name:ru      BongaCams Unchained
// @name         BongaCams Unchained
// @namespace    DiadiaBonga
// @version      1.2
// @description:ru Убирает мусор, добавляет PiP, полноэкранный режим, запись, пульт и базу закладок.
// @description  Removes annoying UI elements, adds PiP, fullscreen mode, remote control and a bookmarks base.
// @author       DiaDiaBogDan
// @license      MIT
// @match        *://*.bongacams.com/*
// @match        *://*.bongacams2.com/*
// @match        *://*.webcamsluts.ru/*
// @match        *://*.ukr.bongacams.com/*
// @match        *://*.stripchat.com/*
// @match        *://stripchat.com/*
// @match        *://*.chaturbate.com/*
// @match        *://chaturbate.com/*
// @icon         https://i.bgicdn.com/favicon/bc/favicon.svg?20240227
// @grant        GM_xmlhttpRequest
// @connect      bestcam.tv
// @connect      camshowrecordings.com
// @connect      camshowrecord.net
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    if (window !== window.top) return;

    const blockLimits = () => {
        const script = document.createElement('script');
        script.textContent = `
        try {
            const origSet = Storage.prototype.setItem;
            const reL = /guestTime|ls\\.tft|limit|freeTime|timeLimit/i;
            Storage.prototype.setItem = function (k, v) {
                if (!reL.test(k)) origSet.apply(this, arguments);
            };
            const origC = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie') || Object.getOwnPropertyDescriptor(HTMLDocument.prototype, 'cookie');
            if (origC && origC.set) {
                const reC = /^(limit|time|guest|free)/i;
                Object.defineProperty(document, 'cookie', {
                    get: () => origC.get.call(document),
                    set: v => { if (!reC.test(v.trim())) origC.set.call(document, v); },
                    configurable: true
                });
            }
        } catch {}
        `;
        (document.head || document.documentElement).appendChild(script);
        script.remove();
    };
    blockLimits();

    const S = {
        g: (k, d) => { try { const v = localStorage.getItem(k); return v !== null ? v : d; } catch { return d; } },
        s: (k, v) => { try { localStorage.setItem(k, v); } catch {} }
    };

    let savedBm = [];
    try { savedBm = JSON.parse(S.g('bc_bookmarks', '[]')); } catch { savedBm = []; }

    const State = {
        bgPlay: S.g('bc_bg', 'true') === 'true',
        isMin: S.g('bc_min', 'false') === 'true',
        isHidden: S.g('bc_hid', 'false') === 'true',
        scale: parseFloat(S.g('bc_scale', '1')),
        x: S.g('bc_x', null), y: S.g('bc_y', null),
        hasPos: S.g('bc_pos', 'false') === 'true',
        theme: S.g('bc_theme', 'rgba(220, 20, 60, 0.9)'),
        lang: S.g('bc_lang', 'RU'),
        userPaused: false,
        bookmarks: savedBm
    };

    const icons = {
        color: '<svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"/></svg>',
        menu: '<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>',
        eye: '<svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>',
        close: '<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
        home: '<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>',
        rel: '<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>',
        min: '<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/></svg>',
        bgOn: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>',
        bgOff: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>',
        vol: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>',
        mute: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/></svg>',
        volLow: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/></svg>',
        volMed: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>',
        snap: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>',
        rec: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3" fill="currentColor"/></svg>',
        qual: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>',
        pip: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><rect x="11" y="11" width="8" height="8" rx="1" ry="1"/></svg>',
        fs: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>',
        thtr: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>',
        search: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>',
        copy: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>',
        fav: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>',
        play: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>',
        pause: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>',
        timer: '<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',
        del: '<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>'
    };

    const i18n = {
        RU: { drag: '⋮⋮ ПУЛЬТ', bg: 'ФОН', sound: 'ЗВУК', snap: 'СКРИН', rec: 'ЗАПИСЬ', start: 'СТАРТ...', stop: 'СТОП', err: 'ОШИБКА', no_vid: 'НЕТ ВИДЕО', qual: 'КАЧ-ВО', pip: 'PIP', fs: 'ЭКРАН', thtr: 'ТЕАТР', search: 'ПОИСК', copy: 'КОПИЯ', pause: 'ПАУЗА', play: 'ПЛЕЙ', timer: 'ТАЙМЕР', home: 'Домой', rel: 'Рестарт', min: 'Свернуть', hide: 'Скрыть (H)', hints: 'M=ЗВУК F=ЭКРАН T=ТЕАТР H=СКРЫТЬ', favs: 'ЗАКЛАДКИ', hidden: 'Панель скрыта. Нажмите Меню или "H".', prompt: 'Остановить через (минут)?', no_model: 'Модель не найдена', copied: 'Ссылка скопирована!', no_audio: '! Без звука', bm_title: 'Избранные модели', bm_add: 'В избранное', bm_empty: 'Список пуст.', bm_exp: 'Экспорт', bm_imp: 'Импорт', bm_dup: 'Уже в списке!', bm_ok: 'Сохранено!', note_ph: 'Заметка...' },
        EN: { drag: '⋮⋮ REMOTE', bg: 'BG', sound: 'VOL', snap: 'SNAP', rec: 'REC', start: 'START...', stop: 'STOP', err: 'ERROR', no_vid: 'NO VIDEO', qual: 'QUAL', pip: 'PIP', fs: 'FULL', thtr: 'THTR', search: 'SEARCH', copy: 'COPY', pause: 'PAUSE', play: 'PLAY', timer: 'TIMER', home: 'Home', rel: 'Reload', min: 'Minimize', hide: 'Hide (H)', hints: 'M=MUTE F=FULL T=THTR H=HIDE', favs: 'FAVS', hidden: 'Panel hidden. Tap Menu or press "H".', prompt: 'Stop in how many minutes?', no_model: 'Model not found', copied: 'Link copied!', no_audio: '! No audio', bm_title: 'Favorite Models', bm_add: 'Add current', bm_empty: 'List is empty.', bm_exp: 'Export', bm_imp: 'Import', bm_dup: 'Already added!', bm_ok: 'Saved!', note_ph: 'Note...' }
    };

    const L = () => i18n[State.lang];
    const sub = btn => btn?.querySelector('sub');
    let UI = {}, cachedVideo = null, cachedOc = null;

    const VIDEO_SELECTORS = [
        '.performer-video-container video', '.video-panel video', '.player-container video',
        '.chat-video-container video', '#video-panel-wrap video', '#stream-container video',
        '.video-js video', '.vjs-tech', '[class*="video-container"] video',
        '[class*="player-wrap"] video', '[class*="stream"] video', '[id*="video"] video',
        '[id*="player"] video', 'video[src]', 'video'
    ];

    const CONTAINER_SELECTORS = [
        '.video-js', '.player-container', '.performer-video-container', '.chat-video-container',
        '.video-panel', '#video-panel-wrap', '#stream-container', '[class*="video-container"]',
        '[class*="player-wrap"]', '[class*="stream-container"]', '[id*="video-panel"]', '[id*="stream"]'
    ];

    const QUALITY_SELECTORS = [
        '.vjs-resolution-button', '.vjs-cog-menu-button', '.vjs-icon-cog', '#quality-btn',
        '[class*="quality"]', '[class*="resolution"]', '[title*="quality" i]',
        '[title*="resolution" i]', '[aria-label*="quality" i]', '.vjs-control-bar button:last-child'
    ];

    const FULLSCREEN_SELECTORS = [
        '.performer-video-container', '.video-panel', '#video-panel-wrap', '.player-container',
        '.chat-video-container', '.video-js', '[class*="video-container"]', '[class*="player-wrap"]', 'video'
    ];

    function findElement(selectors) {
        for (const sel of selectors) {
            try { const el = document.querySelector(sel); if (el) return el; } catch {}
        }
        return null;
    }

    const attachVideoInteractions = (v) => {
        if (v._bcBound) return;
        v._bcBound = true;
        ['volumechange', 'pause', 'play'].forEach(ev => v.addEventListener(ev, () => syncUI(v), { passive: true }));
        let container = null;
        for (const sel of CONTAINER_SELECTORS) {
            try { container = v.closest(sel); if (container) break; } catch {}
        }
        const target = container || v.parentElement;
        if (target && !target._bcTouchBound) {
            target._bcTouchBound = true;
            let wheelTick = false;
            target.addEventListener('wheel', e => {
                try { if (e.target.closest('.vjs-control-bar, .vjs-menu, [role="slider"], [role="menu"]')) return; } catch {}
                e.preventDefault();
                if (!wheelTick) {
                    requestAnimationFrame(() => {
                        v.volume = Math.max(0, Math.min(1, v.volume + (e.deltaY < 0 ? 0.05 : -0.05)));
                        if (v.volume > 0) { v.muted = false; lastVolume = v.volume; }
                        syncUI(v);
                        wheelTick = false;
                    });
                    wheelTick = true;
                }
            }, { passive: false });
            let clicks = 0, clickTimer = null, sX = 0, sY = 0;
            target.addEventListener('pointerdown', e => { sX = e.clientX; sY = e.clientY; }, { passive: true, capture: true });
            target.addEventListener('click', e => {
                try {
                    const ignore = e.target.closest('.vjs-control-bar,.vjs-control,.vjs-button,.vjs-menu,.vjs-menu-item,.vjs-menu-content,[role="menu"],[role="menuitem"],[role="button"],[role="slider"],button,a');
                    if (ignore) return;
                } catch {}
                e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
                if (Math.abs(e.clientX - sX) > 10 || Math.abs(e.clientY - sY) > 10) return;
                clicks++;
                if (clicks === 1) {
                    clickTimer = setTimeout(() => { if (clicks === 1) togglePlayPause(); clicks = 0; }, 250);
                } else if (clicks === 2) {
                    clearTimeout(clickTimer); toggleFullscreen(); clicks = 0;
                }
            }, true);
        }
        syncUI(v);
    };

    const getVideo = () => {
        if (cachedVideo && cachedVideo.isConnected) return cachedVideo;
        let v = null;
        for (const sel of VIDEO_SELECTORS) {
            try { v = document.querySelector(sel); if (v && v.tagName === 'VIDEO') break; } catch {}
        }
        if (!v) {
            const all = Array.from(document.querySelectorAll('video'));
            if (all.length) v = all.reduce((best, cur) => (cur.videoWidth * cur.videoHeight >= best.videoWidth * best.videoHeight ? cur : best));
        }
        if (v && v !== cachedVideo) { cachedVideo = v; attachVideoInteractions(v); }
        else if (!v) cachedVideo = null;
        return cachedVideo;
    };

    const OC_SELECTORS = [
        '.js-oc_count', '.chat-info-viewers', '#online-chat .oc_count',
        '[class*="viewers"]', '[class*="online-count"]', '[id*="viewers"]'
    ];

    const runBackgroundTasks = () => {
        getVideo();
        if (UI.statsDisplay) {
            if (!cachedOc || !cachedOc.isConnected) {
                for (const sel of OC_SELECTORS) {
                    try { cachedOc = document.querySelector(sel); if (cachedOc) break; } catch {}
                }
            }
            if (cachedOc) {
                const t = cachedOc.textContent.trim();
                if (t && UI.statsDisplay._last !== t) { UI.statsDisplay.innerHTML = `${icons.eye}<span>${t}</span>`; UI.statsDisplay._last = t; }
            }
        }
        setTimeout(runBackgroundTasks, 4000);
    };

    function getSiteData() {
        let data = {};
        try {
            document.querySelectorAll('script[data-type="initialState"], script[type="application/json"]').forEach(s => {
                try { Object.assign(data, JSON.parse(s.textContent)); } catch {}
            });
        } catch {}
        return data;
    }

    function getModelName() {
        const strategies = [
            () => document.querySelector('.chat-header-inner h1 a, [class*="room-title"] a, [id*="RoomTitle"]')?.textContent?.trim(),
            () => document.getElementById('bChatRoomTitle')?.textContent?.trim(),
            () => document.title.split(/[|\-–—]/)[0].trim(),
            () => { const p = document.querySelector('a.bChatProfileLink, [class*="profile-link"]'); if (p?.href) { const pt = p.href.split('/').filter(Boolean); return pt[pt.length - 1]; } },
            () => { const segs = location.pathname.split('/').filter(p => p && !['profile', 'chat', 'broadcast', 'search', 'login', 'signup', 'ru', 'en', 'models', 'tag'].includes(p.toLowerCase())); return segs.length ? segs[segs.length - 1] : null; },
            () => getSiteData()?.chatLocalData?.chatHost?.username,
            () => getSiteData()?.chatHeaderOptions?.modelLogin,
            () => { const og = document.querySelector('meta[property="og:url"]')?.content; return og ? og.split('/').filter(Boolean).pop() : null; },
            () => document.querySelector('link[rel="canonical"]')?.href?.split('/').filter(Boolean).pop()
        ];
        for (const fn of strategies) {
            try { const raw = fn()?.split(/[?#]/)[0]?.trim(); if (raw && raw.length > 0 && raw.length < 60) return raw; } catch {}
        }
        return '';
    }

    function getModelAvatar() {
        const strategies = [
            () => { const m = document.querySelector('meta[property="og:image"]'); return m?.content?.includes('profile') ? m.content : null; },
            () => getSiteData()?.chatHeaderOptions?.profileImage,
            () => getSiteData()?.chatHeaderOptions?.profileImage50,
            () => getSiteData()?.awayLayoutOptions?.avatarUrl,
            () => document.querySelector('.chat-info-avatar img,.chat-header-avatar img,.profile-photo img,.mls_avatar img,[class*="avatar"] img')?.src
        ];
        for (const fn of strategies) {
            try { const av = fn(); if (av) { return av.startsWith('//') ? 'https:' + av : av; } } catch {}
        }
        return '';
    }

    function enforceBackgroundPlay() {
        const pb = e => { if (State.bgPlay && e.isTrusted) e.stopImmediatePropagation(); };
        ['blur', 'visibilitychange', 'webkitvisibilitychange', 'pagehide'].forEach(evt => {
            window.addEventListener(evt, pb, true);
            document.addEventListener(evt, pb, true);
        });
        try {
            Object.defineProperties(document, {
                'hidden': { get: () => State.bgPlay ? false : undefined, configurable: true },
                'visibilityState': { get: () => State.bgPlay ? 'visible' : 'hidden', configurable: true }
            });
        } catch {}
    }

    function injectPageCSS() {
        if (document.getElementById('bc-css')) return;
        const s = document.createElement('style'); s.id = 'bc-css';
        s.textContent = [
            `.bc-header,.bc-header-main,.h_container,#mainbar_container,.join_button_container,`,
            `.js-chat_join_button,.join_btn,.login-btn,.bcm_socials,.bc-footer,#footer_container,`,
            `.bcm_footer,#id-chat-header,#bChatInputContainer,#games_button,.mls_title,.live_tabs,`,
            `#login,[data-role="header"],#popupChatSelection,#popupGroup,#popupVoyeur,`,
            `#gamecontrol_popup_container,.get_app_banner,.js-get_app_banner,#chat_actions_bar,`,
            `.bcm_chat_footer,.ui-popup-container,.chat_alert_container,#chat_prompt_container,`,
            `.fancybox-overlay,[class*="promo-banner"],[class*="age-gate"],[class*="age_gate"],`,
            `[class*="cookie-banner"],[class*="notification-bar"]{`,
            `display:none!important;opacity:0!important;pointer-events:none!important;`,
            `height:0!important;margin:0!important;padding:0!important}`,
            `.performer-frame,.blurred-performer-frame{pointer-events:none!important}`,
            `#video-panel-wrap,#stream-container,[class*="video-container"],[class*="player-wrap"]`,
            `{pointer-events:auto!important;touch-action:manipulation}`,
            `.bc_alternate_header_live_tabs,.mls_hash_tag,.__fixed_top{position:static!important}`,
            `#chat_bar_v2_container,#chat_bar_v2,#chatProfileButton,#bChatProfilePhotosLink,`,
            `#chatBarChatButton,#switch_between_chat{display:flex!important;opacity:1!important;`,
            `pointer-events:auto!important;z-index:2147483605!important}`,
            `body.bc-theater-mode{overflow:hidden!important;background:#000!important;margin:0!important;padding:0!important}`,
            `body.bc-theater-mode .chat_panel_wrap,body.bc-theater-mode .player_footer,`,
            `body.bc-theater-mode [class*="chat-panel"],body.bc-theater-mode [class*="chat_panel"],`,
            `body.bc-theater-mode [class*="player-footer"],body.bc-theater-mode [class*="footer"]:not(#bc-host *),`,
            `body.bc-theater-mode #chat_bar_v2_container,body.bc-theater-mode #chat_bar_v2,`,
            `body.bc-theater-mode #switch_between_chat,body.bc-theater-mode .chat-tabs,`,
            `body.bc-theater-mode [class*="chat_nav"],body.bc-theater-mode [class*="chat_bar"]{display:none!important}`,
            `body.bc-theater-mode .bc-thtr-wrap{position:fixed!important;inset:0!important;width:100vw!important;height:100vh!important;`,
            `max-width:100vw!important;max-height:100vh!important;z-index:2147483600!important;`,
            `background:#000!important;margin:0!important;padding:0!important;transform:none!important;display:block!important}`,
            `body.bc-theater-mode video.bc-thtr-video{position:fixed!important;inset:0!important;width:100vw!important;height:100vh!important;`,
            `max-width:100vw!important;max-height:100vh!important;z-index:2147483601!important;`,
            `object-fit:contain!important;background:#000!important;margin:0!important;padding:0!important;transform:none!important}`,
            `@keyframes pulseRec{0%,100%{background:rgba(255,255,255,.06);box-shadow:none}`,
            `50%{background:#c0392b;box-shadow:0 2px 8px rgba(0,0,0,.5)}}`,
            `.rec-active{animation:pulseRec 1.5s infinite!important;border-color:transparent!important}`
        ].join('');
        const inject = () => (document.head || document.documentElement)?.appendChild(s);
        document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', inject) : inject();
    }

    let tOut;
    function showToast(m) {
        let t = document.getElementById('bc-toast');
        if (!t) {
            t = document.createElement('div'); t.id = 'bc-toast';
            t.style.cssText = 'position:fixed;bottom:20px;left:50%;transform:translate(-50%,50px);background:rgba(10,10,10,.85);color:#fff;padding:12px 24px;border-radius:30px;font-family:system-ui,sans-serif;font-size:14px;z-index:2147483647;border:1px solid rgba(255,255,255,.2);backdrop-filter:blur(10px);box-shadow:0 4px 15px rgba(0,0,0,.5);opacity:0;transition:all .4s ease;pointer-events:none;';
            document.documentElement.appendChild(t);
        }
        clearTimeout(tOut); t.textContent = m;
        requestAnimationFrame(() => { t.style.transform = 'translate(-50%,0)'; t.style.opacity = '1'; });
        tOut = setTimeout(() => { t.style.transform = 'translate(-50%,50px)'; t.style.opacity = '0'; }, 3000);
    }

    let mRec = null, chunks = [];
    function toggleRecord() {
        const v = getVideo();
        if (!v) return flash(UI.bRec, `! ${L().no_vid}`);
        if (mRec?.state === 'recording') { mRec.stop(); UI.bRec.classList.remove('on', 'rec-active'); sub(UI.bRec).textContent = L().rec; return; }
        try {
            const s = v.captureStream?.(30) ?? v.mozCaptureStream?.(30); if (!s) throw new Error();
            if (s.getAudioTracks().length === 0) showToast(L().no_audio);
            const mt = ['video/mp4', 'video/webm;codecs=vp8,opus', 'video/webm'].find(t => MediaRecorder.isTypeSupported(t)) ?? 'video/webm';
            chunks = []; mRec = new MediaRecorder(s, { mimeType: mt });
            mRec.ondataavailable = e => { if (e.data?.size > 0) chunks.push(e.data); };
            mRec.onstop = () => {
                const url = URL.createObjectURL(new Blob(chunks, { type: mt })), a = document.createElement('a');
                a.style.display = 'none'; a.href = url;
                a.download = `BC_${getModelName() || 'REC'}_${new Date().toLocaleTimeString().replace(/:/g, '-')}.${mt.includes('mp4') ? 'mp4' : 'webm'}`;
                document.body.appendChild(a); a.click(); setTimeout(() => { a.remove(); URL.revokeObjectURL(url); }, 100);
            };
            UI.bRec.classList.add('on', 'rec-active'); sub(UI.bRec).textContent = L().start;
            setTimeout(() => { if (mRec.state !== 'recording') { mRec.start(); sub(UI.bRec).textContent = L().stop; } }, 500);
        } catch { flash(UI.bRec, `! ${L().err}`); }
    }



    let isThtr = false, thtrWrap = null, thtrVid = null, lastVolume = 1;
    function toggleMute() { const v = getVideo(); if (!v) return; if (v.muted || v.volume === 0) { v.muted = false; v.volume = lastVolume || 1; } else { lastVolume = v.volume || 1; v.muted = true; } syncUI(v); }
    function togglePlayPause() { const v = getVideo(); if (!v) return; if (v.paused) { State.userPaused = false; v.play().catch(() => {}); } else { State.userPaused = true; v.pause(); } syncUI(v); }

    function toggleTheater() {
        isThtr = !isThtr;
        if (isThtr) {
            const v = getVideo();
            if (!v) { isThtr = false; return; }
            let wrap = null;
            for (const sel of CONTAINER_SELECTORS) {
                try { wrap = v.closest(sel); if (wrap) break; } catch {}
            }
            wrap = wrap || v.parentElement;
            thtrWrap = wrap; thtrVid = v;
            thtrWrap.classList.add('bc-thtr-wrap'); thtrVid.classList.add('bc-thtr-video');
            document.body.classList.add('bc-theater-mode'); window.scrollTo(0, 0);
        } else {
            document.body.classList.remove('bc-theater-mode');
            thtrWrap?.classList.remove('bc-thtr-wrap'); thtrVid?.classList.remove('bc-thtr-video');
            thtrWrap = null; thtrVid = null;
        }
        window.dispatchEvent(new Event('resize'));
    }

    function takeScreenshot() {
        const v = getVideo(); if (!v) return;
        try {
            const c = document.createElement('canvas');
            c.width = v.videoWidth || 1280; c.height = v.videoHeight || 720;
            c.getContext('2d').drawImage(v, 0, 0);
            c.toBlob(b => {
                if (!b) return;
                const url = URL.createObjectURL(b), a = document.createElement('a');
                a.href = url; a.download = `BC_${getModelName()}_${new Date().toLocaleTimeString().replace(/:/g, '-')}.jpg`;
                a.click(); setTimeout(() => URL.revokeObjectURL(url), 1000);
                flash(UI.bShot, 'OK');
            }, 'image/jpeg', 0.95);
        } catch {}
    }

    function checkPage(url, btn) {
        if (typeof GM_xmlhttpRequest === 'undefined') return;
        btn.style.borderLeft = '4px solid orange';
        btn.style.opacity = '0.8';
        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            timeout: 8000,
            onload: (res) => {
                try {
                    if (res.status === 404 || res.status >= 500) throw new Error();
                    const text = res.responseText.toLowerCase();
                    const titleMatch = text.match(/<title[^>]*>(.*?)<\/title>/i);
                    const title = titleMatch ? titleMatch[1] : '';
                    if (['not found', '404', 'error'].some(t => title.includes(t))) throw new Error();
                    if (/no\s*videos?\s*found/i.test(text) || /no\s*results?\s*found/i.test(text) || /does\s*not\s*exist/i.test(text) || /\b0\s*results?\b/i.test(text)) throw new Error();
                    if (url.includes('camshowrecordings') && !res.responseText.includes('class="h1modelpage"')) throw new Error();
                    
                    btn.style.borderLeft = '4px solid #10b981';
                    btn.style.opacity = '1';
                } catch(e) {
                    btn.style.borderLeft = '4px solid #ef4444';
                    btn.style.opacity = '0.5';
                }
            },
            onerror: () => { btn.style.borderLeft = '4px solid #ef4444'; btn.style.opacity = '0.5'; },
            ontimeout: () => { btn.style.borderLeft = '4px solid #ef4444'; btn.style.opacity = '0.5'; }
        });
    }

    function searchModel() {
        if (!UI.srchOverlay) return;
        const name = getModelName() || '';
        UI.srchInput.value = name;
        UI.srchOverlay.classList.add('open');
        
        // Автофокус убран!
        if (name && UI.searchBtns) {
            UI.searchBtns.forEach(item => {
                item.btn.style.borderLeft = '4px solid transparent';
                item.btn.style.opacity = '1';
                checkPage(item.urlFn(name), item.btn);
            });
        }
    }

    function buildSearch(r) {
        UI.srchOverlay = mkEl('div', 'bm-overlay');
        UI.srchOverlay.onclick = e => { if (e.target === UI.srchOverlay) UI.srchOverlay.classList.remove('open'); };
        const m = mkEl('div', 'bm-modal'), h = mkEl('div', 'bm-hdr');
        UI.srchTitle = Object.assign(mkEl('span', 'bm-title'), { textContent: '🔍 ' });
        const cB = Object.assign(mkEl('button', 'bm-close'), { innerHTML: icons.close });
        cB.onclick = () => UI.srchOverlay.classList.remove('open');
        h.append(UI.srchTitle, cB);
        
        const cont = mkEl('div', 'bm-list');
        UI.srchInput = Object.assign(mkEl('input', 'bm-note'), { 
            type: 'text', 
            style: 'margin-bottom:12px; font-size:18px; padding:12px; text-align:center; font-weight:bold; color:#fff;' 
        });
        UI.srchInput.onkeydown = e => e.stopPropagation();
        
        UI.searchBtns = [];
        const mkSBtn = (txt, urlFn) => {
            const b = Object.assign(mkEl('button', 'bm-btn'), { 
                textContent: txt, 
                style: 'margin-bottom:8px; font-size:15px; padding:12px; transition: all 0.3s ease; border-left: 4px solid transparent;' 
            });
            b.onclick = () => { 
                const v = UI.srchInput.value.trim(); 
                if(v) window.open(urlFn(v), '_blank'); 
                else showToast(L().no_model); 
            };
            UI.searchBtns.push({ btn: b, urlFn: urlFn });
            return b;
        };
        
        cont.append(
            UI.srchInput,
            mkSBtn('BestCam.tv', v => `https://bestcam.tv/model/${encodeURIComponent(v)}`),
            mkSBtn('CamShowRecordings', v => `https://www.camshowrecordings.com/?s=${encodeURIComponent(v)}`),
            mkSBtn('CamShowRecord', v => `https://camshowrecord.net/video/list?page=1&model=${encodeURIComponent(v)}`)
        );
        
        let debounceTimer;
        UI.srchInput.oninput = () => {
            clearTimeout(debounceTimer);
            debounceTimer = setTimeout(() => {
                const v = UI.srchInput.value.trim();
                if (v) {
                    UI.searchBtns.forEach(item => checkPage(item.urlFn(v), item.btn));
                } else {
                    UI.searchBtns.forEach(item => { item.btn.style.borderLeft = '4px solid transparent'; item.btn.style.opacity = '1'; });
                }
            }, 600);
        };
        
        m.append(h, cont);
        UI.srchOverlay.appendChild(m);
        r.appendChild(UI.srchOverlay);
    }

    function copyProfileLink() {
        const n = getModelName();
        if (!n) return showToast(L().no_model);
        navigator.clipboard.writeText(`${location.origin}/profile/${n}`)
            .then(() => { flash(UI.bCopy, 'OK'); showToast(L().copied); })
            .catch(() => flash(UI.bCopy, 'ERR'));
    }

    function setupHotkeys() {
        window.addEventListener('keydown', e => {
            try { const ae = e.composedPath()[0]; if (ae?.tagName?.match(/INPUT|TEXTAREA/) || ae?.isContentEditable) return; } catch {}
            if (e.ctrlKey || e.altKey || e.metaKey) return;
            if (e.code === 'KeyH') { e.preventDefault(); toggleHide(!State.isHidden); return; }
            const v = getVideo();
            switch (e.code) {
                case 'KeyM': e.preventDefault(); toggleMute(); break;
                case 'KeyF': e.preventDefault(); toggleFullscreen(); break;
                case 'KeyP': e.preventDefault(); if (v) togglePiP(v); break;
                case 'KeyT': e.preventDefault(); toggleTheater(); break;
                case 'KeyS': e.preventDefault(); takeScreenshot(); break;
                case 'KeyR': e.preventDefault(); location.reload(); break;
                case 'ArrowUp': case 'ArrowDown':
                    if (e.shiftKey && v) { e.preventDefault(); v.volume = Math.max(0, Math.min(1, v.volume + (e.code === 'ArrowUp' ? 0.1 : -0.1))); if (v.volume > 0) { v.muted = false; lastVolume = v.volume; } syncUI(v); }
                    break;
            }
        });
    }

    async function togglePiP(v) {
        try {
            if (document.pictureInPictureElement) await document.exitPictureInPicture();
            else if (document.pictureInPictureEnabled) { v.removeAttribute('disablePictureInPicture'); await v.requestPictureInPicture(); }
        } catch {}
    }

    function toggleFullscreen() {
        const t = findElement(FULLSCREEN_SELECTORS);
        if (!t) return;
        if (!document.fullscreenElement && !document.webkitFullscreenElement) {
            try { (t.requestFullscreen ?? t.webkitRequestFullscreen ?? t.mozRequestFullScreen ?? (() => {})).call(t); } catch {}
        } else {
            try { (document.exitFullscreen ?? document.webkitExitFullscreen ?? document.mozCancelFullScreen ?? (() => {})).call(document); } catch {}
        }
    }

    function syncUI(v) {
        if (!v || !UI.bMute) return;
        const vol = v.muted ? 0 : v.volume, muted = v.muted || vol === 0, paused = State.userPaused || v.paused;
        if (UI._lastVol !== vol || UI._lastMuted !== muted) {
            sub(UI.bMute).textContent = L().sound; UI.bMute.firstChild.innerHTML = muted ? icons.mute : icons.vol; UI.bMute.classList.toggle('on', muted);
            UI.vSlider.value = String(vol); UI.vSlider.style.setProperty('--p', `${Math.round(vol * 100)}%`);
            UI.vIco.innerHTML = muted ? icons.mute : (vol < 0.4 ? icons.volLow : vol < 0.75 ? icons.volMed : icons.vol); UI.vVal.textContent = `${Math.round(vol * 100)}%`;
            UI._lastVol = vol; UI._lastMuted = muted;
        }
        if (UI._lastPaused !== paused) { UI.bPlay.firstChild.innerHTML = paused ? icons.play : icons.pause; sub(UI.bPlay).textContent = paused ? L().play : L().pause; UI._lastPaused = paused; }
    }

    function flash(b, t) { const s = sub(b); if (!s) return; const o = s.textContent; s.textContent = t; setTimeout(() => s.textContent = o, 1500); }

    const THEMES = ['rgba(220,20,60,0.9)', 'rgba(255,140,0,0.9)', 'rgba(46,204,113,0.9)', 'rgba(0,153,255,0.9)', 'rgba(155,89,182,0.9)', 'rgba(255,20,147,0.9)'];
    const CSS_UI = `:host{--th:${State.theme};pointer-events:none;position:fixed;inset:0;z-index:2147483647;font-family:system-ui,-apple-system,sans-serif}*{box-sizing:border-box;margin:0;padding:0}.panel{position:absolute;width:285px;padding:12px;border-radius:16px;pointer-events:auto;background:rgba(15,15,18,.85);border:1px solid rgba(255,255,255,.1);box-shadow:0 10px 30px rgba(0,0,0,.7),inset 0 1px 1px rgba(255,255,255,.1);backdrop-filter:blur(24px) saturate(150%);transform-origin:top left;will-change:transform,left,top}.panel.mini{width:48px;height:48px;padding:0;background:transparent;border:none;box-shadow:none;backdrop-filter:none}.panel.hidden{display:none!important}.col{display:flex;flex-direction:column;gap:8px}.g4{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}.g5{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}.sep{height:1px;background:linear-gradient(90deg,transparent,rgba(255,255,255,.15),transparent);margin:2px 0}.hdr{display:flex;justify-content:space-between;align-items:center;background:rgba(0,0,0,.35);border-radius:10px;padding:6px 8px;margin-bottom:6px}.drag{flex:1;color:rgba(255,255,255,.6);font-size:11px;font-weight:600;letter-spacing:1px;text-transform:uppercase;cursor:grab;display:flex;align-items:center;padding-left:8px;user-select:none;touch-action:none!important}.drag:active{cursor:grabbing}.stats{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;background:rgba(0,0,0,.5);color:#fff;font-size:9px;min-width:30px;height:26px;padding:2px 4px;border-radius:6px;font-weight:bold;margin-right:8px;box-sizing:border-box;line-height:1}.hdr-btns{display:flex;gap:5px;align-items:center;flex-shrink:0}.h-btn{width:24px;height:24px;background:rgba(255,255,255,.05);border:none;border-radius:6px;color:#fff;cursor:pointer;display:flex;justify-content:center;align-items:center;font-size:12px;transition:.2s}.h-btn:hover{background:rgba(255,255,255,.2);transform:translateY(-1px)}.h-btn.close:hover{background:#e74c3c}.h-btn.lang{width:28px;font-weight:bold;background:rgba(255,255,255,.1);font-size:10px}.btn{color:#fff;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.05);border-radius:10px;padding:8px 2px 6px;font-size:18px;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;transition:all .2s ease;outline:none}.btn:hover{background:rgba(255,255,255,.15);transform:translateY(-2px);border-color:rgba(255,255,255,.2)}.btn:active{transform:scale(.92)}.btn.on{background:var(--th);box-shadow:0 4px 10px rgba(0,0,0,.3);border-color:transparent}.btn sub{font-size:8px;font-weight:600;margin-top:4px;letter-spacing:.5px;text-transform:uppercase;opacity:.8;pointer-events:none;text-align:center;word-break:break-word;line-height:1}.restore{width:48px;height:48px;border-radius:24px;font-size:22px;background:var(--th);border:2px solid rgba(255,255,255,.3);display:none;cursor:pointer;pointer-events:auto;align-items:center;justify-content:center;box-shadow:0 4px 15px rgba(0,0,0,.6);transition:.2s;touch-action:none!important}.restore:hover{transform:scale(1.05)}.ghost{position:fixed;bottom:80px;right:16px;width:44px;height:44px;border-radius:22px;font-size:20px;background:var(--th);border:2px solid rgba(255,255,255,.35);display:none;cursor:pointer;pointer-events:auto;align-items:center;justify-content:center;box-shadow:0 4px 15px rgba(0,0,0,.6);transition:transform .2s,opacity .2s;touch-action:manipulation;z-index:2147483647}.ghost:hover,.ghost:active{transform:scale(1.1)}.v-cap{display:flex;align-items:center;gap:8px;background:rgba(0,0,0,.4);border-radius:12px;padding:8px 12px;border:1px solid rgba(255,255,255,.05)}.vico{display:flex;align-items:center;justify-content:center;color:#fff;font-size:16px;cursor:pointer;flex-shrink:0;filter:drop-shadow(0 2px 4px rgba(0,0,0,.5))}.vslider{-webkit-appearance:none;appearance:none;flex:1;height:3px;border-radius:2px;outline:none;cursor:pointer;background:linear-gradient(to right,var(--th) 0%,var(--th) var(--p,100%),rgba(255,255,255,.1) var(--p,100%),rgba(255,255,255,.1) 100%)}.vslider::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:#fff;box-shadow:0 0 5px rgba(0,0,0,.5);border:2px solid var(--th);cursor:pointer;transition:.2s}.vslider::-webkit-slider-thumb:hover{transform:scale(1.3)}.vval{font-size:10px;font-weight:600;color:#ccc;min-width:32px;text-align:right}.r4{display:flex;align-items:center;justify-content:space-between;gap:8px}.themes{display:flex;gap:5px;flex:1}.sw{width:100%;height:20px;border-radius:6px;cursor:pointer;pointer-events:auto;transition:.2s;border:1px solid rgba(255,255,255,.1);opacity:.6}.sw:hover{opacity:1;transform:translateY(-1px)}.sw.active{opacity:1;border:2px solid #fff;box-shadow:0 0 8px #fff;transform:scale(1.1)}.scale-ctrl{display:flex;align-items:center;gap:4px;background:rgba(0,0,0,.3);border-radius:6px;padding:2px}.z-btn{width:20px;height:20px;background:rgba(255,255,255,.05);border:none;border-radius:4px;color:#fff;cursor:pointer;display:flex;justify-content:center;align-items:center;font-size:14px;transition:.2s;outline:none}.z-btn:hover{background:rgba(255,255,255,.2)}.s-val{font-size:9px;color:#ccc;min-width:24px;text-align:center;font-weight:bold}.hotkeys{font-size:9px;color:rgba(255,255,255,.4);text-align:center;font-weight:500;letter-spacing:.5px;padding-top:2px}.bm-overlay{position:fixed;inset:0;background:rgba(0,0,0,.7);display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:.3s;backdrop-filter:blur(5px);z-index:2147483647}.bm-overlay.open{opacity:1;pointer-events:auto}.bm-modal{width:340px;max-height:85vh;background:rgba(20,20,25,.95);border:1px solid rgba(255,255,255,.1);border-radius:16px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 10px 40px rgba(0,0,0,.8)}.bm-hdr{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:rgba(0,0,0,.4);border-bottom:1px solid rgba(255,255,255,.05);flex-shrink:0}.bm-title{color:#fff;font-weight:bold;font-size:14px}.bm-close{background:none;border:none;color:#aaa;font-size:20px;cursor:pointer}.bm-close:hover{color:#fff}.bm-list{flex:1;min-height:0;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:10px}.bm-item{display:flex;flex-direction:column;gap:8px;padding:10px;background:rgba(255,255,255,.05);border-radius:12px;transition:.2s}.bm-item:hover{background:rgba(255,255,255,.08)}.bm-item-top{display:flex;align-items:center;justify-content:space-between;gap:10px}.bm-link{display:flex;align-items:center;gap:10px;text-decoration:none;color:#fff;flex:1;overflow:hidden}.bm-link:hover .bm-name{color:var(--th)}.bm-avatar{width:42px;height:42px;border-radius:50%;object-fit:cover;aspect-ratio:1/1;background:#333;border:1px solid rgba(255,255,255,.1);flex-shrink:0}.bm-name{font-size:15px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:.2s}.bm-del{width:32px;height:32px;border-radius:8px;background:rgba(231,76,60,.2);color:#e74c3c;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:14px;transition:.2s;flex-shrink:0}.bm-del:hover{background:#e74c3c;color:#fff}.bm-note{width:100%;background:rgba(0,0,0,.4);border:1px solid rgba(255,255,255,.1);color:#fff;border-radius:8px;padding:8px 10px;font-size:13px;font-family:inherit;outline:none;transition:.2s}.bm-note:focus{border-color:var(--th);background:rgba(0,0,0,.6);box-shadow:0 0 5px rgba(255,255,255,.1)}.bm-foot{padding:12px;display:grid;grid-template-columns:1fr 1fr;gap:8px;background:rgba(0,0,0,.4);border-top:1px solid rgba(255,255,255,.05);flex-shrink:0}.bm-add{grid-column:span 2;padding:12px;border-radius:8px;border:none;background:var(--th);color:#fff;font-weight:bold;cursor:pointer;font-size:14px;box-shadow:0 2px 10px rgba(0,0,0,.3);transition:.2s}.bm-add:hover{filter:brightness(1.2)}.bm-add:disabled{background:#555;cursor:not-allowed;opacity:.5}.bm-btn{padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.05);color:#ddd;font-size:13px;font-weight:600;cursor:pointer;transition:.2s}.bm-btn:hover{background:rgba(255,255,255,.15);color:#fff}.bm-empty{text-align:center;padding:30px 10px;color:#777;font-size:14px}.bm-list::-webkit-scrollbar{width:6px}.bm-list::-webkit-scrollbar-thumb{background:rgba(255,255,255,.2);border-radius:3px}`;

    let host, shadow, panel, full, resBtn, ghBtn;
    let isDrag = false, hasMoved = false, sX, sY, iL, iT, dragRaf = null;

    const mkEl = (t, c = '') => { const e = document.createElement(t); if (c) e.className = c; return e; };
    const mkBtn = (i, l, c = '') => { const b = mkEl('button', `btn ${c}`), s = mkEl('sub'); s.textContent = l; const ic = mkEl('span'); ic.innerHTML = i; b.append(ic, s); return b; };

    function updateLabels() {
        if (!UI.dragZone) return; const l = L();
        UI.dragZone.textContent = l.drag; UI.bLang.textContent = State.lang;
        UI.bHome.title = l.home; UI.bRel.title = l.rel; UI.bMin.title = l.min; UI.bHide.title = l.hide;
        [[UI.bBg, l.bg], [UI.bMute, l.sound], [UI.bShot, l.snap], [UI.bRec, mRec?.state === 'recording' ? l.stop : l.rec], [UI.bQual, l.qual], [UI.bPip, l.pip], [UI.bFs, l.fs], [UI.bThtr, l.thtr], [UI.bSearch, l.search], [UI.bCopy, l.copy], [UI.bFav, l.favs]].forEach(([b, t]) => { if (sub(b)) sub(b).textContent = t; });
        UI.hints.innerHTML = l.hints;
        if (UI.bmTitle) { UI.bmTitle.textContent = l.bm_title; UI.bmBtnExp.textContent = l.bm_exp; UI.bmBtnImp.textContent = l.bm_imp; }
        if (UI.srchTitle) { UI.srchTitle.textContent = '🔍 ' + l.search; }
        const v = getVideo(); if (v) syncUI(v); else if (sub(UI.bPlay)) sub(UI.bPlay).textContent = l.play;
    }

    function toggleHide(h) { State.isHidden = h; S.s('bc_hid', h); panel.classList.toggle('hidden', h); ghBtn.style.display = h ? 'flex' : 'none'; }

    function buildHeader(p) {
        const hdr = mkEl('div', 'hdr'); UI.dragZone = mkEl('div', 'drag'); UI.dragZone.addEventListener('pointerdown', onDragStart);
        UI.statsDisplay = mkEl('div', 'stats'); UI.statsDisplay.innerHTML = `${icons.eye}<span>0</span>`;
        UI.bLang = mkEl('button', 'h-btn lang'); UI.bHome = Object.assign(mkEl('button', 'h-btn'), { innerHTML: icons.home }); UI.bRel = Object.assign(mkEl('button', 'h-btn'), { innerHTML: icons.rel }); UI.bMin = Object.assign(mkEl('button', 'h-btn'), { innerHTML: icons.min }); UI.bHide = Object.assign(mkEl('button', 'h-btn close'), { innerHTML: icons.close });
        UI.bLang.onclick = () => { State.lang = State.lang === 'RU' ? 'EN' : 'RU'; S.s('bc_lang', State.lang); updateLabels(); renderBm(); };
        UI.bHome.onclick = () => location.href = '/'; UI.bRel.onclick = () => location.reload(); UI.bMin.onclick = () => toggleMin(true); UI.bHide.onclick = () => toggleHide(true);
        const hb = mkEl('div', 'hdr-btns'); hb.append(UI.statsDisplay, UI.bLang, UI.bHome, UI.bRel, UI.bMin, UI.bHide);
        hdr.append(UI.dragZone, hb); p.appendChild(hdr);
    }

    function buildMediaRow(p) {
        const r1 = mkEl('div', 'g4');
        UI.bBg = mkBtn(State.bgPlay ? icons.bgOn : icons.bgOff, ''); UI.bMute = mkBtn(icons.vol, ''); UI.bShot = mkBtn(icons.snap, ''); UI.bRec = mkBtn(icons.rec, '');
        if (State.bgPlay) UI.bBg.classList.add('on');
        UI.bBg.onclick = () => { State.bgPlay = !State.bgPlay; S.s('bc_bg', State.bgPlay); UI.bBg.firstChild.innerHTML = State.bgPlay ? icons.bgOn : icons.bgOff; sub(UI.bBg).textContent = L().bg; UI.bBg.classList.toggle('on', State.bgPlay); enforceBackgroundPlay(); };
        UI.bMute.onclick = toggleMute; UI.bShot.onclick = takeScreenshot; UI.bRec.onclick = toggleRecord;
        r1.append(UI.bBg, UI.bMute, UI.bShot, UI.bRec); p.appendChild(r1);
    }

    function buildVolume(p) {
        const vc = mkEl('div', 'v-cap');
        UI.vIco = Object.assign(mkEl('span', 'vico'), { innerHTML: icons.vol }); UI.vSlider = Object.assign(mkEl('input', 'vslider'), { type: 'range', min: '0', max: '1', step: '0.02', value: '1' }); UI.vVal = Object.assign(mkEl('span', 'vval'), { textContent: '100%' }); UI.vSlider.style.setProperty('--p', '100%');
        const setV = val => { const v = getVideo(); if (!v) return; v.volume = val; if (val > 0) { v.muted = false; lastVolume = val; } syncUI(v); };
        UI.vIco.onclick = toggleMute; UI.vSlider.oninput = e => setV(parseFloat(e.target.value)); UI.vSlider.onwheel = e => { e.preventDefault(); const v = getVideo(); if (v) setV(Math.max(0, Math.min(1, v.volume + (e.deltaY < 0 ? 0.05 : -0.05)))); };
        vc.append(UI.vIco, UI.vSlider, UI.vVal); p.appendChild(vc);
    }

    function buildViewCtrls(p) {
        p.appendChild(mkEl('div', 'sep')); const r2 = mkEl('div', 'g4');
        UI.bQual = mkBtn(icons.qual, ''); UI.bPip = mkBtn(icons.pip, ''); UI.bFs = mkBtn(icons.fs, ''); UI.bThtr = mkBtn(icons.thtr, '');
        UI.bQual.onclick = () => { const q = findElement(QUALITY_SELECTORS); if (q) q.click(); else flash(UI.bQual, `! ${L().err}`); };
        UI.bPip.onclick = () => { const v = getVideo(); if (v) togglePiP(v); }; UI.bFs.onclick = toggleFullscreen; UI.bThtr.onclick = toggleTheater;
        r2.append(UI.bQual, UI.bPip, UI.bFs, UI.bThtr); p.appendChild(r2);
    }

    function buildMisc(p) {
        const r3 = mkEl('div', 'g4');
        UI.bSearch = mkBtn(icons.search, ''); UI.bCopy = mkBtn(icons.copy, ''); UI.bFav = mkBtn(icons.fav, ''); UI.bPlay = mkBtn(icons.pause, '');
        UI.bSearch.onclick = searchModel; UI.bCopy.onclick = copyProfileLink; UI.bPlay.onclick = togglePlayPause;
        UI.bFav.onclick = () => { UI.bmOverlay.classList.add('open'); renderBm(); };
        r3.append(UI.bSearch, UI.bCopy, UI.bFav, UI.bPlay); p.appendChild(r3);
    }

    function buildSettings(p) {
        p.appendChild(mkEl('div', 'sep')); const r4 = mkEl('div', 'r4');
        const bCol = Object.assign(mkEl('button', 'z-btn'), { innerHTML: icons.color, style: 'width:32px; height:22px; border-radius:6px' });
        bCol.onclick = () => {
            const c = `hsla(${Math.floor(Math.random() * 360)}, ${Math.floor(35 + Math.random() * 20)}%, ${Math.floor(35 + Math.random() * 15)}%, 0.85)`;
            State.theme = c; S.s('bc_theme', c);
            panel.style.setProperty('--th', c); resBtn.style.background = c; ghBtn.style.background = c;
        };
        const sC = mkEl('div', 'scale-ctrl'), bZO = Object.assign(mkEl('button', 'z-btn'), { innerHTML: '−' }), bZI = Object.assign(mkEl('button', 'z-btn'), { innerHTML: '+' }), sV = Object.assign(mkEl('span', 's-val'), { textContent: `${Math.round(State.scale * 100)}%` });
        const setS = s => { s = Math.max(0.6, Math.min(s, 1.5)); State.scale = s; S.s('bc_scale', s); sV.textContent = `${Math.round(s * 100)}%`; panel.style.transform = `scale(${s})`; if (State.hasPos) clamp(); };
        bZO.onclick = () => setS(State.scale - 0.1); bZI.onclick = () => setS(State.scale + 0.1);
        sC.append(bZO, sV, bZI); r4.append(bCol, sC); p.appendChild(r4);
        UI.hints = mkEl('div', 'hotkeys'); p.appendChild(UI.hints);
    }

    function buildBm(r) {
        UI.bmOverlay = mkEl('div', 'bm-overlay'); UI.bmOverlay.onclick = e => { if (e.target === UI.bmOverlay) UI.bmOverlay.classList.remove('open'); };
        const m = mkEl('div', 'bm-modal'), h = mkEl('div', 'bm-hdr'); UI.bmTitle = mkEl('span', 'bm-title');
        const cB = Object.assign(mkEl('button', 'bm-close'), { innerHTML: '×' }); cB.onclick = () => UI.bmOverlay.classList.remove('open');
        h.append(UI.bmTitle, cB); UI.bmList = mkEl('div', 'bm-list'); const f = mkEl('div', 'bm-foot');
        UI.bmAdd = mkEl('button', 'bm-add'); UI.bmAdd.onclick = addBm; UI.bmBtnExp = mkEl('button', 'bm-btn'); UI.bmBtnExp.onclick = expBm; UI.bmBtnImp = mkEl('button', 'bm-btn'); UI.bmBtnImp.onclick = impBm;
        f.append(UI.bmAdd, UI.bmBtnExp, UI.bmBtnImp); m.append(h, UI.bmList, f); UI.bmOverlay.appendChild(m); r.appendChild(UI.bmOverlay);
    }

    function renderBm() {
        UI.bmList.innerHTML = ''; const n = getModelName();
        UI.bmAdd.disabled = !n; UI.bmAdd.textContent = n ? `${L().bm_add} (${n})` : L().no_model;
        if (!State.bookmarks.length) return void (UI.bmList.innerHTML = `<div class="bm-empty">${L().bm_empty}</div>`);
        State.bookmarks.forEach(b => {
            const i = mkEl('div', 'bm-item'), top = mkEl('div', 'bm-item-top'), l = Object.assign(mkEl('a', 'bm-link'), { href: b.url }), img = mkEl('img', 'bm-avatar');
            img.src = b.avatar || `https://ui-avatars.com/api/?name=${b.name}&background=random`; img.onerror = function () { this.src = `https://ui-avatars.com/api/?name=${b.name}&background=random`; };
            const stat = mkEl('span'); stat.style.cssText = 'width:8px;height:8px;border-radius:50%;background:#888;margin-right:8px;flex-shrink:0;box-shadow:0 0 4px rgba(0,0,0,0.5);transition:0.3s;';
            const nW = mkEl('div'); nW.style.cssText = 'display:flex;align-items:center;min-width:0;flex:1;';
            nW.append(stat, Object.assign(mkEl('span', 'bm-name'), { textContent: b.name }));
            l.append(img, nW);
            const d = Object.assign(mkEl('button', 'bm-del'), { innerHTML: icons.del }); d.onclick = e => { e.preventDefault(); e.stopPropagation(); State.bookmarks = State.bookmarks.filter(x => x.name !== b.name); S.s('bc_bookmarks', JSON.stringify(State.bookmarks)); renderBm(); };
            top.append(l, d);
            const ni = Object.assign(mkEl('input', 'bm-note'), { type: 'text', placeholder: L().note_ph, value: b.note || '' });
            ni.onchange = e => { b.note = e.target.value.trim(); S.s('bc_bookmarks', JSON.stringify(State.bookmarks)); showToast(L().bm_ok); };
            ni.onkeydown = e => e.stopPropagation(); i.append(top, ni); UI.bmList.append(i);
            fetch(b.url).then(r => r.text()).then(h => {
                const on = /"online"\s*:\s*true/i.test(h) || /"isOffline"\s*:\s*false/i.test(h);
                stat.style.background = on ? '#2ecc71' : '#e74c3c';
                if (on) stat.style.boxShadow = '0 0 8px #2ecc71';
            }).catch(()=>{});
        });
    }

    async function addBm() {
        const n = getModelName(); if (!n) return;
        if (State.bookmarks.some(b => b.name === n)) return showToast(L().bm_dup);
        UI.bmAdd.disabled = true; UI.bmAdd.textContent = '⏳...';
        let av = '';
        const bUrl = location.href.split(/[?#]/)[0];
        try {
            const res = await fetch(bUrl);
            const html = await res.text();
            const m = html.match(/<meta\s+(?:[^>]*?\s+)?(?:property=["']og:image["']|name=["']og:image["'])[^>]*?\s+content=["']([^"']+)["']/i)
                || html.match(/content=["']([^"']+)["'][^>]*?property=["']og:image["']/i)
                || html.match(/"profileImage"\s*:\s*"([^"]+)"/i)
                || html.match(/"avatarUrl"\s*:\s*"([^"]+)"/i);
            if (m?.[1]) {
                av = m[1].replace(/\\/g, '');
                if (av.startsWith('//')) av = 'https:' + av;
            }
        } catch {}
        if (!av) av = getModelAvatar();
        State.bookmarks.push({ name: n, url: bUrl, avatar: av, note: '' });
        S.s('bc_bookmarks', JSON.stringify(State.bookmarks)); renderBm(); showToast(L().bm_ok);
    }

    function expBm() {
        if (!State.bookmarks.length) return;
        const u = URL.createObjectURL(new Blob([JSON.stringify(State.bookmarks, null, 2)], { type: 'application/json' })), a = document.createElement('a');
        a.href = u; a.download = `BC_Favs_${new Date().toISOString().slice(0, 10)}.json`; a.click(); URL.revokeObjectURL(u);
    }

    function impBm() {
        const i = document.createElement('input'); i.type = 'file'; i.accept = '.json';
        i.onchange = e => {
            const f = e.target.files[0]; if (!f) return;
            const r = new FileReader(); r.onload = ev => {
                try {
                    const a = JSON.parse(ev.target.result);
                    if (Array.isArray(a)) { const es = new Set(State.bookmarks.map(b => b.name)); State.bookmarks.push(...a.filter(b => b.name && !es.has(b.name)).map(b => ({ ...b, note: b.note || '' }))); S.s('bc_bookmarks', JSON.stringify(State.bookmarks)); renderBm(); showToast(L().bm_ok); }
                } catch { showToast(L().err); }
            }; r.readAsText(f);
        }; i.click();
    }

    function build() {
        if (document.getElementById('bc-host')) return;
        host = Object.assign(mkEl('div'), { id: 'bc-host' }); shadow = host.attachShadow({ mode: 'open' }); shadow.appendChild(Object.assign(mkEl('style'), { textContent: CSS_UI }));
        panel = mkEl('div', 'panel'); panel.style.transform = `scale(${State.scale})`; shadow.appendChild(panel);
        ghBtn = Object.assign(mkEl('div', 'ghost'), { innerHTML: icons.menu }); ghBtn.onclick = () => toggleHide(false); shadow.appendChild(ghBtn);
        resBtn = Object.assign(mkEl('div', 'restore'), { innerHTML: icons.menu }); resBtn.onclick = () => { if (!hasMoved) toggleMin(false); }; resBtn.onpointerdown = onDragStart; panel.appendChild(resBtn);
        full = mkEl('div', 'col'); panel.appendChild(full);
        buildHeader(full); buildMediaRow(full); buildVolume(full); buildViewCtrls(full); buildMisc(full); buildSettings(full); buildBm(shadow); buildSearch(shadow);
        updateLabels(); setupDrag(); applyLayout();
        if (State.isMin) toggleMin(true, true); if (State.isHidden) toggleHide(true);
        document.documentElement.appendChild(host);
        setTimeout(runBackgroundTasks, 3000);
    }

    function onDragStart(e) {
        if (e.button !== 0 && e.pointerType === 'mouse') return;
        e.preventDefault(); isDrag = true; hasMoved = false; const r = panel.getBoundingClientRect(); sX = e.clientX; sY = e.clientY; iL = parseFloat(panel.style.left) || r.left; iT = parseFloat(panel.style.top) || r.top;
    }

    function setupDrag() {
        window.addEventListener('pointermove', e => {
            if (!isDrag) return;
            const dx = e.clientX - sX, dy = e.clientY - sY;
            if (!hasMoved && Math.hypot(dx, dy) > 5) hasMoved = true;
            if (hasMoved && !dragRaf) {
                dragRaf = requestAnimationFrame(() => {
                    panel.style.cssText += `;left:${iL + dx}px;top:${iT + dy}px;right:auto;bottom:auto`;
                    dragRaf = null;
                });
            }
        }, { passive: true });
        const eD = () => { if (!isDrag) return; isDrag = false; if (!hasMoved) return; clamp(); State.x = panel.style.left; State.y = panel.style.top; State.hasPos = true; S.s('bc_x', State.x); S.s('bc_y', State.y); S.s('bc_pos', 'true'); };
        window.addEventListener('pointerup', eD); window.addEventListener('pointercancel', eD);
    }

    function clamp(g = 10) { const r = panel.getBoundingClientRect(); let l = parseFloat(panel.style.left), t = parseFloat(panel.style.top); if (isNaN(l) || isNaN(t)) return; l = Math.max(g, Math.min(l, innerWidth - g - r.width)); t = Math.max(g, Math.min(t, innerHeight - g - r.height)); panel.style.left = `${l}px`; panel.style.top = `${t}px`; }

    function applyLayout() { if (State.hasPos && State.x && State.y) { panel.style.left = State.x; panel.style.top = State.y; panel.style.right = 'auto'; panel.style.bottom = 'auto'; } else { panel.style.right = '15px'; panel.style.bottom = '10%'; panel.style.left = 'auto'; panel.style.top = 'auto'; } }

    function toggleMin(m, i = false) {
        State.isMin = m; S.s('bc_min', m);
        if (!i) { const sg = m ? 1 : -1, l = (parseFloat(panel.style.left) || panel.getBoundingClientRect().left) + sg * 168 * State.scale, t = (parseFloat(panel.style.top) || panel.getBoundingClientRect().top) + sg * 5 * State.scale; panel.style.cssText += `;left:${l}px;top:${t}px;right:auto;bottom:auto`; State.x = `${l}px`; State.y = `${t}px`; State.hasPos = true; S.s('bc_x', State.x); S.s('bc_y', State.y); S.s('bc_pos', 'true'); }
        full.style.display = m ? 'none' : 'flex'; resBtn.style.display = m ? 'flex' : 'none'; panel.className = m ? 'panel mini' : 'panel'; if (!i) clamp();
    }

    function init() {
        injectPageCSS(); enforceBackgroundPlay();
        const b = () => { build(); setupHotkeys(); };
        document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', b) : b();
        window.addEventListener('resize', () => { if (State.hasPos) clamp(); });
        let cssGuardTimer = null;
        new MutationObserver(() => {
            clearTimeout(cssGuardTimer);
            cssGuardTimer = setTimeout(() => { if (!document.getElementById('bc-css')) injectPageCSS(); }, 500);
        }).observe(document.documentElement, { childList: true, subtree: false });
    }

    init();
})();