Skip to content

Modding

Modifications can be made using Tampermonkey by hooking into the games' API. See ##Examples for more details

Examples

API-driven file-manager and bulk-seller by Rain

// ==UserScript==
// @name         DeepNet Data Dealer
// @namespace    https://tampermonkey.net
// @version      1.0
// @description  File management and bulk selling for DeepNet (API-driven)
// @author       Rain
// @match        https://deepnet.us/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    const SETTINGS_KEY = 'dnfs-settings';

    function loadSettings() {
        try {
            const s = JSON.parse(localStorage.getItem(SETTINGS_KEY));
            if (s) {
                if (typeof s.uiScale !== 'number') s.uiScale = 0;
                return s;
            }
        } catch (_) {}
        return { uiScale: 0 };
    }
    function saveSettings() {
        try { localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); } catch (_) {}
    }
    let settings = loadSettings();

    function getScale() {
        if (settings.uiScale > 0) return settings.uiScale;
        return Math.max(1.0, Math.min(1.8, 0.55 + window.innerHeight / 1600));
    }

    // ═══════════════════════════════════════════════════════════
    //  CREDENTIAL CAPTURE & API
    // ═══════════════════════════════════════════════════════════
    let machineId = null, token = null;
    const _origFetch = window.fetch;
    function installCredSniffer() {
        window.fetch = function (...args) {
            try {
                const url = typeof args[0] === 'string' ? args[0] : args[0]?.url;
                if (url?.includes('api.php') && args[1]?.body) {
                    const b = JSON.parse(args[1].body);
                    if (b.machine_id) machineId = b.machine_id;
                    if (b.token) token = b.token;
                    if (machineId && token) {
                        window.fetch = _origFetch;
                    }
                }
            } catch (_) {}
            return _origFetch.apply(this, args);
        };
    }
    installCredSniffer();

    function reqId() {
        const buf = new Uint8Array(16);
        crypto.getRandomValues(buf);
        return [...buf].map(b => b.toString(16).padStart(2, '0')).join('');
    }

    async function api(action, extra = {}) {
        if (!machineId || !token) return null;
        try {
            const r = await _origFetch(`https://deepnet.us/api.php?action=${action}`, {
                method: 'POST', credentials: 'include',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ machine_id: machineId, token, request_id: reqId(), ...extra }),
            });
            return JSON.parse((await r.text()).replace(/^\ufeff/, ''));
        } catch (e) { console.error(`[SELL] API ${action}:`, e); return null; }
    }

    // ═══════════════════════════════════════════════════════════
    //  HELPERS
    // ═══════════════════════════════════════════════════════════
    function isLoggedIn() {
        const p = document.querySelector('#prompt');
        return p && p.textContent.includes('@deepnet') && !p.textContent.includes('guest@deepnet');
    }

    function qualityLabel(q) {
        const n = Number(q || 0);
        if (n >= 5) return { label: 'legendary', color: '#a335ee' };
if (n >= 4) return { label: 'rare', color: '#0070dd' };
        if (n >= 3) return { label: 'magic', color: '#3ddc84' };
        return { label: 'normal', color: 'var(--dos-text-dim)' };
    }

    function timeLeft(expiresAt) {
        if (!expiresAt) return '';
        const ms = new Date(expiresAt + 'Z').getTime() - Date.now();
        if (ms <= 0) return 'expired';
        const h = Math.floor(ms / 3600000);
        const m = Math.floor((ms % 3600000) / 60000);
        return `${h}h ${m}m`;
    }

    // ═══════════════════════════════════════════════════════════
    //  STATE
    // ═══════════════════════════════════════════════════════════
    let files = [];
    let selected = new Set();
    let selling = false;
    let totalEarned = 0;

    // ═══════════════════════════════════════════════════════════
    //  CORE ACTIONS
    // ═══════════════════════════════════════════════════════════
    async function refreshFiles() {
        const data = await api('get_files');
        if (!data?.success) { logEvent('Failed to fetch files'); return; }
        files = (data.files || []).filter(f => f.sellable && !f.sold && !f.expired);
        files.sort((a, b) => Number(b.value) - Number(a.value));
        // Remove any selected ids that no longer exist
        const ids = new Set(files.map(f => f.id));
        for (const id of selected) { if (!ids.has(id)) selected.delete(id); }
        if (currentView === 'main') renderMain();
    }

    async function sellSelected() {
        if (selling || selected.size === 0) return;
        selling = true;
        const queue = files.filter(f => selected.has(f.id));
        const total = queue.length;
        let earned = 0;
        renderMain();

        for (let i = 0; i < queue.length; i++) {
            const f = queue[i];
            setStatus('working', `Selling ${i + 1}/${total}...`);
            const r = await api('sell_file', { fileId: f.id });
            if (r?.success) {
                const amt = Number(r.earned || 0);
                earned += amt;
                totalEarned += amt;
                logEvent(r.message || `Sold ${f.filename} for ${amt} RCH`);
            } else {
                logEvent(`Failed: ${f.filename} — ${r?.error || 'unknown'}`);
            }
        }

        selected.clear();
        setStatus('ready', `+${earned} RCH`);
        logEvent(`Batch complete: +${earned} RCH (session total: ${totalEarned})`);
        await refreshFiles();
        selling = false;
        renderMain();
    }

    // ═══════════════════════════════════════════════════════════
    //  UI
    // ═══════════════════════════════════════════════════════════
    let panelEl = null, logLines = [], currentView = 'main';

    function logEvent(msg) {
        const ts = new Date().toLocaleTimeString('en-GB', { hour12: false }).slice(0, 5);
        logLines.push({ ts, msg });
        if (logLines.length > 50) logLines.shift();
        renderLog();
    }

    function setStatus(state, text) {
        const el = document.getElementById('dnfs-status');
        if (el) { el.textContent = text || ''; el.className = `dnfs-status dnfs-s-${state}`; }
        const gfx = document.querySelector('.deepos-icon[data-app="dnfs"] .deepos-icon-gfx');
        if (gfx) {
            gfx.style.color = state === 'working' ? 'var(--dos-flag-active)' :
                              state === 'ready' ? '#4a8f4a' :
                              state === 'error' ? '#8f4a4a' : 'var(--dos-icon-color)';
        }
    }

    function injectStyles() {
        const style = document.createElement('style');
        style.textContent = `
            #dnfs-panel {
                display: none; position: fixed; z-index: 6000; width: 480px;
                flex-direction: column;
                font-family: var(--dos-font, Consolas, "Courier New", monospace);
                font-size: var(--dos-font-sm, 12px);
                color: var(--dos-text, #b3b3b3);
                background: var(--dos-bg-window, #0a0a0a);
                border: 1px solid var(--dos-border, #222);
                box-shadow: 4px 4px 12px var(--dos-shadow, rgba(0,0,0,0.6));
                cursor: none; transform-origin: top left;
            }
            #dnfs-panel.visible { display: flex; }
            .dnfs-titlebar {
                display: flex; align-items: center; padding: 0 8px; height: 26px;
                background: var(--dos-tab-bg, #1a1a1a);
                border-bottom: 1px solid var(--dos-border, #222);
                cursor: none; flex-shrink: 0; gap: 6px;
            }
            .dnfs-title {
                font-size: 11px; font-weight: normal; letter-spacing: 0.06em;
                text-transform: uppercase; color: var(--dos-text-label, #a3a3a3);
                flex: 1;
            }
            .dnfs-hdr-btn {
                width: 14px; height: 14px;
                background: var(--dos-border, #222); border: 1px solid var(--dos-border-hi, #333);
                cursor: none; display: flex; align-items: center; justify-content: center;
                font-size: 10px; color: var(--dos-text-dim, #7a7a7a); line-height: 1;
            }
            .dnfs-hdr-btn:hover { background: var(--dos-bg-hover); border-color: var(--dos-border-hi); color: var(--dos-text-hi); }
            .dnfs-hdr-btn.close:hover { background: var(--dos-close-bg); border-color: var(--dos-close-border); }
            .dnfs-body { padding: 10px 12px; display: flex; flex-direction: column; gap: 10px; }

            /* Info bar */
            .dnfs-info {
                display: flex; align-items: center; justify-content: space-between;
                padding: 6px 8px;
                background: var(--dos-bg-panel, #1a1a1a); border: 1px solid var(--dos-border-lo, #1a1a1a);
            }
            .dnfs-info-left { display: flex; gap: 12px; align-items: center; }
            .dnfs-info-count { font-size: 11px; color: var(--dos-text); }
            .dnfs-info-value { font-size: 11px; color: #3ddc84; }
            .dnfs-status { font-size: 11px; letter-spacing: 0.04em; }
            .dnfs-s-idle { color: var(--dos-text-dim); }
            .dnfs-s-working { color: var(--dos-flag-active, #884444); }
            .dnfs-s-ready { color: #5a8f5a; }
            .dnfs-s-error { color: #8f4a4a; }

            /* File list */
            .dnfs-list {
                border: 1px solid var(--dos-border-lo, #1a1a1a);
                background: var(--dos-bg-panel, #1a1a1a);
                max-height: 260px; overflow-y: auto;
            }
            .dnfs-list-empty {
                padding: 12px; text-align: center;
                color: var(--dos-text-xdim, #696969); font-style: italic; font-size: 11px;
            }
            .dnfs-file-hdr {
                display: flex; gap: 6px; padding: 3px 8px; font-size: 9px;
                letter-spacing: 0.08em; text-transform: uppercase;
                color: var(--dos-text-xdim); border-bottom: 1px solid var(--dos-border-lo);
                margin-left: 22px;
            }
            .dnfs-file-hdr span:nth-child(1) { flex: 1; }
            .dnfs-file-hdr span:nth-child(2) { width: 70px; }
            .dnfs-file-hdr span:nth-child(3) { width: 50px; text-align: right; }
            .dnfs-file-hdr span:nth-child(4) { width: 50px; text-align: right; }
            .dnfs-file {
                display: flex; align-items: center; gap: 6px;
                padding: 3px 8px; font-size: 11px;
                border-bottom: 1px solid var(--dos-border-lo, #1a1a1a);
                cursor: none;
            }
            .dnfs-file:last-child { border-bottom: none; }
            .dnfs-file:hover { background: var(--dos-bg-hover); }
            .dnfs-file.selected { background: var(--dos-bg-selected); }
            .dnfs-file-chk {
                width: 14px; height: 14px; flex-shrink: 0;
                border: 1px solid var(--dos-border-hi, #333);
                background: var(--dos-bg, #0c0c0c);
                display: flex; align-items: center; justify-content: center;
                font-size: 10px; color: var(--dos-accent-hi);
            }
            .dnfs-file.selected .dnfs-file-chk { border-color: var(--dos-accent); }
            .dnfs-file-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dos-text); }
            .dnfs-file-cat { width: 70px; font-size: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
            .dnfs-file-val { width: 50px; text-align: right; color: var(--dos-text-hi); }
            .dnfs-file-exp { width: 50px; text-align: right; font-size: 10px; color: var(--dos-text-xdim); }

            /* Buttons */
            .dnfs-btns { display: flex; gap: 4px; }
            .dnfs-btn {
                flex: 1; padding: 5px 0; text-align: center;
                font-family: inherit; font-size: 11px; font-weight: normal;
                letter-spacing: 0.06em; text-transform: uppercase;
                border: 1px solid var(--dos-border, #222);
                background: var(--dos-bg-panel, #1a1a1a);
                color: var(--dos-text-label, #a3a3a3);
                cursor: none; transition: background 0.15s, color 0.15s;
            }
            .dnfs-btn:hover { background: var(--dos-bg-hover); color: var(--dos-text-hi); border-color: var(--dos-border-hi); }
            .dnfs-btn:disabled { opacity: 0.4; pointer-events: none; }
            .dnfs-btn.sell { color: #3ddc84; border-color: #335533; }
            .dnfs-btn.sell:hover { background: rgba(16,40,16,0.5); border-color: #448844; }

            /* Log */
            .dnfs-log {
                border: 1px solid var(--dos-border-lo);
                background: var(--dos-bg, #0c0c0c);
                max-height: 80px; overflow-y: auto; font-size: 10px; padding: 4px 6px;
            }
            .dnfs-log-line { color: var(--dos-text-dim); line-height: 1.5; }
            .dnfs-log-ts { color: var(--dos-text-xdim); margin-right: 6px; }

            /* Settings */
            .dnfs-set-section {
                margin-top: 4px; font-size: 10px; letter-spacing: 0.1em;
                text-transform: uppercase; color: var(--dos-text-xdim); margin-bottom: 4px;
            }
            .dnfs-scale-row { display: flex; align-items: center; gap: 4px; }
            .dnfs-scale-btn {
                width: 22px; height: 22px;
                background: var(--dos-bg-panel); border: 1px solid var(--dos-border);
                color: var(--dos-text-label); font-size: 13px; font-family: inherit;
                display: flex; align-items: center; justify-content: center; cursor: none;
            }
            .dnfs-scale-btn:hover { border-color: var(--dos-border-hi); color: var(--dos-text-hi); background: var(--dos-bg-hover); }
            .dnfs-scale-label {
                font-size: 11px; color: var(--dos-text); min-width: 90px;
                text-align: center; letter-spacing: 0.04em;
            }

            /* CLI fallback */
            #dnfs-cli-btn {
                display: none; min-width: 18px; padding: 1px 5px;
                text-align: center; font-weight: bold; font-size: 13px; font-family: inherit;
                color: #9cf79c; border: 1px solid #4a8f4a; background: rgba(12,42,12,0.65);
                cursor: pointer; letter-spacing: 0.5px; white-space: nowrap;
            }
            #dnfs-cli-btn:hover { background: rgba(20,60,20,0.8); border-color: #6abf6a; color: #bfffbf; }
        `;
        document.head.appendChild(style);
    }

    function buildPanel() {
        panelEl = document.createElement('div');
        panelEl.id = 'dnfs-panel';
        panelEl.innerHTML = `
            <div class="dnfs-titlebar">
                <span class="dnfs-title">Data Dealer</span>
                <div class="dnfs-hdr-btn" id="dnfs-settings-btn" title="Settings">\u2699</div>
                <div class="dnfs-hdr-btn close" id="dnfs-close">\u00D7</div>
            </div>
            <div class="dnfs-body" id="dnfs-body"></div>`;
        document.body.appendChild(panelEl);
        makePanelDraggable(panelEl, panelEl.querySelector('.dnfs-titlebar'));

        document.getElementById('dnfs-close').addEventListener('click', hidePanel);
        document.getElementById('dnfs-settings-btn').addEventListener('click', () => {
            switchView(currentView === 'settings' ? 'main' : 'settings');
        });
    }

    function switchView(view) {
        currentView = view;
        if (view === 'main') renderMain(); else renderSettings();
        const btn = document.getElementById('dnfs-settings-btn');
        if (btn) btn.style.color = view === 'settings' ? 'var(--dos-accent-hi)' : '';
    }

    function centerPanel() {
        if (!panelEl) return;
        const s = getScale();
        const pw = 480 * s;
        const ph = (panelEl.offsetHeight || 400) * s;
        panelEl.style.left = Math.max(0, (window.innerWidth - pw) / 2) + 'px';
        panelEl.style.top = Math.max(0, (window.innerHeight - ph) / 2) + 'px';
        panelEl.style.transform = `scale(${s})`;
        delete panelEl.dataset.dragged;
    }

    function applyScale() {
        if (!panelEl) return;
        if (panelEl.dataset.dragged) panelEl.style.transform = `scale(${getScale()})`;
        else centerPanel();
    }

    // ── Main view ──
    function renderMain() {
        const body = document.getElementById('dnfs-body');
        if (!body || currentView !== 'main') return;

        const sellableCount = files.length;
        const selectedValue = files.filter(f => selected.has(f.id)).reduce((s, f) => s + Number(f.value || 0), 0);

        body.innerHTML = `
            <div class="dnfs-info">
                <div class="dnfs-info-left">
                    <span class="dnfs-info-count">${selected.size} / ${sellableCount} files</span>
                    <span class="dnfs-info-value">${selectedValue > 0 ? selectedValue + ' RCH' : ''}</span>
                </div>
                <span class="dnfs-status dnfs-s-idle" id="dnfs-status">idle</span>
            </div>
            <div class="dnfs-file-hdr">
                <span>Filename</span>
                <span>Category</span>
                <span>Value</span>
                <span>Expires</span>
            </div>
            <div class="dnfs-list" id="dnfs-list"></div>
            <div class="dnfs-btns">
                <button class="dnfs-btn" id="dnfs-selall">Select All</button>
                <button class="dnfs-btn" id="dnfs-selnone">Select None</button>
                <button class="dnfs-btn" id="dnfs-refresh">Refresh</button>
                <button class="dnfs-btn sell" id="dnfs-sell" ${selling || selected.size === 0 ? 'disabled' : ''}>Sell (${selected.size})</button>
            </div>
            <div class="dnfs-log" id="dnfs-log"></div>`;

        const listEl = document.getElementById('dnfs-list');
        if (files.length === 0) {
            listEl.innerHTML = '<div class="dnfs-list-empty">No sellable files</div>';
        } else {
            for (const f of files) {
                const sel = selected.has(f.id);
                const q = qualityLabel(f.quality);
                const row = document.createElement('div');
                row.className = `dnfs-file${sel ? ' selected' : ''}`;
                row.innerHTML =
                    `<div class="dnfs-file-chk">${sel ? '\u2713' : ''}</div>` +
                    `<span class="dnfs-file-name" style="color:${q.color}" title="${f.filename}">${f.filename}</span>` +
                    `<span class="dnfs-file-cat" style="color:var(--dos-text-xdim)">${f.category || ''}</span>` +
                    `<span class="dnfs-file-val">${f.value}</span>` +
                    `<span class="dnfs-file-exp">${timeLeft(f.expires_at)}</span>`;
                row.addEventListener('click', () => {
                    if (selling) return;
                    if (selected.has(f.id)) selected.delete(f.id); else selected.add(f.id);
                    renderMain();
                });
                listEl.appendChild(row);
            }
        }

        document.getElementById('dnfs-selall').addEventListener('click', () => {
            files.forEach(f => selected.add(f.id));
            renderMain();
        });
        document.getElementById('dnfs-selnone').addEventListener('click', () => {
            selected.clear();
            renderMain();
        });
        document.getElementById('dnfs-refresh').addEventListener('click', () => {
            if (!selling) refreshFiles();
        });
        document.getElementById('dnfs-sell').addEventListener('click', () => {
            if (!selling && selected.size > 0) sellSelected();
        });
        renderLog();
    }

    // ── Settings view ──
    function renderSettings() {
        const body = document.getElementById('dnfs-body');
        if (!body) return;
        const scaleLabel = settings.uiScale === 0 ? `Auto (${Math.round(getScale() * 100)}%)` : `${Math.round(settings.uiScale * 100)}%`;
        body.innerHTML = `
            <div class="dnfs-set-section">UI Scale</div>
            <div class="dnfs-scale-row">
                <div class="dnfs-scale-btn" id="dnfs-sc-down">\u2212</div>
                <span class="dnfs-scale-label" id="dnfs-sc-label">${scaleLabel}</span>
                <div class="dnfs-scale-btn" id="dnfs-sc-up">+</div>
                <div class="dnfs-scale-btn" id="dnfs-sc-auto" style="margin-left:4px;width:auto;padding:0 6px;${settings.uiScale===0?'color:var(--dos-accent-hi);border-color:var(--dos-accent)':''}">Auto</div>
            </div>`;

        document.getElementById('dnfs-sc-down').addEventListener('click', () => {
            settings.uiScale = Math.max(0.8, Math.round(((settings.uiScale || getScale()) - 0.1) * 10) / 10);
            saveSettings(); applyScale(); renderSettings();
        });
        document.getElementById('dnfs-sc-up').addEventListener('click', () => {
            settings.uiScale = Math.min(2.0, Math.round(((settings.uiScale || getScale()) + 0.1) * 10) / 10);
            saveSettings(); applyScale(); renderSettings();
        });
        document.getElementById('dnfs-sc-auto').addEventListener('click', () => {
            settings.uiScale = 0; saveSettings(); applyScale(); renderSettings();
        });
    }

    function renderLog() {
        const el = document.getElementById('dnfs-log');
        if (!el) return;
        el.innerHTML = '';
        for (const l of logLines) {
            const div = document.createElement('div');
            div.className = 'dnfs-log-line';
            div.innerHTML = `<span class="dnfs-log-ts">${l.ts}</span>${l.msg}`;
            el.appendChild(div);
        }
        el.scrollTop = el.scrollHeight;
    }

    function showPanel() {
        if (!panelEl) buildPanel();
        panelEl.classList.add('visible');
        currentView = 'main'; renderMain();
        requestAnimationFrame(() => centerPanel());
        if (!selling && machineId && token) refreshFiles();
    }

    function hidePanel() { if (panelEl) panelEl.classList.remove('visible'); }

    function makePanelDraggable(el, handle) {
        let ox, oy, dx, dy;
        handle.addEventListener('mousedown', (e) => {
            if (e.target.closest('.dnfs-hdr-btn')) return;
            const rect = el.getBoundingClientRect();
            el.style.transform = `scale(${getScale()})`;
            el.style.left = rect.left + 'px';
            el.style.top = rect.top + 'px';
            el.dataset.dragged = '1';
            ox = e.clientX; oy = e.clientY; dx = rect.left; dy = rect.top;
            const mv = (e2) => { el.style.left = (dx + e2.clientX - ox) + 'px'; el.style.top = (dy + e2.clientY - oy) + 'px'; };
            const up = () => { document.removeEventListener('mousemove', mv); document.removeEventListener('mouseup', up); };
            document.addEventListener('mousemove', mv); document.addEventListener('mouseup', up);
            e.preventDefault();
        });
    }

    // ═══════════════════════════════════════════════════════════
    //  DESKTOP ICON + CLI FALLBACK
    // ═══════════════════════════════════════════════════════════
    let sawDeepOSPending = false, isDesktopIcon = false, cliBtnEl = null;
    const ICON_POS_KEY = 'deepos_icon_positions';
    function isDeepOSPending() {
        const p = window._deeposBootPending || document.body.classList.contains('deepos-active') || (window._DOS && window._DOS.active);
        if (p) sawDeepOSPending = true; return p || sawDeepOSPending;
    }
    function loadIconPositions() { try { return JSON.parse(localStorage.getItem(ICON_POS_KEY)) || {}; } catch (_) { return {}; } }
    function saveIconPositions() {
        const p = {}; document.querySelectorAll('#deepos-icons .deepos-icon').forEach(i => {
            const id = i.dataset.app || i.dataset.intApp || i.dataset.sysApp;
            if (id && i.style.left) p[id] = { x: parseInt(i.style.left) || 0, y: parseInt(i.style.top) || 0 };
        }); try { localStorage.setItem(ICON_POS_KEY, JSON.stringify(p)); } catch (_) {}
    }
    function calcIconSlot(idx) {
        const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16, vw = window.innerWidth / 100;
        const w = Math.min(6 * rem, Math.max(3.8 * rem, 5.2 * vw)), gh = Math.min(3.7 * rem, Math.max(2.1 * rem, 3.3 * vw));
        const ch = gh + 2.4 * rem, mr = Math.max(1, Math.floor((window.innerHeight - 40) / ch));
        return { x: 0.3 * rem + Math.floor(idx / mr) * w, y: 0.3 * rem + (idx % mr) * ch };
    }
    function applyIconPos(icon, id) {
        const s = loadIconPositions(); icon.style.position = 'absolute';
        if (s[id]) { icon.style.left = s[id].x + 'px'; icon.style.top = s[id].y + 'px'; }
        else { const p = calcIconSlot(document.querySelectorAll('#deepos-icons .deepos-icon').length); icon.style.left = p.x + 'px'; icon.style.top = p.y + 'px'; }
    }
    function makeIconDraggable(icon) {
        let ox = 0, oy = 0, d = false, m = false, w = false;
        icon.addEventListener('mousedown', e => { if (e.button) return; d = true; m = false; ox = e.clientX - (parseInt(icon.style.left) || 0); oy = e.clientY - (parseInt(icon.style.top) || 0); e.preventDefault(); });
        document.addEventListener('mousemove', e => {
            if (!d) return;
            if (!m && Math.abs(e.clientX - (ox + parseInt(icon.style.left || 0))) < 3 && Math.abs(e.clientY - (oy + parseInt(icon.style.top || 0))) < 3) return;
            m = true; const p = icon.parentElement.getBoundingClientRect();
            icon.style.left = Math.max(0, Math.min(e.clientX - ox, p.width - icon.offsetWidth)) + 'px';
            icon.style.top = Math.max(0, Math.min(e.clientY - oy, p.height - icon.offsetHeight)) + 'px';
        });
        document.addEventListener('mouseup', () => { if (!d) return; if (m) { saveIconPositions(); w = true; setTimeout(() => { w = false; }, 400); } d = false; m = false; });
        return () => w;
    }

    function placeUI() {
        if (isDesktopIcon || (cliBtnEl && cliBtnEl.parentElement)) return true;
        const icons = document.querySelector('#deepos-icons');
        if (icons) {
            const icon = document.createElement('div'); icon.className = 'deepos-icon'; icon.dataset.app = 'dnfs';
            icon.innerHTML = '<div class="deepos-icon-gfx" style="background:var(--dos-icon-bg);color:var(--dos-icon-color);font-size:clamp(0.5rem,0.85vw,1rem);font-weight:bold;letter-spacing:0.03em;">RCH</div><span>Dealer</span>';
            applyIconPos(icon, 'dnfs'); const chk = makeIconDraggable(icon);
            icon.addEventListener('dblclick', () => { if (!chk()) showPanel(); });
            icons.appendChild(icon); isDesktopIcon = true; return true;
        }
        if (isDeepOSPending()) return false;
        cliBtnEl = document.createElement('span'); cliBtnEl.id = 'dnfs-cli-btn';
        cliBtnEl.textContent = 'SELL'; cliBtnEl.title = 'Data Dealer';
        cliBtnEl.addEventListener('click', showPanel);
        const sr = document.querySelector('#status-right'), sb = document.querySelector('#statusbar');
        if (sr && sb) sb.insertBefore(cliBtnEl, sr); else if (sb) sb.appendChild(cliBtnEl); else return false;
        return true;
    }

    // ═══════════════════════════════════════════════════════════
    //  BOOTSTRAP
    // ═══════════════════════════════════════════════════════════
    function init() {
        injectStyles();
        const bc = setInterval(() => {
            if (!isLoggedIn()) return; if (!placeUI()) return;
            clearInterval(bc);
            if (cliBtnEl) cliBtnEl.style.display = 'inline-block';
            console.log('[SELL] v1.0 placed as', isDesktopIcon ? 'icon' : 'cli');
        }, 500);
    }
    setTimeout(init, 2000);
})();