Cam ARNA

Multi-archive search tool with local StreaMonitor integration

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

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

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

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

})();