Cam ARNA

Multi-archive search tool with local StreaMonitor integration

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Cam ARNA
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  Multi-archive search tool with local StreaMonitor integration 
// @author       user006-ui
// @license      MIT
// @match        https://*.stripchat.com/*
// @match        https://*.chaturbate.com/*
// @match        https://chaturbate.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @connect      *
// ==/UserScript==

(function() {
    'use strict';

    // --- Storage Helper ---
    const Storage = {
        get: (key, defaultValue) => {
            try {
                const val = localStorage.getItem(key);
                return val ? JSON.parse(val) : defaultValue;
            } catch (e) { return defaultValue; }
        },
        set: (key, value) => {
            try {
                localStorage.setItem(key, JSON.stringify(value));
                return true;
            } catch (e) { return false; }
        }
    };

    // --- Configuration ---
    const SM_Config = {
        get enabled() { return Storage.get('sm_enabled', true); },
        get url() { return Storage.get('sm_url', "http://192.168.178.33:5000"); },
        get user() { return Storage.get('sm_user', "admin"); },
        get pass() { return Storage.get('sm_pass', "admin"); }
    };

    const archiveSites = [
        { name: 'Archivebate', url: 'https://archivebate.com/profile/{username}', domain: 'archivebate.com' },
        { name: 'Showcamrips', url: 'https://showcamrips.com/model/en/{username}', domain: 'showcamrips.com' },
        { name: 'Camshowrecordings', url: 'https://www.camshowrecordings.com/model/{username}', domain: 'camshowrecordings.com' },
        { name: 'Camwh', url: 'https://camwh.com/tags/{username}/', domain: 'camwh.com' },
        { name: 'TopCamVideos', url: 'https://www.topcamvideos.com/showall/?search={username}', domain: 'topcamvideos.com' },
        { name: 'LoveCamPorn', url: 'https://lovecamporn.com/showall/?search={username}', domain: 'lovecamporn.com' },
        { name: 'Camwhores.tv', url: 'https://www.camwhores.tv/search/{username}/', domain: 'camwhores.tv' },
        { name: 'Bestcam.tv', url: 'https://bestcam.tv/model/{username}', domain: 'bestcam.tv' },
        { name: 'Xhomealone', url: 'https://xhomealone.com/tags/{username}/', domain: 'xhomealone.com' },
        { name: 'Stream-leak', url: 'https://stream-leak.com/models/{username}/', domain: 'stream-leak.com' },
        { name: 'MFCamhub', url: 'https://mfcamhub.com/models/{username}/', domain: 'mfcamhub.com' },
        { name: 'Camshowrecord', url: 'https://camshowrecord.net/video/list?page=1&model={username}', domain: 'camshowrecord.net' },
        { name: 'Camwhoresbay', url: 'https://www.camwhoresbay.com/search/{username}/', domain: 'camwhoresbay.com' },
        { name: 'CamSave1', url: 'https://www.camsave1.com/?feet=0&face=0&ass=0&tits=0&pussy=0&search={username}&women=true&couples=true&men=false&trans=false', domain: 'camsave1.com' },
        { name: 'Onscreens', url: 'https://www.onscreens.me/m/{username}', domain: 'onscreens.me' },
        { name: 'Livecamrips', url: 'https://livecamrips.to/search/{username}/1', domain: 'livecamrips.to' },
        { name: 'Cumcams', url: 'https://cumcams.cc/performer/{username}', domain: 'cumcams.cc' },
        { name: 'AllMyCam', url: 'https://allmy.cam/search/{username}/', domain: 'allmy.cam' }
    ];

    const mainSites = {
        'stripchat': 'https://stripchat.com/{username}',
        'chaturbate': 'https://chaturbate.com/{username}/'
    };

    function getFaviconUrl(domain) {
        return `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
    }

    // --- API Handlers ---
    const StreaMonitor = {
        checkStatus: function(username, site) {
            return new Promise((resolve) => {
                if (!SM_Config.enabled) { resolve({ disabled: true }); return; }
                if (!username || !site) { resolve({ error: 'No data' }); return; }

                const baseUrl = SM_Config.url.replace(/\/$/, "");
                GM_xmlhttpRequest({
                    method: "GET",
                    url: `${baseUrl}/api/data`,
                    user: SM_Config.user,
                    password: SM_Config.pass,
                    timeout: 4000,
                    onload: function(response) {
                        if (response.status === 200) {
                            try {
                                const data = JSON.parse(response.responseText);
                                const streamers = data.streamers || [];
                                const match = streamers.find(s =>
                                    s.username.toLowerCase() === username.toLowerCase() &&
                                    (
                                        s.site.toLowerCase() === site.toLowerCase() ||
                                        (s.url && s.url.toLowerCase().includes(site.toLowerCase())) ||
                                        (site === 'chaturbate' && s.site === 'CB') ||
                                        (site === 'stripchat' && s.site === 'SC')
                                    )
                                );
                                resolve({ found: !!match, data: match });
                            } catch (e) { resolve({ error: 'Data Error' }); }
                        } else { resolve({ error: `API ${response.status}` }); }
                    },
                    onerror: function() { resolve({ error: 'Offline' }); },
                    ontimeout: function() { resolve({ error: 'Timeout' }); }
                });
            });
        },
        addStreamer: function(username, site) {
            return new Promise((resolve) => {
                const baseUrl = SM_Config.url.replace(/\/$/, "");
                const dataStr = `username=${encodeURIComponent(username)}&site=${encodeURIComponent(site)}`;
                GM_xmlhttpRequest({
                    method: "POST",
                    url: `${baseUrl}/add`,
                    headers: { "Content-Type": "application/x-www-form-urlencoded" },
                    data: dataStr,
                    user: SM_Config.user,
                    password: SM_Config.pass,
                    onload: function(response) {
                        if (response.status === 200 || response.status === 500) { resolve(true); }
                        else { resolve(false); }
                    },
                    onerror: function() { resolve(false); }
                });
            });
        }
    };

    const PageChecker = {
        cache: {},
        // Signatur des 404-Bildes (Showcamrips)
        showcamrips404: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUwAAADYCAMAAAC3MCqMAAABCFBMVEUBAQD7+/j5+fb///6GhoRzc3Fra2moqKaSkpBubmxoaGaJiYdfX11iYmBcXFplZWOamphUVFI6Oji1tbNRUU9XV1WXl5WdnZtOTkxLS0mDg4KBgYA2NjRISEagoJ5wcG+srKp3d3ZDQ0F5eXh1dXN9fXx/f32ysrCjo6GNjYy5ubcvLy2vr617e3o+PjyPj44HBwaLi4krKylZWVfExMIzMzGUlJL9/fq8vLpAQD4VFROlpaO/v70ODg1FRUMlJSMoKCYfHx0RERAiIiAbGxoYGBbIyMYLCwnLy8nR0c/Bwb/OzszU1NL29vTb29nm5uTX19Xf393i4uDq6ujt7evw8e7Z2dfz8/Eqcyz2AACcOElEQVR42uSWxZLk0BFFZY+YmZlZLXRRF1NXlZumZ/z",

        checkPage: function(url) {
            return new Promise((resolve) => {
                if (this.cache.hasOwnProperty(url)) { resolve(this.cache[url]); return; }
                GM_xmlhttpRequest({
                    method: 'GET', url: url, timeout: 8000,
                    onload: (response) => {
                        const exists = this.analyzeResponse(response, url);
                        this.cache[url] = exists; resolve(exists);
                    },
                    onerror: () => { this.cache[url] = false; resolve(false); }
                });
            });
        },
        analyzeResponse: function(response, url) {
            if (response.status === 404 || response.status >= 500) return false;

            const text = response.responseText;
            const lowerText = text.toLowerCase();

            // 1. Showcamrips Special
            if (url && url.includes('showcamrips') && text.includes(this.showcamrips404)) {
                return false;
            }

            // 2. Camshowrecordings Special
            if (url && url.includes('camshowrecordings.com')) {
                if (text.includes('class="h1modelindex"')) return false;
                const userMatch = url.match(/\/model\/([^/?#]+)/);
                if (userMatch && userMatch[1]) {
                    const username = userMatch[1].toLowerCase();
                    if (text.includes('class="h1modelpage"') && lowerText.includes(username)) {
                        return true;
                    }
                    return false;
                }
                return text.includes('class="h1modelpage"');
            }

            // 3. Camwhores.tv Special
            if (url && url.includes('camwhores.tv')) {
                if (lowerText.includes('no videos found') || lowerText.includes('no results')) return false;
                const userMatch = url.match(/\/search\/([^/?#]+)/);
                if (userMatch) {
                    const rawUser = decodeURIComponent(userMatch[1]).toLowerCase();
                    const userRegexStr = rawUser.replace(/[\-\_]/g, '[\\s\\-\\_]+');
                    const titleRegex = new RegExp(`class=["']title["'][^>]*>\\s*[^<]*${userRegexStr}`, 'i');
                    const linkRegex = new RegExp(`href=["'].*?\/videos\/\\d+\/.*?${userRegexStr}`, 'i');
                    if (titleRegex.test(text) || linkRegex.test(text)) return true;
                    return false;
                }
            }

            // 4. Camwhoresbay Special
            if (url && url.includes('camwhoresbay.com')) {
                const userMatch = url.match(/\/search\/([^/?#]+)/);
                if (userMatch) {
                    const rawUser = decodeURIComponent(userMatch[1]).toLowerCase();
                    const safeUser = rawUser.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                    const userRegexStr = safeUser.replace(/[\-\_]/g, '[\\s\\-\\_]*');

                    const videoLinkRegex = new RegExp(`href=["'][^"']*\\/videos\\/[^"']*${userRegexStr}`, 'i');
                    const titleRegex = new RegExp(`title=["'][^"']*${userRegexStr}[^"']*["']`, 'i');

                    if (videoLinkRegex.test(text) || titleRegex.test(text)) {
                        return true;
                    }
                    return false;
                }
            }

            // 5. Camshowrecord Special
            if (url && url.includes('camshowrecord.net')) {
                if (text.includes('Sorry, no video found for this')) {
                    return false;
                }
                return true;
            }

            // 6. Cumcams Special (Improved for False Positives)
            if (url && url.includes('cumcams.cc')) {
                // Wir suchen im Kleingedruckten (lowerText) nach der Phrase, unabhängig von HTML-Tags
                if (lowerText.includes('performer not found')) {
                    return false;
                }
                return true;
            }

            // Standard Checks
            const titleMatch = lowerText.match(/<title[^>]*>(.*?)<\/title>/i);
            const title = titleMatch ? titleMatch[1] : '';

            const notFoundTerms = [
                'no videos found',
                'no results found',
                'does not exist',
                'no videos to show'
            ];

            if (['not found', '404', 'page not found', 'no results'].some(x => title.includes(x))) return false;
            if (notFoundTerms.some(x => lowerText.includes(x))) return false;

            return true;
        },
        clearCache: function() { this.cache = {}; }
    };

    // --- UI & Styles ---
    function injectStyles() {
        const style = document.createElement('style');
        style.textContent = `
            :root {
                --cam-bg: #09090b;
                --cam-surface: #18181b;
                --cam-border: #27272a;
                --cam-primary: #3b82f6; /* Modern Blue */
                --cam-primary-hover: #2563eb;
                --cam-text: #f4f4f5;
                --cam-text-muted: #a1a1aa;
                --cam-danger: #ef4444;
                --cam-success: #22c55e;
                --cam-radius: 16px;
                --cam-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            }

            /* --- Animations --- */
            @keyframes slideUpMobile { from { transform: translateY(100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
            @keyframes scaleInDesktop { from { transform: translate(-50%, -45%) scale(0.95); opacity: 0; } to { transform: translate(-50%, -50%) scale(1); opacity: 1; } }
            @keyframes spin { to { transform: rotate(360deg); } }

            /* --- Base Elements --- */
            .cam-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(4px); z-index: 99999; }

            .cam-container {
                position: fixed; z-index: 100000;
                background: var(--cam-bg); color: var(--cam-text);
                font-family: var(--cam-font);
                box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
                border: 1px solid var(--cam-border);
                display: flex; flex-direction: column;
                overflow: hidden;
            }

            /* --- Responsive Layout --- */
            /* Desktop */
            @media (min-width: 601px) {
                .cam-container {
                    top: 50%; left: 50%;
                    transform: translate(-50%, -50%);
                    width: 450px; max-height: 85vh;
                    border-radius: var(--cam-radius);
                    animation: scaleInDesktop 0.2s cubic-bezier(0.16, 1, 0.3, 1);
                }
            }
            /* Mobile (Bottom Sheet) */
            @media (max-width: 600px) {
                .cam-container {
                    bottom: 0; left: 0; right: 0;
                    width: 100%; max-height: 90vh;
                    border-radius: 24px 24px 0 0;
                    border-bottom: none;
                    animation: slideUpMobile 0.3s cubic-bezier(0.16, 1, 0.3, 1);
                }
            }

            /* --- Header --- */
            .cam-header {
                padding: 16px 20px;
                background: var(--cam-surface);
                border-bottom: 1px solid var(--cam-border);
                display: flex; justify-content: space-between; align-items: center;
                flex-shrink: 0;
            }
            .cam-title { font-size: 18px; font-weight: 700; letter-spacing: -0.5px; display: flex; align-items: center; gap: 8px; }
            .cam-close { background: none; border: none; color: var(--cam-text-muted); font-size: 24px; cursor: pointer; padding: 4px; border-radius: 50%; transition: 0.2s; display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; }
            .cam-close:hover { background: rgba(255,255,255,0.1); color: #fff; }

            /* --- Navigation --- */
            .cam-nav { padding: 12px 20px 0; display: flex; gap: 4px; border-bottom: 1px solid var(--cam-border); background: var(--cam-bg); flex-shrink: 0; }
            .cam-nav-item {
                flex: 1; padding: 10px; text-align: center;
                background: none; border: none;
                color: var(--cam-text-muted); font-size: 14px; font-weight: 600;
                cursor: pointer; position: relative;
                transition: 0.2s;
            }
            .cam-nav-item:hover { color: var(--cam-text); }
            .cam-nav-item.active { color: var(--cam-primary); }
            .cam-nav-item.active::after {
                content: ''; position: absolute; bottom: 0; left: 0; right: 0;
                height: 2px; background: var(--cam-primary); border-radius: 2px 2px 0 0;
            }

            /* --- Content Area --- */
            .cam-body { padding: 20px; overflow-y: auto; overscroll-behavior: contain; flex-grow: 1; }
            .cam-body::-webkit-scrollbar { width: 6px; }
            .cam-body::-webkit-scrollbar-thumb { background: var(--cam-border); border-radius: 3px; }

            /* --- Inputs --- */
            .cam-input-wrap { margin-bottom: 20px; position: relative; }
            .cam-input {
                width: 100%; background: var(--cam-surface);
                border: 1px solid var(--cam-border); border-radius: 12px;
                padding: 14px 14px 14px 44px;
                color: #fff; font-size: 16px;
                transition: border-color 0.2s;
            }
            .cam-input:focus { outline: none; border-color: var(--cam-primary); }
            .cam-input-icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--cam-text-muted); pointer-events: none; }

            /* --- Sections --- */
            .cam-section { margin-bottom: 24px; }
            .cam-section-head { font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--cam-text-muted); margin-bottom: 12px; font-weight: 700; display: flex; justify-content: space-between; align-items: center; }

            /* --- Cards & Buttons --- */
            .cam-card {
                background: var(--cam-surface); border: 1px solid var(--cam-border);
                border-radius: 12px; overflow: hidden;
            }
            .cam-btn {
                width: 100%; display: flex; align-items: center; gap: 12px;
                padding: 14px 16px; background: none; border: none;
                color: var(--cam-text); font-size: 15px; font-weight: 500; text-align: left;
                cursor: pointer; transition: background 0.2s;
                border-bottom: 1px solid var(--cam-border);
            }
            .cam-btn:last-child { border-bottom: none; }
            .cam-btn:hover { background: rgba(255,255,255,0.05); }
            .cam-btn:active { background: rgba(255,255,255,0.1); }

            .cam-btn-primary {
                background: var(--cam-primary); color: #fff; border: none;
                justify-content: center; font-weight: 600; border-radius: 12px;
                margin-top: 10px;
            }
            .cam-btn-primary:hover { background: var(--cam-primary-hover); }

            /* --- StreaMonitor Card --- */
            .cam-sm-card { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: rgba(59, 130, 246, 0.1); border: 1px solid rgba(59, 130, 246, 0.2); border-radius: 12px; }
            .cam-sm-info { display: flex; align-items: center; gap: 10px; }
            .cam-sm-dot { width: 8px; height: 8px; border-radius: 50%; background: #555; }
            .cam-sm-dot.on { background: var(--cam-success); box-shadow: 0 0 8px var(--cam-success); }
            .cam-sm-dot.rec { background: var(--cam-danger); animation: pulse 1.5s infinite; }
            @keyframes pulse { 50% { opacity: 0.5; } }

            .cam-sm-action { padding: 6px 12px; border-radius: 8px; font-size: 13px; font-weight: 600; border: none; cursor: pointer; background: var(--cam-primary); color: white; }
            .cam-sm-action:disabled { background: var(--cam-border); color: var(--cam-text-muted); cursor: default; }

            /* --- Grid for Archives --- */
            .cam-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
            .cam-grid-btn {
                background: var(--cam-surface); border: 1px solid var(--cam-border);
                border-radius: 10px; padding: 12px; display: flex; align-items: center; gap: 10px;
                color: var(--cam-text); cursor: pointer; font-size: 13px; font-weight: 500;
                transition: 0.2s;
            }
            .cam-grid-btn:hover { border-color: var(--cam-text-muted); transform: translateY(-1px); }
            .cam-grid-btn img { width: 16px; height: 16px; border-radius: 4px; }
            .cam-grid-btn.unavailable { opacity: 0.4; pointer-events: none; filter: grayscale(1); }
            .cam-grid-btn.checking { opacity: 0.7; pointer-events: none; }

            /* --- Settings Toggles --- */
            .cam-toggle-row { display: flex; justify-content: space-between; align-items: center; padding: 16px; }
            .cam-toggle { position: relative; width: 44px; height: 24px; }
            .cam-toggle input { opacity: 0; width: 0; height: 0; }
            .cam-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--cam-border); transition: .4s; border-radius: 34px; }
            .cam-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
            input:checked + .cam-slider { background-color: var(--cam-primary); }
            input:checked + .cam-slider:before { transform: translateX(20px); }

            .cam-toast { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); padding: 12px 24px; background: #fff; color: #000; font-weight: 600; border-radius: 50px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); z-index: 200000; font-size: 14px; animation: slideUpMobile 0.3s; }

            /* --- Floating Button --- */
            #cam-fab {
                position: fixed; bottom: 24px; right: 24px; width: 56px; height: 56px;
                border-radius: 28px; background: var(--cam-primary); color: white;
                border: none; font-size: 24px; cursor: pointer;
                box-shadow: 0 8px 24px rgba(59, 130, 246, 0.4);
                z-index: 99998; transition: transform 0.2s;
                display: flex; align-items: center; justify-content: center;
            }
            #cam-fab:active { transform: scale(0.9); }
        `;
        document.head.appendChild(style);
    }

    // --- Profile Manager ---
    const ProfileManager = {
        STORAGE_KEY: 'cam_profiles',
        get: () => Storage.get('cam_profiles', []),
        add: (name) => {
            let list = Storage.get('cam_profiles', []);
            if (!list.includes(name)) { list.push(name); Storage.set('cam_profiles', list); return true; }
            return false;
        },
        remove: (name) => {
            let list = Storage.get('cam_profiles', []);
            Storage.set('cam_profiles', list.filter(n => n !== name));
        }
    };

    // --- Main UI Logic ---
    const UI = {
        isOpen: false,
        activeTab: 'search',

        createMenu: function() {
            if (this.isOpen) return;
            this.isOpen = true;

            const backdrop = document.createElement('div');
            backdrop.className = 'cam-backdrop';
            backdrop.onclick = (e) => { if(e.target === backdrop) this.close(); };

            const container = document.createElement('div');
            container.className = 'cam-container';

            // Detect Context
            const hostname = window.location.hostname;
            const isCB = hostname.includes('chaturbate');
            const siteKey = isCB ? 'chaturbate' : 'stripchat';

            // Try extract username
            let currentUsername = '';
            const path = window.location.pathname.split('/').filter(p => p);
            if (isCB && path.length === 1 && !['tags','auth','search'].includes(path[0])) currentUsername = path[0];
            if (!isCB && path.length >= 1) currentUsername = path[path.length-1];

            // Render
            container.innerHTML = `
                <div class="cam-header">
                    <div class="cam-title"><span>🌟</span> Cam ARNA</div>
                    <button class="cam-close">✕</button>
                </div>

                <div class="cam-nav">
                    <button class="cam-nav-item active" data-tab="search">Search</button>
                    <button class="cam-nav-item" data-tab="saved">Saved</button>
                    <button class="cam-nav-item" data-tab="settings">Settings</button>
                </div>

                <div class="cam-body">
                    <div class="cam-input-wrap">
                        <span class="cam-input-icon">👤</span>
                        <input type="text" class="cam-input" id="cam-user-input" placeholder="Username..." value="${currentUsername}" autocomplete="off">
                    </div>

                    <div id="tab-search" class="cam-tab-content">
                        ${SM_Config.enabled ? `
                        <div class="cam-section">
                            <div class="cam-section-head">StreaMonitor (Local)</div>
                            <div class="cam-sm-card">
                                <div class="cam-sm-info">
                                    <div id="sm-dot" class="cam-sm-dot"></div>
                                    <span id="sm-text" style="font-size:14px; font-weight:500;">Initializing...</span>
                                </div>
                                <button id="sm-btn" class="cam-sm-action" disabled>Check</button>
                            </div>
                        </div>
                        ` : ''}

                        <div class="cam-section">
                            <div class="cam-section-head">Direct Access</div>
                            <div class="cam-grid">
                                <button class="cam-grid-btn main-site" data-site="stripchat" style="border-color: #8b5cf6;">💜 Stripchat</button>
                                <button class="cam-grid-btn main-site" data-site="chaturbate" style="border-color: #f97316;">🧡 Chaturbate</button>
                            </div>
                            <button id="cam-save-btn" class="cam-btn-primary">💾 Save Profile</button>
                        </div>

                        <div class="cam-section">
                            <div class="cam-section-head">Archives <span id="archive-status" style="float:right; font-weight:normal; opacity:0.6;"></span></div>
                            <div class="cam-grid">
                                ${archiveSites.map(s => `
                                    <button class="cam-grid-btn archive-link" data-url="${s.url}">
                                        <img src="${getFaviconUrl(s.domain)}" onerror="this.style.display='none'"> ${s.name}
                                    </button>
                                `).join('')}
                            </div>
                        </div>
                    </div>

                    <div id="tab-saved" class="cam-tab-content" style="display:none;">
                        <div class="cam-card" id="saved-list"></div>
                        <div id="saved-empty" style="text-align:center; padding:40px; color:var(--cam-text-muted); display:none;">
                            No profiles saved yet.
                        </div>
                    </div>

                    <div id="tab-settings" class="cam-tab-content" style="display:none;">
                        <div class="cam-section">
                            <div class="cam-section-head">Integrations</div>
                            <div class="cam-card">
                                <div class="cam-toggle-row">
                                    <span>Enable StreaMonitor</span>
                                    <label class="cam-toggle">
                                        <input type="checkbox" id="setting-sm-enabled" ${SM_Config.enabled ? 'checked' : ''}>
                                        <span class="cam-slider"></span>
                                    </label>
                                </div>
                                ${SM_Config.enabled ? `
                                <div style="padding: 16px; border-top: 1px solid var(--cam-border);">
                                    <div style="margin-bottom:8px; font-size:12px; color:var(--cam-text-muted);">API URL</div>
                                    <input class="cam-input" id="setting-sm-url" value="${SM_Config.url}" style="padding:10px; font-size:14px; margin-bottom:10px;">

                                    <div style="display:grid; grid-template-columns: 1fr 1fr; gap:10px;">
                                        <div>
                                            <div style="margin-bottom:8px; font-size:12px; color:var(--cam-text-muted);">User</div>
                                            <input class="cam-input" id="setting-sm-user" value="${SM_Config.user}" style="padding:10px; font-size:14px;">
                                        </div>
                                        <div>
                                            <div style="margin-bottom:8px; font-size:12px; color:var(--cam-text-muted);">Pass</div>
                                            <input type="password" class="cam-input" id="setting-sm-pass" value="${SM_Config.pass}" style="padding:10px; font-size:14px;">
                                        </div>
                                    </div>
                                </div>
                                ` : ''}
                            </div>
                        </div>

                        <div class="cam-section">
                            <div class="cam-section-head">Data</div>
                            <button class="cam-btn" id="btn-export">📤 Export JSON</button>
                            <button class="cam-btn" id="btn-import">📥 Import JSON</button>
                            <input type="file" id="file-import" style="display:none" accept=".json">
                        </div>

                        <div style="text-align:center; color:var(--cam-text-muted); font-size:12px; margin-top:30px;">
                            Cam ARNA v1.8 by user006-ui
                        </div>
                    </div>
                </div>
            `;

            document.body.appendChild(backdrop);
            document.body.appendChild(container);

            // Bind Events
            container.querySelector('.cam-close').onclick = () => this.close();

            // Tabs
            const tabs = container.querySelectorAll('.cam-nav-item');
            tabs.forEach(t => t.onclick = () => {
                tabs.forEach(x => x.classList.remove('active'));
                t.classList.add('active');
                container.querySelectorAll('.cam-tab-content').forEach(c => c.style.display = 'none');
                container.querySelector(`#tab-${t.dataset.tab}`).style.display = 'block';
                if (t.dataset.tab === 'saved') this.renderSaved();
            });

            // Input
            const input = container.querySelector('#cam-user-input');
            let debounce;
            input.oninput = () => {
                clearTimeout(debounce);
                debounce = setTimeout(() => {
                    this.onUserChange(input.value.trim(), siteKey);
                }, 800);
            };

            // Main Buttons
            container.querySelectorAll('.main-site').forEach(b => b.onclick = () => {
                const u = input.value.trim();
                if(u) window.open(mainSites[b.dataset.site].replace('{username}', u), '_blank');
            });

            // Save Button
            container.querySelector('#cam-save-btn').onclick = () => {
                const u = input.value.trim();
                if(!u) return;
                if(ProfileManager.add(u)) this.toast(`Saved ${u}`);
                else this.toast('Already saved');
            };

            // Archive Links
            container.querySelectorAll('.archive-link').forEach(b => b.onclick = () => {
                if(b.classList.contains('unavailable')) return;
                const u = input.value.trim();
                if(u) window.open(b.dataset.url.replace('{username}', u), '_blank');
            });

            // Settings Logic
            const toggleSM = container.querySelector('#setting-sm-enabled');
            if(toggleSM) toggleSM.onchange = () => {
                Storage.set('sm_enabled', toggleSM.checked);
                this.close(); this.createMenu(); // Re-render to show/hide sections
            };

            if(SM_Config.enabled) {
                const sUrl = container.querySelector('#setting-sm-url');
                const sUser = container.querySelector('#setting-sm-user');
                const sPass = container.querySelector('#setting-sm-pass');
                [sUrl, sUser, sPass].forEach(el => el.oninput = () => {
                    Storage.set('sm_url', sUrl.value);
                    Storage.set('sm_user', sUser.value);
                    Storage.set('sm_pass', sPass.value);
                });
            }

            // Data Export/Import
            container.querySelector('#btn-export').onclick = () => {
                const data = JSON.stringify(ProfileManager.get());
                const blob = new Blob([data], {type: 'application/json'});
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url; a.download = 'cam_rna_backup.json';
                a.click();
            };
            const fInput = container.querySelector('#file-import');
            container.querySelector('#btn-import').onclick = () => fInput.click();
            fInput.onchange = (e) => {
                const reader = new FileReader();
                reader.onload = (ev) => {
                    try {
                        const list = JSON.parse(ev.target.result);
                        if(Array.isArray(list)) {
                            Storage.set('cam_profiles', list);
                            this.toast(`Imported ${list.length} profiles`);
                            this.renderSaved();
                        }
                    } catch(err) { this.toast('Error importing'); }
                };
                if(e.target.files[0]) reader.readAsText(e.target.files[0]);
            };

            // Initial Check
            if(currentUsername) this.onUserChange(currentUsername, siteKey);
        },

        close: function() {
            document.querySelector('.cam-backdrop')?.remove();
            document.querySelector('.cam-container')?.remove();
            this.isOpen = false;
        },

        onUserChange: async function(username, site) {
            if(!username) return;

            // Reset UI
            document.querySelectorAll('.archive-link').forEach(b => b.classList.remove('unavailable', 'checking'));

            // SM Check
            if (SM_Config.enabled) {
                const smText = document.querySelector('#sm-text');
                const smDot = document.querySelector('#sm-dot');
                const smBtn = document.querySelector('#sm-btn');

                if (smText) {
                    smText.innerText = "Checking...";
                    smText.style.color = "var(--cam-text-muted)";
                    smDot.className = "cam-sm-dot";
                    smBtn.disabled = true;

                    const res = await StreaMonitor.checkStatus(username, site);

                    if (res.error) {
                        smText.innerText = res.error;
                        smText.style.color = "var(--cam-danger)";
                    } else if (res.found) {
                        if (res.data.recording) {
                            smText.innerText = "Recording";
                            smText.style.color = "var(--cam-danger)";
                            smDot.classList.add('rec');
                        } else if (res.data.running) {
                            smText.innerText = "Online (Monitored)";
                            smText.style.color = "var(--cam-success)";
                            smDot.classList.add('on');
                        } else {
                            smText.innerText = "Offline (In List)";
                            smText.style.color = "var(--cam-text-muted)";
                        }
                        smBtn.innerText = "Managed";
                    } else {
                        smText.innerText = "Not Monitored";
                        smBtn.innerText = "Add";
                        smBtn.disabled = false;
                        smBtn.onclick = async () => {
                            smBtn.innerText = "...";
                            await StreaMonitor.addStreamer(username, site);
                            this.onUserChange(username, site);
                        };
                    }
                }
            }

            // Archive Check
            const btns = document.querySelectorAll('.archive-link');
            const status = document.getElementById('archive-status');
            if(status) status.innerText = "Checking...";

            let avail = 0;
            const promises = Array.from(btns).map(async b => {
                b.classList.add('checking');
                const ok = await PageChecker.checkPage(b.dataset.url.replace('{username}', username));
                b.classList.remove('checking');
                if(!ok) b.classList.add('unavailable');
                else avail++;
            });
            await Promise.all(promises);
            if(status) status.innerText = `${avail} found`;
        },

        renderSaved: function() {
            const list = ProfileManager.get();
            const el = document.getElementById('saved-list');
            const empty = document.getElementById('saved-empty');
            if(!el) return;

            if(list.length === 0) {
                el.style.display = 'none';
                empty.style.display = 'block';
            } else {
                el.style.display = 'block';
                empty.style.display = 'none';
                el.innerHTML = list.map(u => `
                    <div style="padding:12px 16px; border-bottom:1px solid var(--cam-border); display:flex; justify-content:space-between; align-items:center;">
                        <span style="font-weight:600;">${u}</span>
                        <div style="display:flex; gap:8px;">
                            <button onclick="window.open('https://chaturbate.com/${u}/','_blank')" style="border:none; background:none; cursor:pointer;">🧡</button>
                            <button onclick="window.open('https://stripchat.com/${u}','_blank')" style="border:none; background:none; cursor:pointer;">💜</button>
                            <button class="del-btn" data-user="${u}" style="border:none; background:none; cursor:pointer;">🗑️</button>
                        </div>
                    </div>
                `).join('');

                el.querySelectorAll('.del-btn').forEach(b => b.onclick = () => {
                    ProfileManager.remove(b.dataset.user);
                    this.renderSaved();
                });
            }
        },

        toast: function(msg) {
            const t = document.createElement('div');
            t.className = 'cam-toast';
            t.innerText = msg;
            document.body.appendChild(t);
            setTimeout(() => t.remove(), 3000);
        }
    };

    // --- Init ---
    function init() {
        injectStyles();

        // Fab Button
        const fab = document.createElement('button');
        fab.id = 'cam-fab';
        fab.innerHTML = '⚡';
        fab.onclick = () => UI.createMenu();
        document.body.appendChild(fab);
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();

})();