Candy Cane Addiction

Tracks your personal addiction points automatically with built-in help guide

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         Candy Cane Addiction
// @namespace    https://www.torn.com
// @version      9.0
// @description  Tracks your personal addiction points automatically with built-in help guide
// @author       Fouquet
// @match        https://www.torn.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function () {
    'use strict';

    var roasts = [
        "You just sold your sons tablet for drugs. Go to rehab you terrible parent!!",
        "Even your faction is embarrassed by how high you are right now...",
        "Congrats on trading your last brain cell for Xanax. Rehab time loser.",
        "Your mom called... she said you are a disappointment. Go to Switzerland NOW.",
        "Addiction so high the hospital is already prepping your bed.",
        "Bro you are one Xanax away from selling your own shoes. Go fix it."
    ];

    var DRUG_AP = {
        xanax: 35, ecstasy: 20, cannabis: 3, lsd: 55, pcp: 65,
        speed: 36, vicodin: 28, ketamine: 16, shrooms: 14, opium: 24
    };

    var DRUG_ICON_MAP = {
        xanax: 'xanax', ecstasy: 'ecstasy', cannabis: 'cannabis',
        lsd: 'lsd', pcp: 'pcp', speed: 'speed', vicodin: 'vicodin',
        ketamine: 'ketamine', shrooms: 'shroom', opium: 'opium'
    };

    var apiKey        = GM_getValue('cca_apikey', '');
    var threshold     = parseFloat(GM_getValue('cca_threshold', '12'));
    var currentAP     = parseFloat(GM_getValue('cca_ap', '0'));
    var lastDecayTime = parseInt(GM_getValue('cca_lastdecay', String(Date.now())));
    var calibrated    = GM_getValue('cca_calibrated', 'false') === 'true';
    var toleration    = parseFloat(GM_getValue('cca_toleration', '0'));
    var addMitigation = parseFloat(GM_getValue('cca_mitigation', '0'));
    var snoozedUntil  = 0;
    var lastODCheck   = 0;
    var isFirstRun    = true;
    var lastKnownIcons = {};

    try { lastKnownIcons = JSON.parse(GM_getValue('cca_lastdrugicons', '{}')); } catch(e) {}

    var DAILY_DECAY_AP    = 20;
    var DECAY_INTERVAL_MS = 24 * 60 * 60 * 1000;

    function saveState() {
        GM_setValue('cca_ap',            String(currentAP));
        GM_setValue('cca_lastdecay',     String(lastDecayTime));
        GM_setValue('cca_lastdrugicons', JSON.stringify(lastKnownIcons));
        GM_setValue('cca_calibrated',    calibrated ? 'true' : 'false');
        GM_setValue('cca_toleration',    String(toleration));
        GM_setValue('cca_mitigation',    String(addMitigation));
    }

    function apToPercent(ap) {
        if (ap <= 0) return '0.0';
        if (ap < 143) return (ap / 143).toFixed(1);
        return (12 * Math.log(ap / 143)).toFixed(1);
    }

    function effectiveAPGain(baseAP) {
        return baseAP * (1 - toleration);
    }

    function applyNaturalDecay() {
        var now = Date.now();
        var daysPassed = Math.floor((now - lastDecayTime) / DECAY_INTERVAL_MS);
        if (daysPassed > 0) {
            currentAP = Math.max(0, currentAP - (daysPassed * DAILY_DECAY_AP));
            lastDecayTime = lastDecayTime + (daysPassed * DECAY_INTERVAL_MS);
            saveState();
        }
    }

    function apiCall(selections, section) {
        if (!section) section = 'user';
        if (!apiKey) return Promise.resolve(null);
        return fetch('https://api.torn.com/' + section + '/?selections=' + selections + '&key=' + apiKey)
            .then(function(res) { return res.json(); })
            .then(function(data) { if (data.error) return null; return data; })
            .catch(function() { return null; });
    }

    function calibrate() {
        return apiCall('upgrades', 'faction').then(function(factionData) {
            if (factionData && factionData.upgrades) {
                var ups = Object.values(factionData.upgrades);
                for (var i = 0; i < ups.length; i++) {
                    var u = ups[i];
                    if (u.branch === 'Toleration' && u.name && u.name.toLowerCase().indexOf('addict') !== -1) {
                        toleration = Math.min(0.5, u.level * 0.02);
                        break;
                    }
                }
            }
            return apiCall('merits', 'user');
        }).then(function(meritData) {
            if (meritData && meritData.merits) {
                var keys = Object.keys(meritData.merits);
                for (var i = 0; i < keys.length; i++) {
                    if (keys[i].toLowerCase().indexOf('addiction') !== -1) {
                        addMitigation = Math.min(0.5, meritData.merits[keys[i]] * 0.02);
                        break;
                    }
                }
            }
            calibrated = true;
            saveState();
        });
    }

    function checkOverdoseEvents() {
        if (Date.now() - lastODCheck < 10 * 60 * 1000) return Promise.resolve();
        lastODCheck = Date.now();
        return apiCall('events').then(function(data) {
            if (!data || !data.events) return;
            var lastEventCheck = parseInt(GM_getValue('cca_lasteventcheck', '0'));
            var maxTimestamp = lastEventCheck;
            var evs = Object.values(data.events);
            for (var i = 0; i < evs.length; i++) {
                var ev = evs[i];
                if (ev.timestamp <= lastEventCheck) continue;
                if (maxTimestamp < ev.timestamp) maxTimestamp = ev.timestamp;
                var msg = (ev.event || '').toLowerCase();
                if (msg.indexOf('overdos') !== -1) {
                    var extraAP = DRUG_AP.xanax * 2;
                    var dk = Object.keys(DRUG_ICON_MAP);
                    for (var j = 0; j < dk.length; j++) {
                        if (msg.indexOf(DRUG_ICON_MAP[dk[j]]) !== -1) {
                            extraAP = DRUG_AP[dk[j]] * 2;
                            break;
                        }
                    }
                    currentAP += effectiveAPGain(extraAP);
                }
            }
            GM_setValue('cca_lasteventcheck', String(maxTimestamp));
        });
    }

    function checkDrugIcons() {
        return apiCall('icons,personalstats').then(function(data) {
            if (!data) return;
            var icons = data.icons || {};
            var stats = data.personalstats || {};

            applyNaturalDecay();

            var currentRehabs = stats.rehabs || 0;
            var storedRehabs  = parseInt(GM_getValue('cca_rehabs', '-1'));
            if (storedRehabs === -1) {
                GM_setValue('cca_rehabs', String(currentRehabs));
            } else if (currentRehabs > storedRehabs) {
                var done = currentRehabs - storedRehabs;
                currentAP = Math.max(0, currentAP - (done * 90));
                GM_setValue('cca_rehabs', String(currentRehabs));
                saveState();
            }

            var iconVals = Object.values(icons);
            var drugs = Object.keys(DRUG_ICON_MAP);
            for (var i = 0; i < drugs.length; i++) {
                var drug = drugs[i];
                var sub  = DRUG_ICON_MAP[drug];
                var isActiveNow = iconVals.some(function(v) {
                    return String(v).toLowerCase().indexOf(sub) !== -1;
                });
                var wasActiveBefore = lastKnownIcons[drug] === true;
                if (!isFirstRun && isActiveNow && !wasActiveBefore) {
                    currentAP += effectiveAPGain(DRUG_AP[drug]);
                }
                lastKnownIcons[drug] = isActiveNow;
            }
            isFirstRun = false;

            return checkOverdoseEvents();
        }).then(function() {
            saveState();
            updateIconBadge(parseFloat(apToPercent(currentAP)));
            if (parseFloat(apToPercent(currentAP)) >= threshold && currentAP > 0 && Date.now() >= snoozedUntil) {
                showRoastPopup(parseFloat(apToPercent(currentAP)));
            }
        });
    }

    function updateIconBadge(pct) {
        var icon = document.getElementById('cca-icon');
        if (!icon) return;
        var badge = document.getElementById('cca-badge');
        if (!badge) {
            badge = document.createElement('div');
            badge.id = 'cca-badge';
            badge.style.cssText = 'position:absolute;top:-6px;right:-6px;color:#fff;font-family:Arial;font-weight:bold;font-size:10px;border-radius:999px;padding:1px 4px;min-width:16px;text-align:center;pointer-events:none;border:1px solid #fff;';
            icon.style.position = 'relative';
            icon.appendChild(badge);
        }
        badge.textContent = pct > 0 ? (pct + '%') : 'OK';
        badge.style.background = pct >= threshold ? '#cc0000' : pct > 0 ? '#ff6600' : '#007700';
    }

    function createIcon() {
        if (document.getElementById('cca-icon')) return;
        var icon = document.createElement('div');
        icon.id = 'cca-icon';
        icon.title = 'Candy Cane Addiction - drag to move, tap/click to open';
        icon.style.cssText = 'position:fixed;bottom:80px;right:15px;width:46px;height:46px;background:linear-gradient(45deg,#ff0000 50%,#ffffff 50%);border:3px solid #fff;border-radius:50%;box-shadow:0 0 15px rgba(255,0,0,.8);display:flex;align-items:center;justify-content:center;font-size:24px;cursor:grab;z-index:999999;user-select:none;touch-action:none;';
        icon.innerHTML = '&#127853;';
        document.body.appendChild(icon);
        makeDraggable(icon);
        if (!apiKey) {
            var style = document.createElement('style');
            style.textContent = '@keyframes cca-pulse{0%,100%{box-shadow:0 0 15px rgba(255,0,0,.8)}50%{box-shadow:0 0 35px rgba(255,0,0,1),0 0 60px rgba(255,100,100,.6)}}#cca-icon{animation:cca-pulse 1.3s infinite}';
            document.head.appendChild(style);
        }
    }

    function makeDraggable(el) {
        var d = false, ox = 0, oy = 0, moved = false;

        el.addEventListener('mousedown', function(e) {
            d = true; moved = false;
            ox = e.clientX - el.getBoundingClientRect().left;
            oy = e.clientY - el.getBoundingClientRect().top;
            el.style.cursor = 'grabbing';
            e.preventDefault();
        });
        document.addEventListener('mousemove', function(e) {
            if (!d) return;
            moved = true;
            var nx = e.clientX - ox;
            var ny = e.clientY - oy;
            nx = Math.max(0, Math.min(window.innerWidth  - el.offsetWidth,  nx));
            ny = Math.max(0, Math.min(window.innerHeight - el.offsetHeight, ny));
            el.style.left   = nx + 'px';
            el.style.top    = ny + 'px';
            el.style.right  = 'auto';
            el.style.bottom = 'auto';
        });
        document.addEventListener('mouseup', function() {
            if (d && !moved) showPanel();
            d = false;
            el.style.cursor = 'grab';
        });

        el.addEventListener('touchstart', function(e) {
            d = true; moved = false;
            var t = e.touches[0];
            ox = t.clientX - el.getBoundingClientRect().left;
            oy = t.clientY - el.getBoundingClientRect().top;
        }, { passive: true });
        document.addEventListener('touchmove', function(e) {
            if (!d) return;
            moved = true;
            var t = e.touches[0];
            var nx = t.clientX - ox;
            var ny = t.clientY - oy;
            nx = Math.max(0, Math.min(window.innerWidth  - el.offsetWidth,  nx));
            ny = Math.max(0, Math.min(window.innerHeight - el.offsetHeight, ny));
            el.style.left   = nx + 'px';
            el.style.top    = ny + 'px';
            el.style.right  = 'auto';
            el.style.bottom = 'auto';
            e.preventDefault();
        }, { passive: false });
        document.addEventListener('touchend', function() {
            if (d && !moved) showPanel();
            d = false;
        });
    }

    function showPanel() {
        if (document.getElementById('cca-panel')) return;
        var pct   = parseFloat(apToPercent(currentAP));
        var color = pct >= threshold ? '#cc0000' : pct > 0 ? '#ff6600' : '#007700';

        var panel = document.createElement('div');
        panel.id = 'cca-panel';
        panel.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:linear-gradient(135deg,#ff0000 0%,#ff6666 50%,#ffffff 100%);color:#000;font-family:Arial Black,Arial,sans-serif;padding:28px 25px;border:8px solid #fff;border-radius:20px;box-shadow:0 0 50px rgba(255,0,0,.9);z-index:99999999;width:350px;text-align:center;';

        panel.innerHTML =
            '<div id="cca-x" style="position:absolute;top:10px;right:16px;font-size:30px;cursor:pointer;font-family:Arial;">X</div>' +
            '<h2 style="margin:0 0 10px;font-size:20px;">&#127853; Candy Cane Addiction &#127853;</h2>' +
            '<div style="background:#fff;border-radius:10px;padding:10px;margin:8px 0;font-size:14px;">' +
                '<div style="font-size:22px;font-weight:bold;color:' + color + '">' + pct + '% addiction</div>' +
                '<div style="font-size:12px;color:#555;">' + currentAP.toFixed(1) + ' addiction points</div>' +
                '<div style="font-size:11px;color:#777;margin-top:4px;">Toleration: ' + (toleration*100).toFixed(0) + '% | Mitigation: ' + (addMitigation*100).toFixed(0) + '%</div>' +
            '</div>' +
            '<div style="margin:10px 0;font-size:13px;text-align:left;">' +
                '<strong>Torn API Key:</strong><br>' +
                '<input id="cca-key" type="text" value="' + apiKey + '" placeholder="torn.com Settings API Key" style="width:100%;font-size:12px;padding:5px;border-radius:6px;border:2px solid #c00;box-sizing:border-box;margin-top:3px;">' +
                '<div style="font-size:11px;color:#333;margin-top:2px;">Stored locally only. Never sent anywhere except api.torn.com.</div>' +
            '</div>' +
            '<div style="margin:10px 0;font-size:14px;">Warn me at: <input id="cca-thresh" type="number" min="1" max="100" value="' + threshold + '" style="width:55px;font-size:16px;text-align:center;border-radius:6px;border:2px solid #c00;padding:2px;">%</div>' +
            '<div style="margin:10px 0;font-size:13px;text-align:left;">' +
                '<strong>Current AP override:</strong> ' +
                '<input id="cca-startap" type="number" min="0" value="' + Math.round(currentAP) + '" style="width:70px;font-size:14px;text-align:center;border-radius:6px;border:2px solid #c00;padding:2px;">' +
                '<div style="font-size:11px;color:#333;margin-top:2px;">Only needed on first setup or after a manual correction.</div>' +
            '</div>' +
            '<div style="display:flex;gap:6px;justify-content:center;flex-wrap:wrap;margin-top:12px;">' +
                '<button id="cca-save"        style="padding:8px 12px;background:#000;color:#fff;border:none;border-radius:999px;cursor:pointer;font-size:12px;">Save</button>' +
                '<button id="cca-recalibrate" style="padding:8px 12px;background:#c00;color:#fff;border:none;border-radius:999px;cursor:pointer;font-size:12px;">Re-calibrate</button>' +
                '<button id="cca-test"        style="padding:8px 12px;background:#555;color:#fff;border:none;border-radius:999px;cursor:pointer;font-size:12px;">Test Warning</button>' +
                '<button id="cca-reset"       style="padding:8px 12px;background:#933;color:#fff;border:none;border-radius:999px;cursor:pointer;font-size:12px;">Reset AP</button>' +
                '<button id="cca-help"        style="padding:8px 12px;background:#226;color:#fff;border:none;border-radius:999px;cursor:pointer;font-size:12px;">How It Works</button>' +
            '</div>';

        document.body.appendChild(panel);

        document.getElementById('cca-x').onclick = function() { panel.remove(); };

        document.getElementById('cca-save').onclick = function() {
            apiKey    = document.getElementById('cca-key').value.trim();
            threshold = parseFloat(document.getElementById('cca-thresh').value) || 12;
            var startAP = parseFloat(document.getElementById('cca-startap').value);
            if (!isNaN(startAP)) currentAP = startAP;
            GM_setValue('cca_apikey',    apiKey);
            GM_setValue('cca_threshold', String(threshold));
            saveState();
            panel.remove();
        };

        document.getElementById('cca-recalibrate').onclick = function() {
            var btn = document.getElementById('cca-recalibrate');
            apiKey = document.getElementById('cca-key').value.trim();
            GM_setValue('cca_apikey', apiKey);
            btn.textContent = 'Calibrating...';
            btn.disabled = true;
            calibrate().then(function() {
                btn.textContent = 'Done! Tol:' + (toleration*100).toFixed(0) + '% Mit:' + (addMitigation*100).toFixed(0) + '%';
                btn.disabled = false;
            });
        };

        document.getElementById('cca-test').onclick = function() {
            panel.remove();
            showRoastPopup(parseFloat(apToPercent(currentAP)));
        };

        document.getElementById('cca-reset').onclick = function() {
            if (confirm('Reset addiction points to 0? Use after a full clean rehab if auto-detection seems off.')) {
                currentAP = 0; saveState(); panel.remove(); showPanel();
            }
        };

        document.getElementById('cca-help').onclick = function() {
            panel.remove(); showHelpPanel();
        };
    }

    function showHelpPanel() {
        if (document.getElementById('cca-help-panel')) return;

        var help = document.createElement('div');
        help.id = 'cca-help-panel';
        help.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#1a1a2e;color:#eee;font-family:Arial,sans-serif;padding:0;border:4px solid #ff4444;border-radius:16px;box-shadow:0 0 40px rgba(255,0,0,.6);z-index:99999999;width:520px;max-width:95vw;max-height:85vh;display:flex;flex-direction:column;overflow:hidden;';

        var tabNames = ['Setup', 'How It Works', 'Drug Table', 'Walkthrough', 'Buttons'];
        var tabsHTML = '';
        for (var ti = 0; ti < tabNames.length; ti++) {
            tabsHTML += '<div class="cca-tab" data-tab="' + ti + '" style="padding:10px 6px;cursor:pointer;font-size:11px;border-bottom:3px solid ' + (ti === 0 ? '#ff4444' : 'transparent') + ';color:' + (ti === 0 ? '#fff' : '#aaa') + ';flex:1;text-align:center;">' + tabNames[ti] + '</div>';
        }

        help.innerHTML =
            '<div style="background:linear-gradient(90deg,#cc0000,#ff6666);padding:14px 20px;display:flex;justify-content:space-between;align-items:center;flex-shrink:0;">' +
                '<span style="font-size:16px;font-weight:bold;">How Candy Cane Addiction Works</span>' +
                '<span id="cca-help-x" style="font-size:28px;cursor:pointer;">X</span>' +
            '</div>' +
            '<div id="cca-help-tabs" style="display:flex;background:#111;flex-shrink:0;">' + tabsHTML + '</div>' +
            '<div id="cca-help-content" style="overflow-y:auto;padding:18px 20px;flex:1;line-height:1.6;font-size:13px;"></div>' +
            '<div style="background:#111;padding:10px 20px;flex-shrink:0;text-align:center;">' +
                '<button id="cca-help-back" style="padding:8px 20px;background:#c00;color:#fff;border:none;border-radius:999px;cursor:pointer;font-size:13px;">Back to Panel</button>' +
            '</div>';

        document.body.appendChild(help);

        var tabs = [
            '<h3 style="color:#ff6666;margin-top:0;">First Time Setup</h3>' +
            '<p><b style="color:#ff9">Step 1 - Install Tampermonkey</b><br>Chrome Web Store or Firefox Add-ons, search Tampermonkey, install it.</p>' +
            '<p><b style="color:#ff9">Step 2 - Install Script</b><br>Tampermonkey Dashboard, click +, delete placeholder, paste full script, Ctrl+S. The lollipop icon appears on every Torn page.</p>' +
            '<p><b style="color:#ff9">Step 3 - Get API Key</b><br>Torn Settings, API Key at bottom of page. Create key with Minimal Access. Copy the 16-character key.</p>' +
            '<p><b style="color:#ff9">Step 4 - Enter Key and Calibrate</b><br>Click lollipop, paste API key, click Re-calibrate. Script reads your faction Toleration perk and Addiction Mitigation merits automatically. Wait for confirmation. You only need to do this once unless you change factions or buy new merits.</p>' +
            '<p><b style="color:#ff9">Step 5 - Set Starting AP (first time only)</b><br>Go to your company page, click your name, hover over efficiency stats to find your addiction points. Enter that number in AP override field and click Save. Never need to do this again.</p>' +
            '<p style="background:#2a2a0a;border-left:3px solid #ff9;padding:8px;border-radius:4px;">Minimal Access is enough. Script only needs icons, personal stats, merits, and faction upgrades.</p>',

            '<h3 style="color:#ff6666;margin-top:0;">How the Script Gets Your Numbers</h3>' +
            '<p><b style="color:#ff9">Drug Detection</b><br>Every 5 minutes the script checks your active icons via the Torn API. When a new drug cooldown appears that was not there before, it knows you just took that drug, looks up the exact AP, applies your reductions, and adds it to your running total. Detects all 10 drugs individually.</p>' +
            '<p><b style="color:#ff9">Faction Toleration Perk</b><br>Reduces AP gained per drug by up to 50% (2% per upgrade, max 25 levels). Script reads this from the API on first run automatically. You never enter it manually.</p>' +
            '<p><b style="color:#ff9">Addiction Mitigation Merits</b><br>Reduces effects of addiction on battle stats and work efficiency. Script reads your merits from the API and factors this in correctly.</p>' +
            '<p><b style="color:#ff9">Natural Daily Decay</b><br>Every day at approximately 3am Torn City Time addiction drops by 20 AP. Script subtracts this automatically for every day passed, even if browser was closed.</p>' +
            '<p><b style="color:#ff9">Overdose Detection</b><br>Overdoses add 2-5x normal AP. Script checks your events every 10 minutes and adds the extra penalty automatically.</p>' +
            '<p><b style="color:#ff9">Rehab Detection</b><br>Watches your rehabs done count in personal stats. When it goes up, script subtracts 90 AP per session automatically. Forever. No action needed.</p>' +
            '<p><b style="color:#ff9">Why Other Scripts Failed - The Brain Icon</b><br>The brain icon only appears above roughly 143 AP. Below that it does not exist anywhere in the API or on screen. Every other script tried to read something Torn does not show until it is already too late. This script tracks from your very first drug, before the brain icon ever appears.</p>',

            '<h3 style="color:#ff6666;margin-top:0;">Drug Addiction Points Reference</h3>' +
            '<p style="color:#aaa;font-size:12px;">Community-verified values. Actual AP per dose = Base AP x (1 minus Faction Toleration %). Overdosing adds 2-5x on top.</p>' +
            '<table style="width:100%;border-collapse:collapse;font-size:13px;">' +
            '<tr style="background:#cc0000;color:#fff;"><th style="padding:8px;text-align:left;">Drug</th><th style="padding:8px;text-align:center;">Base AP</th><th style="padding:8px;text-align:center;">Approx %</th><th style="padding:8px;">Notes</th></tr>' +
            '<tr style="background:#2a1a1a;"><td style="padding:7px;">PCP</td><td style="text-align:center;">65</td><td style="text-align:center;">~5.4%</td><td>Highest</td></tr>' +
            '<tr style="background:#1a1a1a;"><td style="padding:7px;">LSD</td><td style="text-align:center;">55</td><td style="text-align:center;">~4.6%</td><td>Very high</td></tr>' +
            '<tr style="background:#2a1a1a;"><td style="padding:7px;">Speed</td><td style="text-align:center;">36</td><td style="text-align:center;">~3.0%</td><td></td></tr>' +
            '<tr style="background:#1a1a1a;"><td style="padding:7px;">Xanax</td><td style="text-align:center;">35</td><td style="text-align:center;">~2.9%</td><td>Most common</td></tr>' +
            '<tr style="background:#2a1a1a;"><td style="padding:7px;">Vicodin</td><td style="text-align:center;">28</td><td style="text-align:center;">~2.3%</td><td></td></tr>' +
            '<tr style="background:#1a1a1a;"><td style="padding:7px;">Opium</td><td style="text-align:center;">24</td><td style="text-align:center;">~2.0%</td><td></td></tr>' +
            '<tr style="background:#2a1a1a;"><td style="padding:7px;">Ecstasy</td><td style="text-align:center;">20</td><td style="text-align:center;">~1.7%</td><td></td></tr>' +
            '<tr style="background:#1a1a1a;"><td style="padding:7px;">Ketamine</td><td style="text-align:center;">16</td><td style="text-align:center;">~1.3%</td><td></td></tr>' +
            '<tr style="background:#2a1a1a;"><td style="padding:7px;">Shrooms</td><td style="text-align:center;">14</td><td style="text-align:center;">~1.2%</td><td></td></tr>' +
            '<tr style="background:#1a1a1a;"><td style="padding:7px;">Cannabis</td><td style="text-align:center;">3</td><td style="text-align:center;">~0.2%</td><td>Very low</td></tr>' +
            '</table><br>' +
            '<p><b style="color:#ff9">Example with Toleration XXV (50% reduction):</b><br>Xanax: 35 x 0.50 = 17.5 AP per dose. Applied automatically.</p>' +
            '<p><b style="color:#ff9">AP to % Formula (community verified):</b><br>Below 143 AP = under 1% debuff.<br>143+ AP: % = 12 x ln(AP / 143)<br>200 AP = 3.2% | 350 AP = 7.8% | 500 AP = 11.1%</p>',

            '<h3 style="color:#ff6666;margin-top:0;">Full Walkthrough - Fresh Rehab to Warning</h3>' +
            '<p><b style="color:#ff9">1. Back from Switzerland after a full rehab.</b><br>Addiction is 0 AP. Script detected the rehab automatically. Badge shows green OK.</p>' +
            '<p><b style="color:#ff9">2. You take a Xanax.</b><br>Within 5 minutes the script sees a new Xanax cooldown appear. Calculates 35 AP x (1 minus your Toleration %). At 30% Toleration you gain 24.5 AP. Badge updates.</p>' +
            '<p><b style="color:#ff9">3. You take drugs over the following days.</b><br>Each drug detected and AP added. Every day overnight 20 AP subtracted for decay. Badge keeps updating silently.</p>' +
            '<p><b style="color:#ff9">4. You overdose.</b><br>Script catches the event within 10 minutes and adds 2x extra AP automatically.</p>' +
            '<p><b style="color:#ff9">5. Addiction hits your warning threshold.</b><br>Roast popup appears. Snooze 30 minutes if needed. Reminds you every 5 minutes.</p>' +
            '<p><b style="color:#ff9">6. You fly to Switzerland and rehab.</b><br>Script detects rehab count increase, subtracts 90 AP per session automatically. Badge drops. No action needed.</p>' +
            '<p><b style="color:#ff9">7. Back to step 1. Runs forever automatically.</b></p>' +
            '<p style="background:#2a2a0a;border-left:3px solid #ff9;padding:8px;border-radius:4px;font-size:12px;">Note: Script cannot detect drugs taken while browser was fully closed. If you took drugs during a gap, correct your AP in the override field.</p>',

            '<h3 style="color:#ff6666;margin-top:0;">Panel Buttons Explained</h3>' +
            '<p><b style="color:#ff9">Save</b><br>Saves your API key, warning threshold, and any manual AP correction. Always click after making changes.</p>' +
            '<p><b style="color:#ff9">Re-calibrate</b><br>Re-reads your faction Toleration perk and Addiction Mitigation merits from the API. Only needed once on first setup, or if you change factions or buy new merits. You do NOT need to click this every time.</p>' +
            '<p><b style="color:#ff9">Test Warning</b><br>Shows a test roast popup to confirm the warning is working.</p>' +
            '<p><b style="color:#ff9">Reset AP</b><br>Sets addiction points to 0. Use after a full clean rehab only if auto-detection seems off. Confirmation prompt appears first.</p>' +
            '<p><b style="color:#ff9">How It Works</b><br>Opens this help panel.</p>' +
            '<p><b style="color:#ff9">The Live Badge</b><br>Green OK = clean. Orange = building up. Red = at or over threshold, time for Switzerland.</p>' +
            '<p><b style="color:#ff9">Moving the icon</b><br>Click and drag on desktop. Press and drag on mobile or PDA. A short tap opens the panel. A drag moves it anywhere on screen.</p>'
        ];

        var contentEl = document.getElementById('cca-help-content');
        var tabEls = help.querySelectorAll('.cca-tab');

        function showTab(i) {
            contentEl.innerHTML = tabs[i];
            for (var j = 0; j < tabEls.length; j++) {
                tabEls[j].style.borderBottom = j === i ? '3px solid #ff4444' : '3px solid transparent';
                tabEls[j].style.color        = j === i ? '#fff' : '#aaa';
            }
        }

        showTab(0);

        (function() {
            for (var i = 0; i < tabEls.length; i++) {
                (function(idx) {
                    tabEls[idx].addEventListener('click', function() { showTab(idx); });
                })(i);
            }
        })();

        document.getElementById('cca-help-x').onclick    = function() { help.remove(); };
        document.getElementById('cca-help-back').onclick = function() { help.remove(); showPanel(); };
    }

    function showRoastPopup(pct) {
        if (document.getElementById('cca-popup')) return;
        var popup = document.createElement('div');
        popup.id = 'cca-popup';
        popup.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:linear-gradient(135deg,#ff0000,#ffffff);color:#000;font-family:Impact,Arial Black,sans-serif;font-size:26px;padding:45px 55px 35px;border:12px solid #fff;border-radius:25px;box-shadow:0 0 60px rgba(255,0,0,1);text-align:center;z-index:99999999;max-width:88%;line-height:1.35;';
        popup.innerHTML =
            '<div style="font-size:18px;font-family:Arial;margin-bottom:16px;background:rgba(0,0,0,.12);border-radius:8px;padding:6px;">' +
                'Addiction: <strong>' + pct + '%</strong> / Threshold: ' + threshold + '%' +
            '</div>' +
            roasts[Math.floor(Math.random() * roasts.length)];

        var snooze = document.createElement('button');
        snooze.textContent = 'Snooze 30 min';
        snooze.style.cssText = 'display:block;margin:20px auto 0;padding:10px 24px;background:#000;color:#fff;border:none;border-radius:999px;cursor:pointer;font-size:15px;font-family:Arial;';
        snooze.onclick = function() { snoozedUntil = Date.now() + 30 * 60 * 1000; popup.remove(); };

        var closeX = document.createElement('div');
        closeX.textContent = 'X';
        closeX.style.cssText = 'position:absolute;top:10px;right:20px;font-size:38px;cursor:pointer;line-height:1;font-family:Arial;';
        closeX.onclick = function() { popup.remove(); };

        popup.appendChild(snooze);
        popup.appendChild(closeX);
        document.body.appendChild(popup);
        setTimeout(function() { if (popup.parentNode) popup.remove(); }, 9000);
    }

    function init() {
        createIcon();
        if (!apiKey) return;
        applyNaturalDecay();
        var startScript = function() {
            setTimeout(function() {
                checkDrugIcons();
                setInterval(checkDrugIcons, 5 * 60 * 1000);
            }, 3000);
        };
        if (!calibrated) {
            calibrate().then(startScript);
        } else {
            startScript();
        }
    }

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

})();