Torn City OD Tracker (HTML Parser)

Parses Xanax and overdose data directly from the Addiction Log page HTML

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Torn City OD Tracker (HTML Parser)
// @namespace    http://tampermonkey.net/
// @version      10.0
// @description  Parses Xanax and overdose data directly from the Addiction Log page HTML
// @author       c0pyc4t
// @match        https://www.torn.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      torn.com
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let xanaxEvents = [];
    let overdoseEvents = [];
    let rehabilitationEvents = [];
    let currentStats = {
        xanaxSinceLastOD: 0,
        lastODTimestamp: null,
        lastODDate: null,
        totalXanaxCount: 0,
        totalODCount: 0,
        addictionLevel: 0
    };
    
    let panelPosition = { left: 20, top: 100 };
    let isLoading = false;

    // ================= STORAGE =================
    function saveData() {
        GM_setValue('xanax_events_v3', JSON.stringify(xanaxEvents));
        GM_setValue('overdose_events_v3', JSON.stringify(overdoseEvents));
        GM_setValue('rehab_events_v3', JSON.stringify(rehabilitationEvents));
        GM_setValue('current_stats_v3', JSON.stringify(currentStats));
        GM_setValue('panel_position', JSON.stringify(panelPosition));
    }
    
    function loadData() {
        xanaxEvents = JSON.parse(GM_getValue('xanax_events_v3', '[]'));
        overdoseEvents = JSON.parse(GM_getValue('overdose_events_v3', '[]'));
        rehabilitationEvents = JSON.parse(GM_getValue('rehab_events_v3', '[]'));
        currentStats = JSON.parse(GM_getValue('current_stats_v3', JSON.stringify(currentStats)));
        panelPosition = JSON.parse(GM_getValue('panel_position', JSON.stringify({ left: 20, top: 100 })));
    }

    // ================= FETCH AND PARSE ADDICTION LOG =================
    async function fetchAddictionLog() {
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: "https://www.torn.com/page.php?sid=log&cat=126",
                onload: (response) => {
                    if (response.status === 200) {
                        console.log("✅ Addiction log page loaded");
                        resolve(response.responseText);
                    } else {
                        console.error("Failed to load page:", response.status);
                        resolve(null);
                    }
                },
                onerror: () => resolve(null)
            });
        });
    }

    function parseAddictionLog(html) {
        const newXanax = [];
        const newOverdoses = [];
        const newRehabs = [];
        
        console.log("📝 Parsing HTML log entries...");
        
        // Create a temporary DOM element to parse the HTML
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        
        // Find all log-text spans
        const logSpans = doc.querySelectorAll('span.log-text');
        console.log(`📊 Found ${logSpans.length} log entries`);
        
        // Also look for date headers
        const dateHeaders = doc.querySelectorAll('.log-date, .date-header, [class*="date"]');
        
        let currentDate = new Date();
        
        // Process date headers first to establish timeline
        dateHeaders.forEach(header => {
            const headerText = header.textContent.trim().toUpperCase();
            if (headerText.includes('TODAY')) {
                currentDate = new Date();
            } else if (headerText.includes('YESTERDAY')) {
                currentDate = new Date();
                currentDate.setDate(currentDate.getDate() - 1);
            } else {
                const daysMatch = headerText.match(/(\d+)\s+DAYS?\s+AGO/);
                if (daysMatch) {
                    const daysAgo = parseInt(daysMatch[1]);
                    currentDate = new Date();
                    currentDate.setDate(currentDate.getDate() - daysAgo);
                }
            }
        });
        
        // Process each log entry
        logSpans.forEach(span => {
            const logText = span.textContent.trim();
            
            // Look for timestamps in parent elements or nearby
            let timestamp = currentDate.getTime();
            let dateStr = currentDate.toLocaleString();
            
            // Check for time in nearby elements
            let parent = span.parentElement;
            let timeElement = parent?.querySelector('.log-time, [class*="time"]');
            if (timeElement) {
                const timeText = timeElement.textContent.trim();
                const timeMatch = timeText.match(/(\d{2}):(\d{2})/);
                if (timeMatch) {
                    const hours = parseInt(timeMatch[1]);
                    const minutes = parseInt(timeMatch[2]);
                    const dateWithTime = new Date(currentDate);
                    dateWithTime.setHours(hours, minutes, 0);
                    timestamp = dateWithTime.getTime();
                    dateStr = dateWithTime.toLocaleString();
                }
            }
            
            // Check for Xanax usage
            if (logText.toLowerCase().includes('xanax')) {
                // Check if it's an overdose
                if (logText.toLowerCase().includes('overdose') || logText.toLowerCase().includes('od')) {
                    console.log(`💀 Overdose found: ${dateStr} - ${logText.substring(0, 50)}...`);
                    newOverdoses.push({
                        timestamp: timestamp,
                        date: dateStr,
                        text: logText,
                        type: 'overdose'
                    });
                } 
                // Regular Xanax use
                else if (logText.toLowerCase().includes('used') || logText.toLowerCase().includes('gain')) {
                    console.log(`💊 Xanax found: ${dateStr} - ${logText.substring(0, 50)}...`);
                    newXanax.push({
                        timestamp: timestamp,
                        date: dateStr,
                        text: logText,
                        type: 'xanax'
                    });
                }
            }
            
            // Track rehabilitation (resets addiction)
            if (logText.toLowerCase().includes('rehabilitated')) {
                console.log(`💚 Rehab found: ${dateStr}`);
                newRehabs.push({
                    timestamp: timestamp,
                    date: dateStr,
                    text: logText,
                    type: 'rehab'
                });
            }
        });
        
        // Also use regex as backup to catch any missed entries
        const xanaxRegex = /You used some Xanax/g;
        const odRegex = /You overdosed on some Xanax/g;
        const rehabRegex = /You rehabilitated \d+ times/g;
        
        let match;
        while ((match = xanaxRegex.exec(html)) !== null) {
            // Check if already captured
            const contextStart = Math.max(0, match.index - 200);
            const context = html.substring(contextStart, match.index + 200);
            
            // Try to find timestamp in context
            let timestamp = Date.now();
            let dateStr = new Date().toLocaleString();
            
            const timeMatch = context.match(/(\d{2}:\d{2}:\d{2}\s*-\s*\d{2}\/\d{2}\/\d{2})/);
            if (timeMatch) {
                const parsedDate = parseLogDate(timeMatch[1]);
                timestamp = parsedDate.getTime();
                dateStr = parsedDate.toLocaleString();
            }
            
            if (!newXanax.some(x => Math.abs(x.timestamp - timestamp) < 60000)) {
                newXanax.push({
                    timestamp: timestamp,
                    date: dateStr,
                    text: "You used some Xanax",
                    type: 'xanax'
                });
            }
        }
        
        while ((match = odRegex.exec(html)) !== null) {
            const contextStart = Math.max(0, match.index - 200);
            const context = html.substring(contextStart, match.index + 200);
            
            let timestamp = Date.now();
            let dateStr = new Date().toLocaleString();
            
            const timeMatch = context.match(/(\d{2}:\d{2}:\d{2}\s*-\s*\d{2}\/\d{2}\/\d{2})/);
            if (timeMatch) {
                const parsedDate = parseLogDate(timeMatch[1]);
                timestamp = parsedDate.getTime();
                dateStr = parsedDate.toLocaleString();
            }
            
            if (!newOverdoses.some(od => Math.abs(od.timestamp - timestamp) < 60000)) {
                newOverdoses.push({
                    timestamp: timestamp,
                    date: dateStr,
                    text: "You overdosed on some Xanax",
                    type: 'overdose'
                });
            }
        }
        
        console.log(`📊 Parse results: ${newXanax.length} Xanax, ${newOverdoses.length} ODs, ${newRehabs.length} Rehabs`);
        
        return { xanax: newXanax, overdoses: newOverdoses, rehabs: newRehabs };
    }
    
    function parseLogDate(dateStr) {
        // Format: "19:47:39 - 01/04/26"
        const timeMatch = dateStr.match(/(\d{2}):(\d{2}):(\d{2})\s*-\s*(\d{2})\/(\d{2})\/(\d{2})/);
        if (timeMatch) {
            const hours = parseInt(timeMatch[1]);
            const minutes = parseInt(timeMatch[2]);
            const seconds = parseInt(timeMatch[3]);
            const day = parseInt(timeMatch[4]);
            const month = parseInt(timeMatch[5]) - 1;
            let year = parseInt(timeMatch[6]);
            year = 2000 + year;
            
            const date = new Date(year, month, day, hours, minutes, seconds);
            if (!isNaN(date.getTime())) {
                return date;
            }
        }
        return new Date();
    }

    // ================= UPDATE STATS =================
    function updateStats() {
        // Sort by timestamp (newest first)
        xanaxEvents.sort((a, b) => b.timestamp - a.timestamp);
        overdoseEvents.sort((a, b) => b.timestamp - a.timestamp);
        rehabilitationEvents.sort((a, b) => b.timestamp - a.timestamp);
        
        // Remove duplicates
        const uniqueXanax = [];
        const seenXanax = new Set();
        for (let x of xanaxEvents) {
            const key = `${x.timestamp}`;
            if (!seenXanax.has(key)) {
                seenXanax.add(key);
                uniqueXanax.push(x);
            }
        }
        xanaxEvents = uniqueXanax;
        
        const uniqueODs = [];
        const seenODs = new Set();
        for (let od of overdoseEvents) {
            const key = `${od.timestamp}`;
            if (!seenODs.has(key)) {
                seenODs.add(key);
                uniqueODs.push(od);
            }
        }
        overdoseEvents = uniqueODs;
        
        // Get last overdose
        if (overdoseEvents.length > 0) {
            const lastOD = overdoseEvents[0];
            currentStats.lastODTimestamp = lastOD.timestamp;
            currentStats.lastODDate = lastOD.date;
            
            // Count Xanax since last OD
            currentStats.xanaxSinceLastOD = xanaxEvents.filter(x => x.timestamp > lastOD.timestamp).length;
        } else {
            currentStats.xanaxSinceLastOD = xanaxEvents.length;
            currentStats.lastODDate = null;
            currentStats.lastODTimestamp = null;
        }
        
        currentStats.totalXanaxCount = xanaxEvents.length;
        currentStats.totalODCount = overdoseEvents.length;
        
        // Calculate addiction level (rough estimate)
        // Each Xanax increases addiction, rehab reduces it
        let addiction = 0;
        const events = [...xanaxEvents, ...overdoseEvents, ...rehabilitationEvents];
        events.sort((a, b) => a.timestamp - b.timestamp);
        
        for (let event of events) {
            if (event.type === 'xanax' || event.type === 'overdose') {
                addiction = Math.min(100, addiction + 5);
            } else if (event.type === 'rehab') {
                addiction = Math.max(0, addiction - 100);
            }
        }
        currentStats.addictionLevel = Math.round(addiction);
        
        saveData();
        updateUI();
        
        console.log(`📈 Stats updated: ${currentStats.xanaxSinceLastOD} Xanax since last OD`);
        console.log(`📊 Total: ${xanaxEvents.length} Xanax, ${overdoseEvents.length} ODs`);
        console.log(`💊 Addiction level: ${currentStats.addictionLevel}%`);
    }

    // ================= PREDICTIONS =================
    function calculateODProbability() {
        if (overdoseEvents.length === 0) {
            const risk = Math.min(95, Math.round((currentStats.xanaxSinceLastOD / 18) * 100));
            return risk;
        }
        
        // Calculate average Xanax between ODs
        const xanaxBetweenODs = [];
        for (let i = 0; i < overdoseEvents.length - 1; i++) {
            const currentOD = overdoseEvents[i];
            const nextOD = overdoseEvents[i + 1];
            const count = xanaxEvents.filter(x => 
                x.timestamp > nextOD.timestamp && x.timestamp < currentOD.timestamp
            ).length;
            if (count > 0) xanaxBetweenODs.push(count);
        }
        
        if (xanaxBetweenODs.length === 0) {
            return Math.min(95, Math.round((currentStats.xanaxSinceLastOD / 18) * 100));
        }
        
        const avg = xanaxBetweenODs.reduce((a, b) => a + b, 0) / xanaxBetweenODs.length;
        const current = currentStats.xanaxSinceLastOD;
        
        let probability;
        if (current >= avg) {
            probability = 50 + Math.min(48, ((current - avg) / avg) * 50);
        } else {
            probability = (current / avg) * 50;
        }
        
        // Factor in addiction level
        const addictionFactor = currentStats.addictionLevel / 100;
        probability = probability * (1 + addictionFactor * 0.3);
        
        return Math.min(98, Math.max(1, Math.round(probability)));
    }
    
    function getNextODPrediction() {
        if (overdoseEvents.length < 2) return null;
        
        const xanaxBetweenODs = [];
        for (let i = 0; i < overdoseEvents.length - 1; i++) {
            const currentOD = overdoseEvents[i];
            const nextOD = overdoseEvents[i + 1];
            const count = xanaxEvents.filter(x => 
                x.timestamp > nextOD.timestamp && x.timestamp < currentOD.timestamp
            ).length;
            if (count > 0) xanaxBetweenODs.push(count);
        }
        
        if (xanaxBetweenODs.length === 0) return null;
        
        const avgXanaxToOD = xanaxBetweenODs.reduce((a, b) => a + b, 0) / xanaxBetweenODs.length;
        const remainingXanax = Math.max(0, Math.round(avgXanaxToOD - currentStats.xanaxSinceLastOD));
        
        // Calculate trend (increasing or decreasing risk)
        let trend = "stable";
        if (xanaxBetweenODs.length >= 3) {
            const recent = xanaxBetweenODs.slice(0, 2);
            const older = xanaxBetweenODs.slice(-2);
            const recentAvg = recent.reduce((a, b) => a + b, 0) / recent.length;
            const olderAvg = older.reduce((a, b) => a + b, 0) / older.length;
            if (recentAvg < olderAvg) trend = "decreasing (safer)";
            else if (recentAvg > olderAvg) trend = "increasing (riskier)";
        }
        
        return {
            remainingXanax: remainingXanax,
            avgXanaxToOD: Math.round(avgXanaxToOD),
            confidence: Math.min(95, Math.round((overdoseEvents.length / 15) * 100)),
            trend: trend
        };
    }
    
    function getRiskLevel(probability) {
        if (probability >= 80) return { text: "CRITICAL", color: "#ff0000", emoji: "🔴", action: "⚠️ STOP! Very high risk!" };
        if (probability >= 60) return { text: "HIGH", color: "#ff6600", emoji: "🟠", action: "⚠️ Be extremely careful" };
        if (probability >= 35) return { text: "MEDIUM", color: "#ffcc00", emoji: "🟡", action: "Monitor closely" };
        if (probability >= 15) return { text: "LOW", color: "#88ff88", emoji: "🟢", action: "Normal risk level" };
        return { text: "VERY LOW", color: "#00ff00", emoji: "✅", action: "Safe for now" };
    }

    // ================= SYNC LOGS =================
    async function syncLogs() {
        if (isLoading) {
            showNotification("Already loading...", "info");
            return;
        }
        
        isLoading = true;
        showNotification("📊 Reading Addiction Log...", "info");
        updateUI(true);
        
        const html = await fetchAddictionLog();
        if (!html) {
            showNotification("❌ Could not load Addiction Log. Make sure you're on Torn.", "error");
            isLoading = false;
            updateUI(false);
            return;
        }
        
        const parsed = parseAddictionLog(html);
        
        let newXanaxCount = 0;
        for (let x of parsed.xanax) {
            if (!xanaxEvents.some(e => Math.abs(e.timestamp - x.timestamp) < 60000)) {
                xanaxEvents.push(x);
                newXanaxCount++;
            }
        }
        
        let newODCount = 0;
        for (let od of parsed.overdoses) {
            if (!overdoseEvents.some(e => Math.abs(e.timestamp - od.timestamp) < 60000)) {
                overdoseEvents.push(od);
                newODCount++;
                showNotification(`⚠️ Overdose found: ${od.date}`, "warning");
            }
        }
        
        let newRehabCount = 0;
        for (let r of parsed.rehabs) {
            if (!rehabilitationEvents.some(e => Math.abs(e.timestamp - r.timestamp) < 60000)) {
                rehabilitationEvents.push(r);
                newRehabCount++;
            }
        }
        
        updateStats();
        
        showNotification(`✅ Found ${newXanaxCount} Xanax, ${newODCount} ODs, ${newRehabCount} Rehabs`, "success");
        isLoading = false;
    }

    // ================= UI =================
    function makeDraggable(element) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        const header = element.querySelector('.od-header');
        if (!header) return;
        
        header.style.cursor = 'move';
        
        header.onmousedown = dragMouseDown;
        
        function dragMouseDown(e) {
            if (e.target.tagName === 'BUTTON') return;
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }
        
        function elementDrag(e) {
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            
            let newLeft = element.offsetLeft - pos1;
            let newTop = element.offsetTop - pos2;
            
            newLeft = Math.min(Math.max(0, newLeft), window.innerWidth - element.offsetWidth);
            newTop = Math.min(Math.max(0, newTop), window.innerHeight - element.offsetHeight);
            
            element.style.left = newLeft + 'px';
            element.style.top = newTop + 'px';
            element.style.bottom = 'auto';
            element.style.right = 'auto';
            
            panelPosition = { left: newLeft, top: newTop };
            saveData();
        }
        
        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;
        }
    }
    
    function createUI() {
        if (document.getElementById("odbox")) return;
        
        const panelHTML = `
            <div id="odbox" style="
                position: fixed;
                left: ${panelPosition.left}px;
                top: ${panelPosition.top}px;
                background: linear-gradient(135deg, #1a1a2e 0%, #0d1117 100%);
                border-radius: 12px;
                z-index: 9999;
                width: 360px;
                font-family: 'Segoe UI', Arial, sans-serif;
                border: 1px solid rgba(255,255,255,0.15);
                box-shadow: 0 4px 15px rgba(0,0,0,0.4);
            ">
                <div class="od-header" style="
                    padding: 12px 15px;
                    background: rgba(0,0,0,0.3);
                    border-radius: 12px 12px 0 0;
                    border-bottom: 1px solid rgba(255,255,255,0.1);
                    user-select: none;
                ">
                    <div style="display: flex; justify-content: space-between; align-items: center;">
                        <span style="font-size: 14px; font-weight: bold;">💊 OD Tracker</span>
                        <div style="display: flex; gap: 5px;">
                            <button id="od-refresh-btn" style="
                                background: #44aaff;
                                border: none;
                                color: white;
                                padding: 4px 8px;
                                border-radius: 5px;
                                cursor: pointer;
                                font-size: 11px;
                            ">🔄 Refresh</button>
                            <button id="od-toggle-btn" style="
                                background: #ff8844;
                                border: none;
                                color: white;
                                padding: 4px 8px;
                                border-radius: 5px;
                                cursor: pointer;
                                font-size: 11px;
                            ">👁️ Hide</button>
                            <button id="od-minimize-btn" style="
                                background: none;
                                border: none;
                                color: #aaa;
                                cursor: pointer;
                                font-size: 18px;
                            ">−</button>
                        </div>
                    </div>
                </div>
                <div id="od-tracker-content" style="padding: 15px;">
                    <div style="text-align:center; color:#888;">Click Refresh to load logs</div>
                </div>
            </div>
        `;
        
        document.body.insertAdjacentHTML('beforeend', panelHTML);
        
        const panel = document.getElementById('odbox');
        makeDraggable(panel);
        
        document.getElementById('od-refresh-btn')?.addEventListener('click', () => syncLogs());
        document.getElementById('od-toggle-btn')?.addEventListener('click', () => {
            const content = document.getElementById('od-tracker-content');
            if (content) {
                panelVisible = !panelVisible;
                content.style.display = panelVisible ? 'block' : 'none';
            }
        });
        
        let minimized = false;
        document.getElementById('od-minimize-btn')?.addEventListener('click', () => {
            const content = document.getElementById('od-tracker-content');
            if (content) {
                minimized = !minimized;
                content.style.display = minimized ? 'none' : 'block';
                document.getElementById('od-minimize-btn').textContent = minimized ? '+' : '−';
            }
        });
        
        // Auto-load on first run
        if (xanaxEvents.length === 0 && overdoseEvents.length === 0) {
            setTimeout(() => syncLogs(), 2000);
        } else {
            updateUI();
        }
    }
    
    function updateUI(loading = false) {
        const content = document.getElementById('od-tracker-content');
        if (!content) return;
        
        if (loading) {
            content.innerHTML = `
                <div style="text-align:center; padding: 20px;">
                    <div style="color: #44aaff;">📊 Loading Addiction Log...</div>
                    <div style="font-size: 11px; color: #888; margin-top: 8px;">Parsing Xanax and overdose events</div>
                </div>
            `;
            return;
        }
        
        const probability = calculateODProbability();
        const risk = getRiskLevel(probability);
        const prediction = getNextODPrediction();
        
        // Addiction level color
        let addictionColor = "#88ff88";
        if (currentStats.addictionLevel > 70) addictionColor = "#ff4444";
        else if (currentStats.addictionLevel > 40) addictionColor = "#ffcc00";
        
        content.innerHTML = `
            <div style="text-align:center; margin-bottom: 12px;">
                <div style="font-size: 48px; font-weight: bold; color: ${risk.color};">${currentStats.xanaxSinceLastOD}</div>
                <div style="font-size: 11px; color: #888;">XANAX SINCE LAST OVERDOSE</div>
            </div>
            
            <div style="background: rgba(0,0,0,0.3); border-radius: 8px; padding: 10px; margin-bottom: 12px;">
                <div style="display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 6px;">
                    <span>${risk.emoji} OD Risk Level:</span>
                    <span style="color: ${risk.color}; font-weight: bold;">${risk.text} (${probability}%)</span>
                </div>
                <div style="background: rgba(255,255,255,0.1); height: 6px; border-radius: 3px; overflow: hidden;">
                    <div style="width: ${probability}%; background: ${risk.color}; height: 100%;"></div>
                </div>
                <div style="font-size: 10px; margin-top: 5px; color: #aaa;">${risk.action}</div>
            </div>
            
            <div style="font-size: 11px; margin-bottom: 12px;">
                <div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
                    <span>📅 Last Overdose:</span>
                    <span style="color: #aaa;">${currentStats.lastODDate || 'Never'}</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
                    <span>💊 Total Xanax:</span>
                    <span style="color: #88ff88;">${currentStats.totalXanaxCount}</span>
                </div>
                <div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
                    <span>💀 Total ODs:</span>
                    <span style="color: #ff8888;">${currentStats.totalODCount}</span>
                </div>
                <div style="display: flex; justify-content: space-between;">
                    <span>💚 Addiction Level:</span>
                    <span style="color: ${addictionColor};">${currentStats.addictionLevel}%</span>
                </div>
                
                ${prediction ? `
                <div style="background: rgba(0,0,0,0.4); border-radius: 8px; padding: 8px; margin-top: 8px; border-left: 3px solid #ffcc00;">
                    <div style="font-size: 10px; color: #ffcc00; margin-bottom: 5px;">🎯 PREDICTION</div>
                    <div style="display: flex; justify-content: space-between; font-size: 11px;">
                        <span>Expected in:</span>
                        <span style="color: #88ff88; font-weight: bold;">~${prediction.remainingXanax} more Xanax</span>
                    </div>
                    <div style="display: flex; justify-content: space-between; font-size: 10px; margin-top: 3px;">
                        <span>Your average:</span>
                        <span>${prediction.avgXanaxToOD} Xanax between ODs</span>
                    </div>
                    <div style="display: flex; justify-content: space-between; font-size: 10px;">
                        <span>Trend:</span>
                        <span style="color: #ffcc00;">${prediction.trend}</span>
                    </div>
                    <div style="font-size: 9px; color: #666; margin-top: 4px;">Confidence: ${prediction.confidence}% (${overdoseEvents.length} ODs)</div>
                </div>
                ` : '<div style="color:#666;text-align:center;margin-top:8px;">📈 Need 2+ overdoses for prediction</div>'}
            </div>
            
            <div style="font-size: 10px; color: #666; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 8px; text-align: center;">
                📊 ${overdoseEvents.length} ODs | 💊 ${xanaxEvents.length} Xanax | 💚 ${rehabilitationEvents.length} Rehabs
                <br>🔄 Data from Addiction Log
            </div>
        `;
    }
    
    function showNotification(message, type) {
        const notif = document.createElement('div');
        notif.textContent = message;
        notif.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: ${type === 'error' ? '#ff4444' : type === 'warning' ? '#ff8800' : '#44aaff'};
            color: white;
            padding: 10px 15px;
            border-radius: 8px;
            z-index: 10000;
            font-weight: bold;
            font-family: 'Segoe UI', Arial, sans-serif;
            box-shadow: 0 2px 10px rgba(0,0,0,0.3);
        `;
        document.body.appendChild(notif);
        setTimeout(() => notif.remove(), 4000);
    }

    // ================= INIT =================
    function init() {
        console.log("🚀 OD Tracker v10.0 - HTML Parser");
        console.log("📝 Looking for span.log-text elements");
        loadData();
        createUI();
    }
    
    init();
})();