KIRA NEXUS

Airstrip Prediction + Maximum Layer Priority + UI Recovery

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         KIRA NEXUS
// @namespace    Gemini.Torn
// @version      1.1.0
// @description  Airstrip Prediction + Maximum Layer Priority + UI Recovery
// @author       Kira
// @license      MIT
// @match        *://www.torn.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // Airstrip Flight Times (Total Seconds)
    const FLIGHT_SECONDS = {
        "Mexico": 1080, "Cayman Islands": 1500, "Canada": 1740,
        "Hawaii": 5640, "United Kingdom": 6660, "Switzerland": 7380,
        "Argentina": 7020, "Japan": 9480, "China": 10140,
        "UAE": 11400, "South Africa": 12480
    };

    const AIRSTRIP_DATA = {
        "Mexico": 600000, "Cayman Islands": 500000, "Canada": 550000,
        "Hawaii": 700000, "United Kingdom": 600000, "Argentina": 650000,
        "Switzerland": 1200000, "Japan": 800000, "China": 1100000,
        "UAE": 1000000, "South Africa": 1300000
    };

    const DEFAULTS = { apiKey: "", dayName: "United Kingdom", sleepName: "China", profitMode: "market", traderRate: 98 };
    let config = JSON.parse(GM_getValue("kira_config", JSON.stringify(DEFAULTS)));
    let stats = JSON.parse(GM_getValue("kira_stats", `{"date":"${new Date().toLocaleDateString()}", "dailyProfit":0}`));
    let isMinimized = GM_getValue("kira_minimized", false);
    
    const savedPos = JSON.parse(GM_getValue("boxPos", '{"top":"150px", "left":"10px"}'));
    const savedSize = JSON.parse(GM_getValue("boxSize", '{"width":"260px", "height":"auto"}'));
    
    let cachedTimeLeft = 0;
    let lastSync = Date.now();
    let currentLoc = "Syncing...";

    // MAXIMIZED Z-INDEX AND LAYER FIXES
    GM_addStyle(`
        #kira-tracker { 
            position: fixed !important; 
            top: ${savedPos.top}; left: ${savedPos.left}; 
            width: ${isMinimized ? '140px' : savedSize.width}; 
            min-width: ${isMinimized ? '140px' : '220px'}; 
            background: #121212; color: #e0e0e0; padding: ${isMinimized ? '10px 15px' : '20px'}; 
            border-radius: ${isMinimized ? '30px' : '20px'}; 
            font-family: 'Inter', -apple-system, sans-serif; 
            z-index: 999999999 !important; /* Maximum safe z-index */
            border: 1px solid rgba(255, 255, 255, 0.15); 
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8);
            overflow: hidden; display: flex; flex-direction: column;
            opacity: 1 !important;
            touch-action: none;
            pointer-events: auto !important;
            -webkit-transform: translate3d(0,0,0); /* Force Hardware Acceleration for layering */
        }
        #top-bar { display: flex; align-items: center; margin-bottom: ${isMinimized ? '0' : '18px'}; cursor: grab; user-select: none; padding: 5px 0; }
        #status-dot { 
            width: 10px; height: 10px; border-radius: 50%; 
            background: #00ff9f; margin-right: 12px; 
            box-shadow: 0 0 12px #00ff9f;
            animation: kira-pulse 2s infinite ease-in-out;
        }
        @keyframes kira-pulse {
            0% { opacity: 1; transform: scale(1); box-shadow: 0 0 12px #00ff9f; }
            50% { opacity: 0.7; transform: scale(0.95); box-shadow: 0 0 18px #00ff9f; }
            100% { opacity: 1; transform: scale(1); box-shadow: 0 0 12px #00ff9f; }
        }
        #title-text { flex-grow: 1; font-weight: 700; font-size: 13px; letter-spacing: 1px; color: #fff; text-transform: uppercase; display: ${isMinimized ? 'none' : 'block'}; }
        .nav-icons { display: flex; gap: 8px; }
        .nav-btn { cursor: pointer; color: #666; font-size: 18px; transition: color 0.2s; padding: 5px; font-weight: bold; }
        #tracker-body { display: ${isMinimized ? 'none' : 'block'}; }
        #mini-timer { display: ${isMinimized ? 'block' : 'none'}; font-size: 14px; font-weight: 700; color: #fff; margin-right: 10px; }
        .stat-group { margin-bottom: 15px; }
        .stat-label { font-size: 9px; text-transform: uppercase; color: #888; letter-spacing: 1px; margin-bottom: 4px; display: block; }
        .stat-value { font-size: 18px; font-weight: 600; color: #fff; }
        .stat-sub { font-size: 10px; color: #555; margin-left: 8px; cursor: pointer; text-transform: uppercase; padding: 5px; }
        #loc-display { color: #00d4ff; font-size: 12px; font-weight: 800; }
        #profit-display { color: #00ff9f; font-size: 20px; }
        .action-btn { 
            width: 100%; font-size: 10px; margin-top: 8px; cursor: pointer; 
            background: #1a1a1a; color: #aaa; border: 1px solid #333; 
            padding: 12px; border-radius: 10px; font-weight: 700; text-transform: uppercase;
        }
        #settings-menu { display: none; margin-top: 15px; padding-top: 15px; border-top: 1px solid #333; }
        .kira-input { background: #000; color: #fff; border: 1px solid #444; width: 100%; margin-bottom: 10px; font-size: 16px; padding: 10px; border-radius: 8px; }
    `);

    async function syncAPI() {
        if (!config.apiKey) return;
        try {
            const res = await fetch(`https://api.torn.com/user/?selections=travel&key=${config.apiKey}`);
            const data = await res.json();
            if (data && data.travel) {
                cachedTimeLeft = data.travel.time_left;
                currentLoc = data.travel.destination || "Torn City";
                lastSync = Date.now();
                GM_setValue("last_travel_state", {time: cachedTimeLeft, stamp: lastSync, loc: currentLoc});
            }
        } catch (e) {}
    }

    function setupUI() {
        if (document.getElementById('kira-tracker')) return;
        const box = document.createElement('div');
        box.id = 'kira-tracker';
        const getOptions = (sel) => Object.keys(AIRSTRIP_DATA).map(l => `<option value="${l}" ${l===sel?'selected':''}>${l}</option>`).join('');

        box.innerHTML = `
            <div id="top-bar">
                <div id="status-dot"></div>
                <div id="mini-timer">00:00:00</div>
                <div id="title-text">Kira Nexus</div>
                <div class="nav-icons">
                    <div id="toggle-mini" class="nav-btn">${isMinimized ? '+' : '−'}</div>
                    <div id="open-settings" class="nav-btn">⚙</div>
                </div>
            </div>
            <div id="tracker-body">
                <div class="stat-group"><span class="stat-label">Timer</span><div id="time-display" class="stat-value">00:00:00</div><span id="manual-sync" class="stat-sub">Sync</span></div>
                <div class="stat-group"><span class="stat-label">Arrival Window</span><div id="land-display" style="font-size: 14px; color: #bfef45;">--:--:--</div></div>
                <div class="stat-group" style="background: #1a1a1a; padding: 10px; border-radius: 10px; border-left: 3px solid #00d4ff;"><span class="stat-label" style="color: #00d4ff;">Current Sector</span><div id="loc-display">SYNCING...</div></div>
                <div class="stat-group"><span class="stat-label" id="profit-mode-label">Session Profit</span><div id="profit-display" class="stat-value">$0.00M</div></div>
                <button id="add-profit" class="action-btn">Log Transaction</button>
                <button id="mode-swap" class="action-btn">Switch Route</button>
            </div>
            <div id="settings-menu">
                <span class="stat-label">API Key</span><input id="set-api" class="kira-input" value="${config.apiKey}">
                <span class="stat-label">Standard</span><select id="set-day" class="kira-input">${getOptions(config.dayName)}</select>
                <span class="stat-label">Extended</span><select id="set-sleep" class="kira-input">${getOptions(config.sleepName)}</select>
                <span class="stat-label">Valuation</span>
                <select id="set-method" class="kira-input">
                    <option value="market" ${config.profitMode === 'market' ? 'selected' : ''}>Market (-5%)</option>
                    <option value="trader" ${config.profitMode === 'trader' ? 'selected' : ''}>Trader (%)</option>
                </select>
                <div id="trader-rate-wrap"><span class="stat-label">Trader Ratio %</span><input id="set-rate" type="number" class="kira-input" value="${config.traderRate}"></div>
                <button id="save-settings" class="action-btn" style="background: #fff; color: #000;">Save & Apply</button>
                <button id="reset-ui" class="action-btn" style="background: #ff4444; color: #fff; margin-top: 20px;">Emergency UI Reset</button>
            </div>
        `;
        document.body.appendChild(box);

        // Movement logic
        const topBar = document.getElementById('top-bar');
        topBar.addEventListener('pointerdown', (e) => {
            if (e.target.closest('.nav-btn')) return;
            box.setPointerCapture(e.pointerId);
            let shiftX = e.clientX - box.getBoundingClientRect().left, shiftY = e.clientY - box.getBoundingClientRect().top;
            const onMove = (ev) => {
                let newX = Math.max(0, Math.min(window.innerWidth - box.offsetWidth, ev.clientX - shiftX));
                let newY = Math.max(0, Math.min(window.innerHeight - box.offsetHeight, ev.clientY - shiftY));
                box.style.left = newX + 'px'; box.style.top = newY + 'px'; 
            };
            const onUp = () => {
                window.removeEventListener('pointermove', onMove);
                GM_setValue("boxPos", JSON.stringify({top: box.style.top, left: box.style.left}));
            };
            window.addEventListener('pointermove', onMove);
            window.addEventListener('pointerup', onUp, {once: true});
        });

        // UI Controls
        document.getElementById('toggle-mini').onclick = () => { isMinimized = !isMinimized; GM_setValue("kira_minimized", isMinimized); location.reload(); };
        document.getElementById('open-settings').onclick = () => { const m = document.getElementById('settings-menu'); m.style.display = (m.style.display === 'block') ? 'none' : 'block'; };
        document.getElementById('reset-ui').onclick = () => { GM_setValue("boxPos", '{"top":"150px", "left":"10px"}'); location.reload(); };
        document.getElementById('save-settings').onclick = () => {
            config.apiKey = document.getElementById('set-api').value;
            config.dayName = document.getElementById('set-day').value;
            config.sleepName = document.getElementById('set-sleep').value;
            config.profitMode = document.getElementById('set-method').value;
            config.traderRate = parseFloat(document.getElementById('set-rate').value) || 98;
            GM_setValue("kira_config", JSON.stringify(config)); location.reload();
        };

        document.getElementById('add-profit').onclick = async () => {
            if (!config.apiKey) return alert("API Key Required");
            try {
                const res = await fetch(`https://api.torn.com/user/?selections=basic&key=${config.apiKey}`);
                const data = await res.json();
                if (data.level < 15) return alert("International Deals are locked until Level 15.");
                const dest = GM_getValue("isSleep", false) ? config.sleepName : config.dayName;
                let finalProfit = config.profitMode === 'market' ? (AIRSTRIP_DATA[dest] || 0) * 0.95 : (AIRSTRIP_DATA[dest] || 0) * (config.traderRate / 100);
                stats.dailyProfit += finalProfit;
                GM_setValue("kira_stats", JSON.stringify(stats));
            } catch (e) { alert("API Error."); }
        };

        document.getElementById('mode-swap').onclick = () => { GM_setValue("isSleep", !GM_getValue("isSleep", false)); render(); };
        document.getElementById('manual-sync').onclick = () => syncAPI();
    }

    function render() {
        if (!document.getElementById('kira-tracker')) setupUI();
        const globalState = GM_getValue("last_travel_state", {time: 0, stamp: Date.now(), loc: "SYNCING..."});
        const isFlying = globalState.time > 0;
        const currentRoute = GM_getValue("isSleep", false) ? config.sleepName : config.dayName;
        
        let remaining = isFlying ? Math.max(0, globalState.time - Math.floor((Date.now() - globalState.stamp) / 1000)) : (FLIGHT_SECONDS[currentRoute] || 0);
        let arrivalTimestamp = isFlying ? globalState.stamp + (globalState.time * 1000) : Date.now() + (remaining * 1000);

        const pad = (n) => n.toString().padStart(2, '0');
        const timeStr = `${pad(Math.floor(remaining / 3600))}:${pad(Math.floor((remaining % 3600) / 60))}:${pad(remaining % 60)}`;
        
        if (document.getElementById('time-display')) document.getElementById('time-display').innerText = timeStr;
        if (document.getElementById('mini-timer')) document.getElementById('mini-timer').innerText = timeStr;
        if (document.getElementById('land-display')) document.getElementById('land-display').innerText = new Date(arrivalTimestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true });
        if (document.getElementById('loc-display')) document.getElementById('loc-display').innerText = globalState.loc.toUpperCase();
        if (document.getElementById('profit-display')) document.getElementById('profit-display').innerText = `$${(stats.dailyProfit / 1000000).toFixed(2)}M`;
        if (document.getElementById('mode-swap')) document.getElementById('mode-swap').innerText = `ROUTE: ${currentRoute}`.toUpperCase();
    }

    // High-frequency observer to fight Torn's dynamic UI refreshes
    const observer = new MutationObserver(() => { if (!document.getElementById('kira-tracker')) setupUI(); });
    observer.observe(document.body, { childList: true, subtree: true });

    setupUI(); syncAPI();
    setInterval(syncAPI, 30000); setInterval(render, 1000);
})();