Sleazy Fork is available in English.

BongaCams Unchained

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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();
})();