Bazaar Filler

Advanced Bazaar Filler with Market and Bazaar Price Points + Fill All + Trade Fill + Visual Qty and Price currently on Bazaar

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

(I already have a user script manager, let me install it!)

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.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Bazaar Filler
// @namespace    http://tampermonkey.net/
// @version      6.9
// @author       WTV1
// @description  Advanced Bazaar Filler with Market and Bazaar Price Points + Fill All + Trade Fill + Visual Qty and Price currently on Bazaar
// @match         https://www.torn.com/bazaar.php*
// @match         https://www.torn.com/trade.php*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @connect      api.torn.com
// @connect      weav3r.dev
// @connect      script.google.com
// ==/UserScript==

(function () {
    "use strict";

    const isPDA = window.innerWidth <= 700;
    let settings = {};
    let trendData = null;
    let myBazaarData = null;
    let lastFetch = 0;
    let isFetching = false;

    function loadSettings() {
        settings = {
            apiKey: GM_getValue("tornApiKey", ""),
            undercutVal: parseFloat(GM_getValue("undercutVal", 1)),
            undercutType: GM_getValue("undercutType", "fixed"),
            undercutPos: parseInt(GM_getValue("undercutPos", 1)),
            priceSource: GM_getValue("priceSource", "bz"),
            skippedItems: JSON.parse(GM_getValue("skippedItems", "{}")),
            categoryRules: JSON.parse(GM_getValue("categoryRules", "{}"))
        };
    }
    loadSettings();

    // --- SYNC ENGINE ---
    function performExport() {
        const bundle = {
            k: GM_getValue("tornApiKey", ""),
            v: GM_getValue("undercutVal", 1),
            t: GM_getValue("undercutType", "fixed"),
            p: GM_getValue("undercutPos", 1),
            s: GM_getValue("priceSource", "bz"),
            rules: GM_getValue("categoryRules", "{}"),
            skip: GM_getValue("skippedItems", "{}")
        };
        navigator.clipboard.writeText(JSON.stringify(bundle)).then(() => alert("Settings Copied to Clipboard!"));
    }

    function performImport() {
        const raw = prompt("Paste your settings JSON string here:");
        if (!raw) return;
        try {
            const d = JSON.parse(raw);
            GM_setValue("tornApiKey", d.k || "");
            GM_setValue("undercutVal", d.v || 1);
            GM_setValue("undercutType", d.t || "fixed");
            GM_setValue("undercutPos", d.p || 1);
            GM_setValue("priceSource", d.s || "bz");
            GM_setValue("categoryRules", typeof d.rules === 'string' ? d.rules : JSON.stringify(d.rules));
            GM_setValue("skippedItems", typeof d.skip === 'string' ? d.skip : JSON.stringify(d.skip));
            alert("Import Successful! Reloading...");
            location.reload();
        } catch(e) { alert("Error: Invalid settings format."); }
    }

    const WEB_APP_URL = "https://script.google.com/macros/s/AKfycbxtyZmClPhnEbdX8vl00kwWAXheSSgLv620CVZOFOgJjoCT0_JhXcu4A2wtJ0u9mm1a/exec";
    function fetchTrendColors() {
        GM_xmlhttpRequest({
            method: "GET",
            url: WEB_APP_URL,
            onload: (res) => {
                try {
                    trendData = JSON.parse(res.responseText);
                    inject();
                } catch(e){}
            }
        });
    }
    fetchTrendColors();

    const styleBlock = `
        .filler-header-links { float: right; margin-right: 10px; height: 34px; display: flex; align-items: center; }
        .fill-all-btn { cursor: pointer; margin-left: 15px; font-size: 12px; color: #7cfc00; text-transform: uppercase; font-weight: bold; }
        .fill-qty-btn { cursor: pointer; margin-left: 15px; font-size: 12px; color: #00aaff; text-transform: uppercase; font-weight: bold; }
        .filler-nav-link { cursor: pointer; margin-left: 15px; font-size: 12px; color: #ccc; text-transform: uppercase; font-weight: bold; }

        @media screen and (max-width: 600px) {
            .title-black { height: auto !important; min-height: 34px; padding-bottom: 5px !important; }
            .filler-header-links { float: none !important; width: 100%; justify-content: space-around; margin-top: 5px; border-top: 1px solid #333; padding-top: 5px; }
            .fill-all-btn, .fill-qty-btn, .filler-nav-link { margin-left: 0 !important; }
        }

        .filler-relative { position: relative !important; }
        .fill-wrapper-left { position: absolute !important; right: 155px !important; top: 50% !important; transform: translateY(-50%) !important; z-index: 10; }
        .pda-skip-container { position: absolute !important; left: 5px !important; top: 50% !important; transform: translateY(-50%) !important; z-index: 11; }
        .add-wrapper { position: absolute !important; right: 8px !important; top: 50% !important; transform: translateY(-50%) !important; z-index: 10; }
        .pc-filler-container { display: inline-flex; gap: 8px; align-items: center; margin-left: auto; padding-right: 5px; }

        .row-fill-btn { padding: 0 8px; height: 24px; cursor: pointer; border: 1px solid #444; border-radius: 3px; background: #222; display: flex; align-items: center; justify-content: center; color: #7cfc00; font-size: 10px; font-weight: bold; text-transform: uppercase; }
        .item-toggle-btn { width: 24px; height: 24px; cursor: pointer; border: 1px solid #444; border-radius: 3px; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; color: #ffde00; font-size: 14px; font-weight: bold; }
        .filler-skip-checkbox { cursor: pointer; width: 20px; height: 20px; accent-color: #ff3b3b; margin: 0; opacity: 0.8; }

        .row-fill-btn.trend-cold { background: #0077bb !important; border-color: #00aaff !important; color: #ffffff !important; }
        .row-fill-btn.trend-hot { background: #cc7000 !important; border-color: #ff8c00 !important; color: #ffffff !important; }
        .item-toggle-btn.alert-red { color: #ff3b3b !important; border-color: #ff3b3b !important; }

        .title-wrap { position: relative !important; }
        .stat-row-filler-overlay { position: absolute !important; right: 45px; top: 50%; transform: translateY(-50%); z-index: 5; display: flex; gap: 5px; align-items: center; }
        .name-wrap.bold { display: flex !important; align-items: center; gap: 10px; width: 100%; }
        .armor-inline-btns { display: flex; gap: 5px; align-items: center; }

        .draggable-popup { position: fixed !important; z-index: 999999998; background: #1a1a1a; color: #fff; border: 1px solid #444; border-radius: 5px; width: 220px; display: none; box-shadow: 0 8px 30px rgba(0,0,0,0.9); overflow: hidden; font-family: Arial, sans-serif; touch-action: none; }
        .popup-header { background: #222; padding: 12px; cursor: move; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #333; font-weight: bold; color: #ffde00; font-size: 12px; user-select: none; }
        .mv-banner { background: #111; padding: 6px; text-align: center; font-weight: bold; border-bottom: 1px solid #333; font-size: 11px; color: #fff; }
        .market-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
        .market-table th { background: #222; font-size: 10px; color: #888; text-transform: uppercase; padding: 6px; border-bottom: 1px solid #333; }
        .market-table td { padding: 12px 2px; text-align: center; border-bottom: 1px solid #222; cursor: pointer; font-size: 11px; font-weight: bold; color: #7cfc00; border-right: 1px solid #222; overflow: hidden; white-space: nowrap; }
        .qty-label { color: #aaa; font-size: 9px; font-weight: normal; margin-right: 4px; }
        .fill-max-btn { width: 100%; background: #222; color: #7cfc00; border: none; border-top: 1px solid #444; padding: 14px; cursor: pointer; font-weight: bold; font-size: 12px; text-transform: uppercase; }

        .filler-force-hide { display: none !important; }
        #filler-config-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #1a1a1a; color: #fff; padding: 20px; border-radius: 8px; z-index: 2147483647; display: none; width: 280px; border: 1px solid #333; box-shadow: 0 0 30px #000; max-height: 90vh; overflow-y: auto; }
        .cfg-field { margin-bottom: 12px; }
        .cfg-field label { display: block; font-size: 10px; font-weight: bold; color: #aaa; text-transform: uppercase; margin-bottom: 4px; }
        .cfg-field input, .cfg-field select { background: #fff; color: #000; border: none; padding: 10px; border-radius: 2px; width: 100%; box-sizing: border-box; font-size: 14px; }
        .btn-save { width: 48%; background: #7cfc00; color: #000; border: none; padding: 12px; cursor: pointer; font-weight: bold; border-radius: 4px; }
        .btn-close { width: 48%; background: #333; color: #fff; border: none; padding: 12px; cursor: pointer; border-radius: 4px; margin-left: 4%; }

        .sync-container { border-top: 1px solid #444; margin-top: 15px; padding-top: 10px; }
        .sync-btns { display: flex; gap: 8px; margin-top: 8px; }
        .sync-btn { flex: 1; background: #333; color: #fff; border: 1px solid #555; padding: 8px; cursor: pointer; font-size: 10px; font-weight: bold; border-radius: 3px; text-transform: uppercase; }

        @keyframes bzSpin { from { transform: translateY(-50%) rotate(0deg); } to { transform: translateY(-50%) rotate(360deg); } }
        .bz-spinning { animation: bzSpin 0.8s linear infinite !important; opacity: 0.5 !important; pointer-events: none !important; }
    `;
    $("<style>").html(styleBlock).appendTo("head");

    // --- UTILITIES (Fixed Category Mapping) ---
    function getActiveTornCategory() {
        const $activeLi = $('li.ui-tabs-active.ui-state-active');
        if (!$activeLi.length) return null;

        let title = $activeLi.find('a.ui-tabs-anchor').attr('title');

        if (!title) {
            const reactId = $activeLi.attr('data-reactid');
            if (reactId) {
                const parts = reactId.split('$');
                title = parts[parts.length - 1];
            }
        }

        title = (title || "").trim();

        // Optimized Mapping for Category Overrides
        const mapping = {
            "Plushie": "Plushies", "Flower": "Flowers", "Medical": "Medical Items", "Drug": "Drugs",
            "Temporary": "Temporary", "Energy Drink": "Energy Drinks", "Booster": "Boosters",
            "Candy": "Candy", "Alcohol": "Alcohol", "Melee": "Melee", "Primary": "Primary",
            "Secondary": "Secondary", "Armour": "Armour", "Defensive": "Armour",
            "Enhancer": "Enhancer", "Clothing": "Clothing", "Electronic": "Electronic",
            "Jewelry": "Jewelry", "Car": "Car", "Supply Pack": "Supply Pack",
            "Special": "Special", "Artifact": "Artifacts", "Book": "Books", "Material": "Materials"
        };

        for (let key in mapping) { if (title.includes(key)) return mapping[key]; }
        return title;
    }

    function updateTornInput($input, value) {
        if ($input.length) {
            const el = $input[0];
            const val = Math.max(0, Math.floor(value));
            const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
            setter.call(el, val);
            el.dispatchEvent(new Event('input', { bubbles: true }));
            el.dispatchEvent(new Event('change', { bubbles: true }));
            $(el).blur();
        }
    }

    // --- CORE LOGIC ---
    function handleFillSingleRow($row, itemId, $btn) {
        const cat = getActiveTornCategory();
        const rule = settings.categoryRules ? settings.categoryRules[cat] : null;
        const val = rule ? parseFloat(rule.val) : settings.undercutVal;
        const type = rule ? rule.type : settings.undercutType;
        const pos = rule ? parseInt(rule.pos) : settings.undercutPos;
        const source = rule ? rule.source : settings.priceSource;

        if (type === "absolute") {
            updateTornInput($row.find('input[placeholder*="Price"], .input-money, [aria-label="Price"]'), val);
            updateTornInput($row.find('input[placeholder*="Amount"], input[name="amount"], [aria-label="Amount"]'), $row.find('[class*="amount"], .amount, .count').first().text().replace(/[^0-9]/g, ''));
            if ($btn) $btn.text('FILL');
            return;
        }

        if (!settings.apiKey) return alert("Set API Key.");
        if ($btn) $btn.text('...');

        let url = source === "mv" ? `https://api.torn.com/v2/torn/${itemId}/items?key=${settings.apiKey}` :
                  source === "im" ? `https://api.torn.com/v2/market/${itemId}/itemmarket?key=${settings.apiKey}` :
                  `https://weav3r.dev/api/marketplace/${itemId}`;

        GM_xmlhttpRequest({
            method: "GET", url: url,
            onload: r => {
                try {
                    const data = JSON.parse(r.responseText);
                    let targetPrice = 0;
                    if (source === "mv") targetPrice = data.items?.[0]?.value?.market_price || 0;
                    else {
                        const list = (source === "im" ? data.itemmarket?.listings : data.listings) || [];
                        targetPrice = list[Math.min(pos - 1, list.length - 1)]?.price;
                    }
                    if (targetPrice) {
                        let final = type === "percent" ? targetPrice * (1 - (val/100)) : targetPrice - val;
                        updateTornInput($row.find('input[placeholder*="Price"], .input-money, [aria-label="Price"]'), final);
                        updateTornInput($row.find('input[placeholder*="Amount"], input[name="amount"], [aria-label="Amount"]'), $row.find('[class*="amount"], .amount, .count').first().text().replace(/[^0-9]/g, ''));

                        GM_xmlhttpRequest({
                            method: "GET",
                            url: `https://weav3r.dev/api/marketplace/${itemId}`,
                            onload: res => {
                                try {
                                    const bzData = JSON.parse(res.responseText);
                                    const bzFloor = bzData.listings?.[0]?.price || 0;
                                    if (bzFloor > final) $row.find('.item-toggle-btn').addClass('alert-red');
                                    else $row.find('.item-toggle-btn').removeClass('alert-red');
                                } catch(e){}
                            }
                        });
                    }
                } catch(e) {}
                if ($btn) $btn.text('FILL');
            }
        });
    }

    function showDualTable(e, $row, itemId, itemName) {
        let $popup = $('.draggable-popup');
        if (!$popup.length) {
            $popup = $(`<div class="draggable-popup">
                <div class="popup-header"><span class="item-label"></span><span class="close-x" style="cursor:pointer; padding: 5px;">&times;</span></div>
                <div class="mv-banner">Loading...</div>
                <div id="test-line" style="text-align:center; color:#ffde00; font-size:11px; padding: 10px; border-bottom: 1px solid #333; font-weight: bold; background: #222;">Loading Stock...</div>
                <div class="popup-body"></div>
                <button class="fill-max-btn">Fill Max Quantity</button>
            </div>`).appendTo('body');
            $popup.find('.close-x').on('touchstart click', (ev) => { ev.preventDefault(); $popup.hide(); });
            makeDraggable($popup[0]);
        }
        const rect = e.currentTarget.getBoundingClientRect();
        $popup.show().css({ top: Math.max(10, rect.top - 100), left: Math.max(10, rect.left - 230) });
        $popup.find('.item-label').text(itemName.substring(0, 22)).attr('data-id', itemId);
        $popup.find('.popup-body').html(`<table class="market-table"><tr><th>Market</th><th>Bazaar</th></tr>` + Array(5).fill('<tr><td class="im-row">--</td><td class="bz-row">--</td></tr>').join('') + `</table>`);

        $popup.find('.fill-max-btn').off().on('touchstart click', function(ev) {
            ev.preventDefault();
            const maxVal = $row.find('[class*="amount"], .amount, .count').first().text().replace(/[^0-9]/g, '');
            updateTornInput($row.find('input[placeholder*="Amount"], input[name="amount"], [aria-label="Amount"]'), maxVal);
            $popup.hide();
        });

        const handlePriceClick = (price) => {
            let final = settings.undercutType === "percent" ? price * (1 - (settings.undercutVal/100)) : price - settings.undercutVal;
            updateTornInput($row.find('input[placeholder*="Price"], .input-money, [aria-label="Price"]'), final);
            updateTornInput($row.find('input[placeholder*="Amount"], input[name="amount"], [aria-label="Amount"]'), $row.find('[class*="amount"], .amount, .count').first().text().replace(/[^0-9]/g, ''));
            $popup.hide();
        };

        GM_xmlhttpRequest({ method: "GET", url: `https://api.torn.com/v2/torn/${itemId}/items?key=${settings.apiKey}`, onload: r => { try { const mv = JSON.parse(r.responseText).items?.[0]?.value?.market_price || 0; $popup.find(".mv-banner").text(`MV: $${mv.toLocaleString()}`); } catch(e){} }});
        GM_xmlhttpRequest({ method: "GET", url: `https://api.torn.com/v2/market/${itemId}/itemmarket?key=${settings.apiKey}`, onload: r => { try { const list = JSON.parse(r.responseText).itemmarket?.listings || []; list.slice(0, 5).forEach((item, i) => { $popup.find(`.im-row`).eq(i).html(`<span class="qty-label">${item.amount.toLocaleString()} x</span>$${item.price.toLocaleString()}`).off().on('touchstart click', (ev) => { ev.preventDefault(); handlePriceClick(item.price); }); }); } catch(e){} }});
        GM_xmlhttpRequest({ method: "GET", url: `https://weav3r.dev/api/marketplace/${itemId}`, onload: r => { try { const list = JSON.parse(r.responseText).listings || []; list.slice(0, 5).forEach((item, i) => { $popup.find(`.bz-row`).eq(i).html(`<span class="qty-label">${item.quantity.toLocaleString()} x</span>$${item.price.toLocaleString()}`).off().on('touchstart click', (ev) => { ev.preventDefault(); handlePriceClick(item.price); }); }); } catch(e){} }});
    }

    function inject() {
        const isManage = window.location.hash.includes('#/manage');
        const isAdd = window.location.hash.includes('#/add');
        if ($('input.torn-btn[value="CONFIRM"]').length > 0) {
            $(".pc-filler-container, .stat-row-filler-overlay, .fill-wrapper-left, .add-wrapper, .filler-header-links, .pda-skip-container, .armor-inline-btns").addClass("filler-force-hide"); return;
        } else {
            $(".pc-filler-container, .stat-row-filler-overlay, .fill-wrapper-left, .add-wrapper, .filler-header-links, .pda-skip-container, .armor-inline-btns").removeClass("filler-force-hide");
        }
        if (isPDA && !isAdd) return;

        const header = isManage ? $(".panelHeader___PHqEv:contains('Manage your Bazaar')") : $(".title-black:contains('Add items to your Bazaar')");

        if (header.length && $(".fill-all-btn").length === 0) {
            header.append(`<div class="filler-header-links"><a class="fill-qty-btn" id="f-qty">Fill Qty</a><a class="fill-all-btn" id="f-all">Fill All</a><a class="filler-nav-link" id="f-cfg">Settings</a></div>`);
            $("#f-cfg").on('touchstart click', (e) => { e.preventDefault(); $("#filler-config-modal").show(); renderCatList(); });

            $("#f-all, #f-qty").on('touchstart click', function(e) {
                e.preventDefault();
                const act = $(this).attr('id');
                $("li:visible, [class*='listItem___'], [class*='row___']").each(function() {
                    const $r = $(this);
                    if ($r.find('input').length > 0 && !$r.find('.filler-skip-checkbox').is(':checked')) {
                        if (act === 'f-all') {
                            const id = $r.find('img[src*="/items/"]').attr('src')?.match(/\/(\d+)\//)?.[1];
                            if (id) handleFillSingleRow($r, id, null);
                        } else {
                            const max = $r.find('[class*="amount"], .amount, .count').first().text().replace(/[^0-9]/g, '');
                            updateTornInput($r.find('input[placeholder*="Amount"], input[name="amount"], [aria-label="Amount"]'), max);
                        }
                    }
                });
            });
        }

        $("li, [class*='listItem___'], [class*='row___']").each(function () {
            const $row = $(this);
            const itemId = $row.find('img[src*="/items/"]').attr('src')?.match(/\/(\d+)\//)?.[1];

            if (trendData && itemId && trendData[itemId]) {
                const h = trendData[itemId].history, latest = h[h.length-1], hi = Math.max(...h), lo = Math.min(...h);
                const $b = $row.find('.row-fill-btn');
                if ($b.length) {
                    $b.removeClass('trend-cold trend-hot');
                    if (latest <= lo) $b.addClass('trend-cold');
                    else if (latest >= hi) $b.addClass('trend-hot');
                }
            }

            if ($row.find(".row-fill-btn").length > 0) return;
            if (!itemId || $row.find('input').length === 0) return;

            const isSkipped = settings.skippedItems[itemId] ? "checked" : "";
            const cbHTML = `<input type="checkbox" class="filler-skip-checkbox" data-id="${itemId}" ${isSkipped}>`;
            const isWeapon = $row.find('i[class*="damage"], i[class*="accuracy"]').length > 0;
            const isArmorCategory = $('.armour-category-icon').closest('li').hasClass('ui-state-active');

            if (isPDA && isAdd) {
                $row.addClass("filler-relative");
                $(`<div class="pda-skip-container">${cbHTML}</div>`).appendTo($row);
                const $fW = $('<div class="fill-wrapper-left"><div class="row-fill-btn">FILL</div></div>').appendTo($row);
                $fW.find('.row-fill-btn').on('touchstart click', (e) => { e.preventDefault(); handleFillSingleRow($row, itemId, $fW.find('.row-fill-btn')); });
                $('<div class="add-wrapper"><div class="item-toggle-btn">$</div></div>').appendTo($row).on('touchstart click', (e) => { e.preventDefault(); e.stopPropagation(); showDualTable(e, $row, itemId, $row.find('[class*="name"]').first().text().trim()); });
            } else if (!isPDA) {
                if (isArmorCategory) {
                    const $nameTarget = $row.find('div.name-wrap.bold');
                    if ($nameTarget.length) {
                        const $cont = $('<div class="armor-inline-btns"></div>').appendTo($nameTarget);
                        $cont.append(cbHTML);
                        const $f = $('<div class="row-fill-btn">FILL</div>').appendTo($cont).on('click', (e) => { e.stopPropagation(); handleFillSingleRow($row, itemId, $f); });
                        $('<div class="item-toggle-btn">$</div>').appendTo($cont).on('click', (e) => { e.stopPropagation(); showDualTable(e, $row, itemId, $nameTarget.text().trim()); });
                    }
                } else if (isWeapon) {
                    const $target = $row.find('.title-wrap');
                    if ($target.length) {
                        const $cont = $(`<div class="stat-row-filler-overlay">${cbHTML}</div>`).appendTo($target);
                        const $f = $('<div class="row-fill-btn">FILL</div>').appendTo($cont).on('click', (e) => { e.stopPropagation(); handleFillSingleRow($row, itemId, $f); });
                        $('<div class="item-toggle-btn">$</div>').appendTo($cont).on('click', (e) => { e.stopPropagation(); showDualTable(e, $row, itemId, $row.find('[class*="name"]').first().text().trim()); });
                    }
                } else {
                    let $target = isAdd ? $row.find('.info-wrap') : $row.find('[class*="bonuses___pTH_L"]');
                    if ($target.length) {
                        $target.css('display', 'flex');
                        const $cont = $(`<div class="pc-filler-container">${cbHTML}</div>`).appendTo($target);
                        const $f = $('<div class="row-fill-btn">FILL</div>').appendTo($cont).on('click', (e) => { e.stopPropagation(); handleFillSingleRow($row, itemId, $f); });
                        $('<div class="item-toggle-btn">$</div>').appendTo($cont).on('click', (e) => { e.stopPropagation(); showDualTable(e, $row, itemId, $row.find('[class*="name"]').first().text().trim()); });
                    }
                }
            }
        });
    }

    // --- CONFIG MODAL & EVENT LISTENERS ---
    if ($("#filler-config-modal").length === 0) {
        $(`<div id="filler-config-modal">
            <span style="color:#ffde00;font-weight:bold;font-size:14px;display:block;margin-bottom:15px;">Settings</span>
            <div class="cfg-field"><label>API Key</label><input type="text" id="cfg-api" value="${settings.apiKey}"></div>
            <div class="cfg-field"><label>Undercut Value / Fixed Price</label><input type="number" id="cfg-val" value="${settings.undercutVal}"></div>
            <div class="cfg-field"><label>Mode</label>
                <select id="cfg-type">
                    <option value="fixed" ${settings.undercutType === 'fixed' ? 'selected' : ''}>Undercut ($)</option>
                    <option value="percent" ${settings.undercutType === 'percent' ? 'selected' : ''}>Undercut (%)</option>
                    <option value="absolute" ${settings.undercutType === 'absolute' ? 'selected' : ''}>Fixed Price (Force Value)</option>
                </select>
            </div>
            <div class="cfg-field"><label>Undercut Position (1-5)</label><input type="number" id="cfg-pos" min="1" max="5" value="${settings.undercutPos}"></div>
            <div class="cfg-field"><label>Auto-Fill Source</label>
                <select id="cfg-source"><option value="bz" ${settings.priceSource === 'bz' ? 'selected' : ''}>Bazaar</option><option value="im" ${settings.priceSource === 'im' ? 'selected' : ''}>Item Market</option><option value="mv" ${settings.priceSource === 'mv' ? 'selected' : ''}>Market Value (MV)</option></select>
            </div>

            <div id="cfg-cat-section" style="border-top: 1px solid #444; margin-top: 15px; padding-top: 10px;">
                <div id="toggle-cat-editor" style="color:#00aaff; font-weight:bold; font-size:11px; cursor:pointer; display:flex; justify-content:space-between; padding: 5px 0;">
                    <span>CATEGORY OVERRIDES</span> <span id="cat-status-icon">▼</span>
                </div>
                <div id="cat-editor-body" style="margin-top:10px; display:none;">
                    <select id="cfg-cat-select" style="width:100%; margin-bottom:8px; height:35px; color:#000; background:#fff;">
                        <option value="Plushies">Plushies</option><option value="Flowers">Flowers</option><option value="Medical Items">Medical Items</option>
                        <option value="Drugs">Drugs</option><option value="Energy Drinks">Energy Drinks</option><option value="Boosters">Boosters</option>
                        <option value="Candy">Candy</option><option value="Alcohol">Alcohol</option><option value="Melee">Melee</option>
                        <option value="Primary">Primary</option><option value="Secondary">Secondary</option><option value="Armour">Armour</option>
                        <option value="Enhancer">Enhancer</option><option value="Clothing">Clothing</option><option value="Electronic">Electronic</option>
                        <option value="Jewelry">Jewelry</option><option value="Car">Car</option><option value="Supply Pack">Supply Pack</option>
                        <option value="Special">Special</option><option value="Artifacts">Artifacts</option><option value="Books">Books</option>
                        <option value="Materials">Materials</option>
                    </select>
                    <div style="display:flex; gap:5px; margin-bottom:8px;">
                        <input type="number" id="cfg-cat-val" placeholder="Amount / Value" style="width:40%; height:35px; color:#000; background:#fff;">
                        <select id="cfg-cat-type" style="width:35%; height:35px; color:#000; background:#fff;">
                            <option value="fixed">$ Under</option><option value="percent">% Under</option><option value="absolute">Fixed</option>
                        </select>
                        <input type="number" id="cfg-cat-pos" placeholder="Position" value="" style="width:25%; height:35px; color:#000; background:#fff;">
                    </div>
                    <select id="cfg-cat-source" style="width:100%; height:35px; margin-bottom:10px; color:#000; background:#fff;">
                        <option value="im">Item Market</option><option value="bz">Bazaar</option><option value="mv">Market Value</option>
                    </select>
                    <button id="cfg-add-cat" style="width:100%; background:#00aaff; color:#fff; padding:10px; border:none; font-weight:bold; border-radius:4px;">ADD RULE</button>
                    <div id="cfg-cat-list" style="font-size:12px; color:#ccc; max-height:100px; overflow-y:auto; margin-top:10px;"></div>
                </div>
            </div>

            <div class="sync-container">
                <label style="font-size:10px; font-weight:bold; color:#aaa; text-transform:uppercase;">Device Sync</label>
                <div class="sync-btns">
                    <button class="sync-btn" id="btn-export">Export</button>
                    <button class="sync-btn" id="btn-import">Import</button>
                </div>
            </div>

            <div style="margin-top:20px;"><button class="btn-save" id="cfg-save">SAVE ALL</button><button class="btn-close" id="cfg-close">CLOSE</button></div>
        </div>`).appendTo("body");

        $("#cfg-save").on('touchstart click', (e) => { e.preventDefault(); GM_setValue("tornApiKey", $("#cfg-api").val()); GM_setValue("undercutVal", $("#cfg-val").val()); GM_setValue("undercutType", $("#cfg-type").val()); GM_setValue("undercutPos", $("#cfg-pos").val()); GM_setValue("priceSource", $("#cfg-source").val()); loadSettings(); $("#filler-config-modal").hide(); });
        $("#cfg-close").on('touchstart click', (e) => { e.preventDefault(); $("#filler-config-modal").hide(); });
        $('#toggle-cat-editor').on('click', () => { $('#cat-editor-body').slideToggle(150); $('#cat-status-icon').text($('#cat-status-icon').text() === '▼' ? '▲' : '▼'); });

        $("#btn-export").on('click', performExport);
        $("#btn-import").on('click', performImport);
    }

    $(document).on('click', '#cfg-add-cat', function(e) {
        e.preventDefault();
        const val = $('#cfg-cat-val').val(); if(!val) return;
        const cat = $('#cfg-cat-select').val();
        settings.categoryRules[cat] = { val, type: $('#cfg-cat-type').val(), pos: $('#cfg-cat-pos').val() || 1, source: $('#cfg-cat-source').val() };
        GM_setValue("categoryRules", JSON.stringify(settings.categoryRules));
        renderCatList();
        $('#cfg-cat-val').val('');
        $('#cfg-cat-pos').val('');
    });

    function renderCatList() {
        let html = "";
        const types = { 'fixed': '$ Under', 'percent': '% Under', 'absolute': 'Fixed' };
        Object.keys(settings.categoryRules || {}).forEach(cat => {
            let r = settings.categoryRules[cat];
            let typeLabel = types[r.type] || r.type;
            html += `<div style="display:flex; justify-content:space-between; border-bottom:1px solid #222; padding:4px 0;">
                <span>[${r.source.toUpperCase()}] <b>${cat}</b>: ${r.val} ${typeLabel} (P${r.pos})</span>
                <span class="del-cat" data-cat="${cat}" style="color:#ff3b3b; cursor:pointer;">[X]</span>
            </div>`;
        });
        $('#cfg-cat-list').html(html || "No overrides");
    }

    $(document).on('click', '.del-cat', function() { delete settings.categoryRules[$(this).data('cat')]; GM_setValue("categoryRules", JSON.stringify(settings.categoryRules)); renderCatList(); });
    $(document).on('change', '.filler-skip-checkbox', function() { settings.skippedItems[$(this).data('id')] = $(this).is(':checked'); GM_setValue("skippedItems", JSON.stringify(settings.skippedItems)); });

    const obs = new MutationObserver(inject);
    obs.observe(document.body, { childList: true, subtree: true });
    inject();

    // Trends Logic
    (function() {
        const trendObserver = new MutationObserver(() => {
            const $popup = $('.draggable-popup');
            const $mvLine = $popup.find('.mv-banner');
            const itemId = $popup.find('.item-label').attr('data-id');
            if ($popup.is(':visible') && $mvLine.length > 0 && $mvLine.find('.pro-trend').length === 0 && trendData && itemId) {
                if (trendData[itemId]) {
                    const h = trendData[itemId].history, latest = h[h.length - 1], prev = h[h.length - 2] || latest, hi = Math.max(...h), lo = Math.min(...h);
                    const change = (((latest - prev) / prev) * 100).toFixed(2), col = change > 0 ? "#7cfc00" : (change < 0 ? "#ff3b3b" : "#aaa");
                    let stat = latest >= hi ? "🔥" : (latest <= lo ? "🧊" : "");
                    const pts = h.map((p, i) => `${(i / (h.length - 1 || 1)) * 40},${10 - ((p - lo) / (hi - lo || 1)) * 10}`).join(' ');
                    $mvLine.append(`<span class="pro-trend" style="margin-left:10px; display:inline-flex; align-items:center; border-left: 1px solid #444; padding-left: 10px;"><span style="color:${col}; font-weight:bold; font-size:12px; margin-right:5px;">${stat} ${change}%</span><svg width="40" height="12"><polyline fill="none" stroke="${col}" stroke-width="1.5" points="${pts}"/></svg></span>`);
                }
            }
        });
        trendObserver.observe(document.body, { childList: true, subtree: true });
    })();

    // Stock Logic - Optimized to prevent flashing
    (function() {
        function updateStock() {
            const $p = $('.draggable-popup'), $h = $p.find('.popup-header');
            if ($p.is(':visible') && $h.length > 0 && !isFetching) {
                const itemId = $p.find('.item-label').attr('data-id');
                if (myBazaarData && itemId) {
                    let itm = myBazaarData.find(i => String(i.ID || i.item_id) === String(itemId));
                    if (itm) {
                        const testTxt = `Qty: ${itm.quantity.toLocaleString()} @ $${itm.price.toLocaleString()}`;
                        if ($p.find('#test-line').text() !== testTxt) {
                            $p.find('#test-line').text(testTxt).css('opacity', '1');
                        }
                    } else {
                        $p.find('#test-line').text("Qty: 0 @ $0").css('opacity', '1');
                    }
                }
            }
        }

        function fetchBZ(force = false) {
            if (!settings.apiKey || isFetching) return;
            if (force || (Date.now() - lastFetch > 15000)) {
                isFetching = true;
                $('.draggable-popup #test-line').css('opacity', '0.5');
                GM_xmlhttpRequest({
                    method: "GET",
                    url: `https://api.torn.com/user/?selections=bazaar&key=${settings.apiKey}`,
                    onload: (res) => {
                        try {
                            const json = JSON.parse(res.responseText);
                            if (json.bazaar) {
                                myBazaarData = Array.isArray(json.bazaar) ? json.bazaar : Object.values(json.bazaar);
                                lastFetch = Date.now();
                                updateStock();
                            }
                        } catch(e){ console.error("Bazaar Sync Error", e); }
                        isFetching = false;
                        $('.draggable-popup #test-line').css('opacity', '1');
                    },
                    onerror: () => {
                        isFetching = false;
                        $('.draggable-popup #test-line').css('opacity', '1');
                    }
                });
            }
        }

        setInterval(() => { if ($('.draggable-popup').is(':visible')) updateStock(); }, 500);
        $(document).on('click touchstart', '.item-toggle-btn', function() { fetchBZ(); });
        fetchBZ();
    })();

    // Trade Fill Logic
    function injectTradeFill() {
        if (!window.location.href.includes('trade.php')) return;
        const topFooter = $(".items-footer.clearfix").first();
        if (topFooter.length && !$("#trade-fill-qty-single").length) {
            const $btn = $('<a id="trade-fill-qty-single" style="cursor: pointer; margin-left: 15px; font-size: 12px; color: #00aaff; text-transform: uppercase; font-weight: bold; line-height: 24px;">Fill Qty</a>');
            topFooter.append($btn);
            $btn.on('click', function(e) {
                e.preventDefault();
                $("li.clearfix.no-mods").each(function() {
                    const qtyMatch = $(this).find(".name-wrap").text().match(/x(\d+)/);
                    updateTornInput($(this).find("input[type='text']"), qtyMatch ? qtyMatch[1] : "1");
                });
            });
        }
    }
    const tradeObs = new MutationObserver(injectTradeFill);
    tradeObs.observe(document.body, { childList: true, subtree: true });
    injectTradeFill();

    // --- POPUP CONTROLS (DRAG & AUTO-CLOSE) ---
    function makeDraggable(el) {
        let p1 = 0, p2 = 0, p3 = 0, p4 = 0;
        const header = el.querySelector('.popup-header') || el;
        const dragStart = (e) => {
            const touch = e.type === 'touchstart' ? e.touches[0] : e;
            p3 = touch.clientX; p4 = touch.clientY;
            if (e.type === 'mousedown') {
                document.addEventListener('mousemove', dragging); document.addEventListener('mouseup', dragEnd);
            } else {
                document.addEventListener('touchmove', dragging, { passive: false }); document.addEventListener('touchend', dragEnd);
            }
        };
        const dragging = (e) => {
            if (e.type === 'touchmove') e.preventDefault();
            const touch = e.type === 'touchmove' ? e.touches[0] : e;
            p1 = p3 - touch.clientX; p2 = p4 - touch.clientY;
            p3 = touch.clientX; p4 = touch.clientY;
            el.style.top = (el.offsetTop - p2) + "px"; el.style.left = (el.offsetLeft - p1) + "px";
        };
        const dragEnd = () => {
            document.removeEventListener('mousemove', dragging); document.removeEventListener('mouseup', dragEnd);
            document.removeEventListener('touchmove', dragging); document.removeEventListener('touchend', dragEnd);
        };
        header.addEventListener('mousedown', dragStart); header.addEventListener('touchstart', dragStart);
    }

    $(document).on('mousedown touchstart', function (e) {
        const $popup = $('.draggable-popup');
        if ($popup.is(':visible')) {
            if (!$popup.is(e.target) && $popup.has(e.target).length === 0 && !$(e.target).closest('.item-toggle-btn').length) {
                $popup.hide();
            }
        }
    });

})();