Junk Seller

Quickly sell unwanted items in your inventory to various shops

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

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

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

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

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

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

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

Advertisement:

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

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

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

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

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

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

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

Advertisement:

// ==UserScript==
// @name         Junk Seller
// @namespace    http://tampermonkey.net/
// @version      0.2.0
// @description  Quickly sell unwanted items in your inventory to various shops
// @author       DF__Lykos [4070584]
// @match        https://www.torn.com/item.php*
// @match        https://www.torn.com/shops.php*
// @match        https://www.torn.com/bigalgunshop.php*
// @grant        none
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // ==================== Junk List (from price_comparison.txt) ====================
    const JUNK_LIST = [
      { id: 181,  name: "Bottle of Champagne",    type: "Alcohol",   seller: "Bits 'n' Bobs",      sellerLink: "https://www.torn.com/shops.php?step=bitsnbobs" },
      { id: 645,  name: "Safety Boots",            type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 176,  name: "Chain Mail",              type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 178,  name: "Flak Jacket",             type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 640,  name: "Kevlar Gloves",           type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 646,  name: "Hiking Boots",            type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 50,   name: "Outer Tactical Vest",     type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 49,   name: "Full Body Armor",         type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 647,  name: "Leather Helmet",          type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 32,   name: "Leather Vest",            type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 650,  name: "Leather Gloves",          type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 649,  name: "Leather Boots",           type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 648,  name: "Leather Pants",           type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 34,   name: "Bulletproof Vest",        type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 643,  name: "Construction Helmet",     type: "Armor",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 517,  name: "Weston Marlin 177",       type: "Car",       seller: "Docks",              sellerLink: "https://www.torn.com/shops.php?step=docks" },
      { id: 83,   name: "Dart Rampager",           type: "Car",       seller: "Docks",              sellerLink: "https://www.torn.com/shops.php?step=docks" },
      { id: 78,   name: "Edomondo NSX",            type: "Car",       seller: "Docks",              sellerLink: "https://www.torn.com/shops.php?step=docks" },
      { id: 523,  name: "Mercia SLR",              type: "Car",       seller: "Docks",              sellerLink: "https://www.torn.com/shops.php?step=docks" },
      { id: 520,  name: "Lolo 458",                type: "Car",       seller: "Docks",              sellerLink: "https://www.torn.com/shops.php?step=docks" },
      { id: 80,   name: "Bavaria M5",              type: "Car",       seller: "Docks",              sellerLink: "https://www.torn.com/shops.php?step=docks" },
      { id: 89,   name: "Edomondo ACD",            type: "Car",       seller: "Docks",              sellerLink: "https://www.torn.com/shops.php?step=docks" },
      { id: 79,   name: "Echo Quadrato",           type: "Car",       seller: "Docks",              sellerLink: "https://www.torn.com/shops.php?step=docks" },
      { id: 95,   name: "Oceania SS",              type: "Car",       seller: "Docks",              sellerLink: "https://www.torn.com/shops.php?step=docks" },
      { id: 404,  name: "Bandana",                 type: "Clothing",  seller: "TC Clothing",        sellerLink: "https://www.torn.com/shops.php?step=clothes" },
      { id: 625,  name: "Wetsuit",                 type: "Clothing",  seller: "TC Clothing",        sellerLink: "https://www.torn.com/shops.php?step=clothes" },
      { id: 621,  name: "Snorkel",                 type: "Clothing",  seller: "TC Clothing",        sellerLink: "https://www.torn.com/shops.php?step=clothes" },
      { id: 107,  name: "Trench Coat",             type: "Clothing",  seller: "TC Clothing",        sellerLink: "https://www.torn.com/shops.php?step=clothes" },
      { id: 430,  name: "Coconut Bra",             type: "Clothing",  seller: "TC Clothing",        sellerLink: "https://www.torn.com/shops.php?step=clothes" },
      { id: 626,  name: "Diving Gloves",           type: "Clothing",  seller: "TC Clothing",        sellerLink: "https://www.torn.com/shops.php?step=clothes" },
      { id: 414,  name: "Proda Sunglasses",        type: "Clothing",  seller: "TC Clothing",        sellerLink: "https://www.torn.com/shops.php?step=clothes" },
      { id: 412,  name: "Sports Shades",           type: "Clothing",  seller: "TC Clothing",        sellerLink: "https://www.torn.com/shops.php?step=clothes" },
      { id: 624,  name: "Bikini",                  type: "Clothing",  seller: "TC Clothing",        sellerLink: "https://www.torn.com/shops.php?step=clothes" },
      { id: 622,  name: "Flippers",                type: "Clothing",  seller: "TC Clothing",        sellerLink: "https://www.torn.com/shops.php?step=clothes" },
      { id: 413,  name: "Mountie Hat",             type: "Clothing",  seller: "TC Clothing",        sellerLink: "https://www.torn.com/shops.php?step=clothes" },
      { id: 623,  name: "Speedo",                  type: "Clothing",  seller: "TC Clothing",        sellerLink: "https://www.torn.com/shops.php?step=clothes" },
      { id: 54,   name: "Diamond Ring",            type: "Jewelry",   seller: "Jewelry Store",      sellerLink: "https://www.torn.com/shops.php?step=jewelry" },
      { id: 227,  name: "Spear",                   type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 173,  name: "Butterfly Knife",         type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 8,    name: "Axe",                     type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 224,  name: "Swiss Army Knife",        type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 110,  name: "Leather Bullwhip",        type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 1231, name: "Golf Club",               type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 401,  name: "Lead Pipe",               type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 4,    name: "Knuckle Dusters",         type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 3,    name: "Crowbar",                 type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 439,  name: "Frying Pan",              type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 1,    name: "Hammer",                  type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 5,    name: "Pen Knife",               type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 7,    name: "Dagger",                  type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 217,  name: "Claymore Sword",          type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 236,  name: "Kama",                    type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 402,  name: "Ice Pick",                type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 247,  name: "Katana",                  type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 11,   name: "Samurai Sword",           type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 400,  name: "Guandao",                 type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 438,  name: "Cricket Bat",             type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 9,    name: "Scimitar",                type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 6,    name: "Kitchen Knife",           type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 2,    name: "Baseball Bat",            type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 10,   name: "Chainsaw",                type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 111,  name: "Ninja Claws",             type: "Melee",     seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 1495, name: "Ambergris Lump",          type: "Other",     seller: "Jewelry Store",      sellerLink: "https://www.torn.com/shops.php?step=jewelry" },
      { id: 411,  name: "Model Space Ship",        type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 1498, name: "Tiger Bone Powder",       type: "Other",     seller: "Nikeh Performance",  sellerLink: "https://www.torn.com/shops.php?step=nikeh" },
      { id: 1497, name: "Uncut Diamonds",          type: "Other",     seller: "Jewelry Store",      sellerLink: "https://www.torn.com/shops.php?step=jewelry" },
      { id: 1483, name: "Insulin",                 type: "Other",     seller: "Pharmacy",           sellerLink: "https://www.torn.com/shops.php?step=pharmacy" },
      { id: 434,  name: "Yakitori Lantern",        type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 894,  name: "Cosmetics Case",          type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 1482, name: "Bearer Bond",             type: "Other",     seller: "Pawn Shop",          sellerLink: "https://www.torn.com/shops.php?step=pawnshop" },
      { id: 275,  name: "Jade Buddha",             type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 1251, name: "Boat Engine",             type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 409,  name: "Yucca Plant",             type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 896,  name: "Subway Pass",             type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 1211, name: "Fishing Rod",             type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 890,  name: "Headphones",              type: "Other",     seller: "Super Store",        sellerLink: "https://www.torn.com/shops.php?step=super" },
      { id: 410,  name: "Fire Hydrant",            type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 1345, name: "Prescription",            type: "Other",     seller: "Print Shop",         sellerLink: "https://www.torn.com/shops.php?step=printstore" },
      { id: 1240, name: "Perfume",                 type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 436,  name: "Snowboard",               type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 1335, name: "Parking Permit",          type: "Other",     seller: "Print Shop",         sellerLink: "https://www.torn.com/shops.php?step=printstore" },
      { id: 1336, name: "Birth Certificate",       type: "Other",     seller: "Print Shop",         sellerLink: "https://www.torn.com/shops.php?step=printstore" },
      { id: 1086, name: "Driver's License",        type: "Other",     seller: "Print Shop",         sellerLink: "https://www.torn.com/shops.php?step=printstore" },
      { id: 418,  name: "Tailor's Dummy",          type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 1341, name: "Concert Ticket",          type: "Other",     seller: "Print Shop",         sellerLink: "https://www.torn.com/shops.php?step=printstore" },
      { id: 1253, name: "Tractor Part",            type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 279,  name: "Maneki Neko",             type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 1342, name: "Travel Visa",             type: "Other",     seller: "Print Shop",         sellerLink: "https://www.torn.com/shops.php?step=printstore" },
      { id: 1256, name: "Machine Part",            type: "Other",     seller: "Bits 'n' Bobs",      sellerLink: "https://www.torn.com/shops.php?step=bitsnbobs" },
      { id: 358,  name: "Raw Ivory",               type: "Other",     seller: "Pawn Shop",          sellerLink: "https://www.torn.com/shops.php?step=pawnshop" },
      { id: 1491, name: "Ergotamine Ampoule",      type: "Other",     seller: "Pharmacy",           sellerLink: "https://www.torn.com/shops.php?step=pharmacy" },
      { id: 1337, name: "Diploma",                 type: "Other",     seller: "Print Shop",         sellerLink: "https://www.torn.com/shops.php?step=printstore" },
      { id: 1493, name: "Whale Meat",              type: "Other",     seller: "Nikeh Performance",  sellerLink: "https://www.torn.com/shops.php?step=nikeh" },
      { id: 1496, name: "Natural Pearls",          type: "Other",     seller: "Jewelry Store",      sellerLink: "https://www.torn.com/shops.php?step=jewelry" },
      { id: 1486, name: "Turtle Shell",            type: "Other",     seller: "Jewelry Store",      sellerLink: "https://www.torn.com/shops.php?step=jewelry" },
      { id: 1494, name: "Pangolin Scales",         type: "Other",     seller: "Nikeh Performance",  sellerLink: "https://www.torn.com/shops.php?step=nikeh" },
      { id: 1485, name: "Shark Fin",               type: "Other",     seller: "Nikeh Performance",  sellerLink: "https://www.torn.com/shops.php?step=nikeh" },
      { id: 1492, name: "Counterfeit Manga",       type: "Other",     seller: "Pawn Shop",          sellerLink: "https://www.torn.com/shops.php?step=pawnshop" },
      { id: 1343, name: "Passport",                type: "Other",     seller: "Print Shop",         sellerLink: "https://www.torn.com/shops.php?step=printstore" },
      { id: 1489, name: "Ephedrine Powder",        type: "Other",     seller: "Pharmacy",           sellerLink: "https://www.torn.com/shops.php?step=pharmacy" },
      { id: 1490, name: "Safrole Oil",             type: "Other",     seller: "Pharmacy",           sellerLink: "https://www.torn.com/shops.php?step=pharmacy" },
      { id: 1484, name: "Bear Gall",               type: "Other",     seller: "Nikeh Performance",  sellerLink: "https://www.torn.com/shops.php?step=nikeh" },
      { id: 1349, name: "License Plate",           type: "Other",     seller: "Print Shop",         sellerLink: "https://www.torn.com/shops.php?step=printstore" },
      { id: 1339, name: "Bank Check",              type: "Other",     seller: "Print Shop",         sellerLink: "https://www.torn.com/shops.php?step=printstore" },
      { id: 406,  name: "Afro Comb",               type: "Other",     seller: "Recycling Center",   sellerLink: "https://www.torn.com/shops.php?step=recyclingcenter" },
      { id: 487,  name: "Thompson",                type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 249,  name: "SKS Carbine",             type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 26,   name: "AK-47",                   type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 231,  name: "Heckler & Koch SL8",      type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 241,  name: "Bushmaster Carbon 15",    type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 108,  name: "9mm Uzi",                 type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 252,  name: "Ithaca 37",               type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 25,   name: "P90",                     type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 232,  name: "SIG 550",                 type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 30,   name: "Steyr AUG",               type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 29,   name: "M16 A2 Rifle",            type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 23,   name: "Benelli M1 Tactical",     type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 22,   name: "Sawed-Off Shotgun",       type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 27,   name: "M4A1 Colt Carbine",       type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 174,  name: "XM8 Rifle",               type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 28,   name: "Benelli M4 Super",        type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 24,   name: "MP5 Navy",                type: "Primary",   seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 613,  name: "Harpoon",                 type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 175,  name: "Taser",                   type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 486,  name: "TMP",                     type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 230,  name: "Flare Gun",               type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 16,   name: "USP",                     type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 15,   name: "Beretta M9",              type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 489,  name: "Luger",                   type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 13,   name: "Raven MP25",              type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 485,  name: "Skorpion",                type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 189,  name: "S&W Revolver",            type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 244,  name: "Blowgun",                 type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 14,   name: "Ruger 57",                type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 19,   name: "Magnum",                  type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 20,   name: "Desert Eagle",            type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 18,   name: "Fiveseven",               type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 218,  name: "Crossbow",                type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 243,  name: "Taurus",                  type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 240,  name: "Type 98 Anti Tank",       type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 248,  name: "Qsz-92",                  type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 12,   name: "Glock 17",                type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 177,  name: "Cobra Derringer",         type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 483,  name: "MP5k",                    type: "Secondary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 257,  name: "Throwing Knife",          type: "Temporary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 242,  name: "HEG",                     type: "Temporary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
      { id: 220,  name: "Grenade",                 type: "Temporary", seller: "Big Al's Gun Shop",  sellerLink: "https://www.torn.com/bigalgunshop.php" },
    ];

    // Fast lookup: item id -> junk list entry
    const JUNK_MAP = Object.fromEntries(JUNK_LIST.map(e => [e.id, e]));

    // Our type names -> Torn API "cat" parameter values
    // (Armor = "Defensive" in the Torn v2 API enum)
    const TYPE_TO_API_CAT = {
        Alcohol:   'Alcohol',
        Armor:     'Defensive',
        Car:       'Car',
        Clothing:  'Clothing',
        Jewelry:   'Jewelry',
        Melee:     'Melee',
        Other:     'Other',
        Primary:   'Primary',
        Secondary: 'Secondary',
        Temporary: 'Temporary',
    };

    // Distinct API categories actually needed for our list
    const API_CATS = [...new Set(
        JUNK_LIST.map(e => TYPE_TO_API_CAT[e.type]).filter(Boolean)
    )];

    // ==================== Constants ====================
    const STORAGE_KEY_API     = 'sell_junk_api_key';
    const STORAGE_KEY_SESSION = 'sell_junk_session';
    const STORAGE_KEY_CONFIG  = 'sell_junk_config';
    const API_BASE            = 'https://api.torn.com/v2/user/inventory';

    // Categories excluded from junk by default (protect rank-war gear)
    const DEFAULT_OFF_CATS = new Set(['Armor', 'Melee', 'Primary', 'Secondary']);

    // ==================== Storage Helpers ====================
    function getApiKey() { return localStorage.getItem(STORAGE_KEY_API) || ''; }
    function setApiKey(k) { localStorage.setItem(STORAGE_KEY_API, k); }

    function getSession() {
        try { return JSON.parse(localStorage.getItem(STORAGE_KEY_SESSION)) || null; }
        catch { return null; }
    }
    function setSession(s) { localStorage.setItem(STORAGE_KEY_SESSION, JSON.stringify(s)); }
    function clearSession() { localStorage.removeItem(STORAGE_KEY_SESSION); }

    // Config: { excludedIds: number[] }  —  ids of items NOT considered junk
    function buildDefaultConfig() {
        return { excludedIds: JUNK_LIST.filter(e => DEFAULT_OFF_CATS.has(e.type)).map(e => e.id) };
    }
    function getConfig() {
        try {
            const raw = localStorage.getItem(STORAGE_KEY_CONFIG);
            if (raw) return JSON.parse(raw);
        } catch {}
        return buildDefaultConfig();
    }
    function saveConfig(cfg) { localStorage.setItem(STORAGE_KEY_CONFIG, JSON.stringify(cfg)); }

    // ==================== Inventory API ====================
    async function fetchCat(key, cat) {
        const now = Math.floor(Date.now() / 1000); // Unix timestamp — bypasses Torn's 1-hour cache
        const url = `${API_BASE}?cat=${encodeURIComponent(cat)}&offset=0&limit=250` +
                    `&timestamp=${now}` +
                    `&key=${encodeURIComponent(key)}&comment=SellJunk`;
        const resp = await fetch(url);
        if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
        const data = await resp.json();
        if (data.error) throw new Error(`API error ${data.error.code}: ${data.error.error}`);
        return data.inventory?.items ?? [];
    }

    async function fetchAllMatchingInventory(key) {
        const results = await Promise.all(
            API_CATS.map(cat =>
                fetchCat(key, cat).catch(err => {
                    console.warn(`[Sell Junk] cat=${cat}:`, err);
                    return [];
                })
            )
        );

        // Flatten, deduplicate by id+uid, keep only non-excluded items in our junk list.
        const excludedSet = new Set(getConfig().excludedIds);
        const seen = new Set();
        const matches = [];

        for (const items of results) {
            for (const inv of items) {
                const key = `${inv.id}_${inv.uid ?? ''}`;
                if (seen.has(key)) continue;
                seen.add(key);
                if (JUNK_MAP[inv.id] && !excludedSet.has(inv.id)) {
                    matches.push({ inv, entry: JUNK_MAP[inv.id] });
                }
            }
        }

        return matches;
    }

    // ==================== Loading Overlay ====================
    function showLoading() {
        removePanel();
        const ov = document.createElement('div');
        ov.id = 'sell-junk-panel-overlay';
        ov.style.cssText = overlayStyle();
        ov.innerHTML = `
            <div style="${boxStyle()};padding:30px 44px;text-align:center;">
                <div style="font-size:14px;margin-bottom:8px;">🔍 Checking inventory…</div>
                <div style="font-size:12px;color:#666;">
                    Querying ${API_CATS.length} categories in parallel
                </div>
            </div>`;
        document.body.appendChild(ov);
        return ov;
    }

    // ==================== Results Panel ====================
    function showResults(matches) {
        removePanel();

        // Group by shop, preserving insertion order
        const byShop = new Map();
        for (const { inv, entry } of matches) {
            const shop = entry.seller || '(Unknown)';
            if (!byShop.has(shop)) {
                byShop.set(shop, { link: entry.sellerLink, items: [] });
            }
            byShop.get(shop).items.push({ id: entry.id, name: entry.name, qty: inv.amount });
        }

        const ov = document.createElement('div');
        ov.id = 'sell-junk-panel-overlay';
        ov.style.cssText = overlayStyle();

        const panel = document.createElement('div');
        panel.style.cssText = [
            boxStyle(),
            'width:540px',
            'max-width:94vw',
            'max-height:82vh',
            'overflow-y:auto',
            'padding:20px 22px',
        ].join(';');

        /* ---- header ---- */
        let html = `
            <div style="display:flex;align-items:center;justify-content:space-between;
                        border-bottom:1px solid #383838;padding-bottom:10px;margin-bottom:14px;">
                <span style="font-size:15px;font-weight:bold;">🗑️ Sell Junk</span>
                <div style="display:flex;gap:6px;align-items:center;">
                    <button id="sj-config-btn" title="Configure which items are treated as junk"
                        style="${btnStyle('#1a3a5a','#7ab')};font-size:11px;padding:3px 9px;">
                        ⚙ Junk List
                    </button>
                    <button id="sj-key-btn" title="Update API key"
                        style="${btnStyle('#333','#888')};font-size:11px;padding:3px 9px;">
                        ⚙ Key
                    </button>
                    <button id="sj-close-btn"
                        style="${btnStyle('#444','#ccc')};padding:3px 10px;">✕</button>
                </div>
            </div>`;

        /* ---- body ---- */
        if (byShop.size === 0) {
            html += `
                <div style="text-align:center;padding:20px 0;">
                    <p style="color:#666;font-size:13px;margin:0 0 14px;">
                        No junk found in your inventory.
                    </p>
                    <p style="color:#555;font-size:12px;margin:0 0 14px;">
                        Weapons &amp; armor are excluded by default to protect rank war gear.<br>
                        Open the junk list config to add or remove items.
                    </p>
                    <button id="sj-config-btn-empty"
                        style="${btnStyle('#1a3a5a','#7ab')};padding:7px 16px;">
                        ⚙ Configure Junk List
                    </button>
                </div>`;
        } else {
            for (const [shop, { items }] of byShop) {
                html += `
                <div style="margin-bottom:12px;background:#111;border:1px solid #2e2e2e;
                            border-radius:5px;padding:10px 12px;">
                    <div style="margin-bottom:7px;">
                        <span style="font-weight:bold;font-size:13px;color:#c8c8c8;">
                            🏪 ${shop}
                        </span>
                    </div>
                    <ul style="margin:0;padding:0 0 0 14px;font-size:12px;color:#999;
                                line-height:1.7;">
                        ${items.map(i =>
                            `<li>${i.name} <span style="color:#5a9;font-size:11px;">×${i.qty}</span></li>`
                        ).join('')}
                    </ul>
                </div>`;
            }
        }

        /* ---- footer ---- */
        html += `
            <div style="border-top:1px solid #383838;padding-top:12px;margin-top:4px;
                        display:flex;align-items:center;justify-content:space-between;">
                <span style="font-size:11px;color:#555;">
                    ${matches.length} item type(s) · ${byShop.size} shop(s)
                </span>
                ${byShop.size > 0 ? `
                <button id="sj-go-shops-btn" style="${btnStyle('#1a6a30','#fff')}">
                    🛒 Go to Shops →
                </button>` : ''}
            </div>`;

        panel.innerHTML = html;
        ov.appendChild(panel);
        document.body.appendChild(ov);

        ov.addEventListener('click', e => { if (e.target === ov) removePanel(); });
        panel.querySelector('#sj-close-btn').addEventListener('click', removePanel);
        panel.querySelector('#sj-key-btn').addEventListener('click', () => {
            removePanel();
            showApiKeyModal();
        });
        panel.querySelector('#sj-config-btn')?.addEventListener('click', () => {
            removePanel();
            showConfigPanel();
        });
        panel.querySelector('#sj-config-btn-empty')?.addEventListener('click', () => {
            removePanel();
            showConfigPanel();
        });
        panel.querySelector('#sj-go-shops-btn')?.addEventListener('click', () => {
            startSession(byShop);
        });
    }

    // ==================== Select All Junk ====================
    /**
     * Iterates every <li data-item="…"> in .sell-items-list and, for each one
     * whose item-ID is in the current session's junk list:
     *  • choice-container items (qty=1): clicks the label to tick the checkbox
     *  • input-money-group items (qty>1): fills the visible text input with
     *    data-money (max qty) and fires events so Torn's JS reacts
     *
     * Full console logging is included — open DevTools > Console and look for
     * [Sell Junk] entries to diagnose any issues.
     */
    function selectAllJunk(btn, _attempt) {
        const attempt = _attempt || 1;
        const LOG = (...a) => console.log('[Sell Junk]', ...a);

        // ── 1. Read session ───────────────────────────────────────────────
        const session = getSession();
        if (!session) {
            LOG('selectAllJunk: no active session in localStorage');
            return;
        }
        const { shops, currentIndex } = session;
        const current = shops[currentIndex];
        LOG(`selectAllJunk: shop="${current.name}" items=`, current.items);

        // ── 2. Build junk-ID set (handle stale sessions missing .id) ─────
        //    Fallback: look up by name via JUNK_MAP if id is absent
        const junkIds = new Set();
        for (const item of current.items) {
            if (item.id !== undefined && item.id !== null) {
                junkIds.add(String(item.id));
            } else {
                // Stale session — recover id from JUNK_MAP by name
                const found = JUNK_LIST.find(e => e.name === item.name);
                if (found) junkIds.add(String(found.id));
            }
        }
        LOG('selectAllJunk: junkIds =', [...junkIds]);

        // ── 3. Wait for sell list ─────────────────────────────────────────
        const sellList = document.querySelector('.sell-items-list');
        if (!sellList) {
            LOG(`selectAllJunk: .sell-items-list not found (attempt ${attempt})`);
            if (attempt <= 10) {
                if (btn && attempt === 1) { btn.textContent = 'Waiting…'; btn.disabled = true; }
                setTimeout(() => selectAllJunk(btn, attempt + 1), 500);
            } else {
                LOG('selectAllJunk: gave up waiting for .sell-items-list');
                if (btn) { btn.textContent = '☑ Select All Junk'; btn.disabled = false; }
            }
            return;
        }

        const allLis = sellList.querySelectorAll('li[data-item]');
        LOG(`selectAllJunk: found ${allLis.length} li[data-item] rows in sell list`);

        let count = 0;

        allLis.forEach(li => {
            const dataItem = li.getAttribute('data-item');
            if (!junkIds.has(dataItem)) {
                LOG(`  skip data-item="${dataItem}" (not in junk list)`);
                return;
            }
            LOG(`  process data-item="${dataItem}"`);

            // ── Toggle (qty = 1): choice-container checkbox ───────────────
            const checkbox = li.querySelector('.choice-container input[type="checkbox"]');
            if (checkbox) {
                LOG(`    → checkbox found, checked=${checkbox.checked}`);
                if (!checkbox.checked) {
                    const label = li.querySelector('label.marker-css');
                    if (label) {
                        LOG('    → clicking label');
                        label.click();
                    } else {
                        LOG('    → clicking checkbox directly');
                        checkbox.click();
                    }
                }
                count++;
                return;
            }

            // ── Quantity input (qty > 1): input-money-group ───────────────
            const textInput = li.querySelector('.input-money-group input[type="text"]');
            if (textInput) {
                const max = textInput.getAttribute('data-money') || '';
                LOG(`    → text input found, data-money="${max}", current value="${textInput.value}"`);

                // Use native setter so React/framework change detection triggers
                const nativeSetter = Object.getOwnPropertyDescriptor(
                    window.HTMLInputElement.prototype, 'value'
                )?.set;
                if (nativeSetter) {
                    nativeSetter.call(textInput, max);
                } else {
                    textInput.value = max;
                }

                textInput.dispatchEvent(new Event('input',  { bubbles: true }));
                textInput.dispatchEvent(new Event('change', { bubbles: true }));
                textInput.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true }));
                count++;
                return;
            }

            LOG(`    → no known control found in this li`);
        });

        LOG(`selectAllJunk: done — ${count} item(s) acted on`);

        // Visual feedback
        if (btn) {
            const orig = '☑ Select All Junk';
            btn.textContent = count > 0 ? `✔ ${count} selected` : '⚠ Nothing matched';
            btn.disabled = true;
            setTimeout(() => { btn.textContent = orig; btn.disabled = false; }, 2500);
        }
    }

    // ==================== Session: start & shop overlay ====================
    function startSession(byShop) {
        const shops = [];
        for (const [name, { link, items }] of byShop) {
            shops.push({ name, link, items });
        }
        setSession({ shops, currentIndex: 0 });
        removePanel();
        window.location.href = shops[0].link;
    }

    function showShopOverlay() {
        const session = getSession();
        if (!session || !session.shops) return;

        const { shops, currentIndex } = session;
        if (currentIndex >= shops.length) { clearSession(); return; }

        const current = shops[currentIndex];
        const isLast  = currentIndex === shops.length - 1;
        const total   = shops.length;

        // Remove any stale overlay from a previous inject attempt
        document.getElementById('sell-junk-shop-overlay')?.remove();

        const el = document.createElement('div');
        el.id = 'sell-junk-shop-overlay';
        el.style.cssText = [
            'position:fixed',
            'top:24px',
            'right:24px',
            'z-index:99999',
            'width:300px',
            boxStyle(),
            'padding:14px 16px',
        ].join(';');

        el.innerHTML = `
            <div style="display:flex;align-items:center;justify-content:space-between;
                        border-bottom:1px solid #383838;padding-bottom:8px;margin-bottom:10px;">
                <span style="font-size:13px;font-weight:bold;">🗑️ Sell Junk Mode</span>
                <span style="font-size:11px;color:#555;background:#222;padding:2px 7px;
                             border-radius:10px;">${currentIndex + 1} / ${total}</span>
            </div>
            <div style="font-size:13px;font-weight:bold;color:#c8c8c8;margin-bottom:8px;">
                🏪 ${current.name}
            </div>
            <ul style="margin:0 0 10px;padding:0 0 0 14px;font-size:12px;color:#999;
                       line-height:1.8;">
                ${current.items.map(i =>
                    `<li>${i.name} <span style="color:#5a9;font-size:11px;">×${i.qty}</span></li>`
                ).join('')}
            </ul>
            <button id="sj-select-all"
                style="${btnStyle('#5a4a00','#f0c040')};width:100%;margin-bottom:10px;
                       text-align:center;border:1px solid #7a6a00;">
                ☑ Select All Junk
            </button>
            <div style="display:flex;gap:8px;justify-content:flex-end;">
                <button id="sj-mode-cancel"
                    style="${btnStyle('#444','#bbb')}">✕ Cancel</button>
                <button id="sj-mode-next"
                    style="${btnStyle(isLast ? '#2a7a3a' : '#1a4fa0', '#fff')}">
                    ${isLast ? '✓ Finish' : 'Next Shop →'}
                </button>
            </div>`;

        document.body.appendChild(el);

        el.querySelector('#sj-select-all').addEventListener('click', function () {
            selectAllJunk(this);
        });

        el.querySelector('#sj-mode-cancel').addEventListener('click', () => {
            clearSession();
            el.remove();
        });

        el.querySelector('#sj-mode-next').addEventListener('click', () => {
            if (isLast) {
                clearSession();
                el.remove();
            } else {
                setSession({ shops, currentIndex: currentIndex + 1 });
                window.location.href = shops[currentIndex + 1].link;
            }
        });
    }

    // ==================== Config Panel ====================
    function showConfigPanel() {
        removePanel();

        const cfg        = getConfig();
        const excludedSet = new Set(cfg.excludedIds);

        // Group JUNK_LIST by category; sort: non-default-off first then alpha
        const byCategory = new Map();
        for (const item of JUNK_LIST) {
            if (!byCategory.has(item.type)) byCategory.set(item.type, []);
            byCategory.get(item.type).push(item);
        }
        const sortedCats = [...byCategory.keys()].sort((a, b) => {
            const aOff = DEFAULT_OFF_CATS.has(a) ? 1 : 0, bOff = DEFAULT_OFF_CATS.has(b) ? 1 : 0;
            return aOff !== bOff ? aOff - bOff : a.localeCompare(b);
        });

        const ov = document.createElement('div');
        ov.id = 'sell-junk-panel-overlay';
        ov.style.cssText = overlayStyle();

        const panel = document.createElement('div');
        panel.style.cssText = [
            boxStyle(), 'width:560px', 'max-width:94vw',
            'max-height:86vh', 'overflow-y:auto', 'padding:20px 22px',
        ].join(';');

        const cbBase = 'width:14px;height:14px;margin-right:7px;cursor:pointer;' +
                       'accent-color:#4A90E2;flex-shrink:0;';

        let html = `
            <!-- header -->
            <div style="display:flex;align-items:center;justify-content:space-between;
                        border-bottom:1px solid #383838;padding-bottom:10px;margin-bottom:12px;">
                <span style="font-size:15px;font-weight:bold;">⚙ Configure Junk List</span>
                <div style="display:flex;gap:6px;">
                    <button id="sj-cfg-reset" style="${btnStyle('#3a2000','#ca6')};font-size:11px;padding:3px 9px;"
                            title="Restore factory defaults (weapons &amp; armor excluded)">
                        ↺ Defaults
                    </button>
                    <button id="sj-cfg-close" style="${btnStyle('#444','#ccc')};padding:3px 10px;">✕</button>
                </div>
            </div>
            <p style="font-size:12px;color:#666;margin:0 0 14px;line-height:1.6;">
                ✔ Checked items are treated as junk and will appear in scan results.<br>
                🛡 <em>Weapons &amp; armor are unchecked by default to protect rank war equipment.</em>
            </p>`;

        for (const cat of sortedCats) {
            const items     = byCategory.get(cat);
            const isProtected = DEFAULT_OFF_CATS.has(cat);
            // Category is "on" if at least one item in it is NOT excluded
            const catCheckedCount = items.filter(i => !excludedSet.has(i.id)).length;
            const catChecked      = catCheckedCount === items.length ? 'checked' : '';
            const catIndet        = catCheckedCount > 0 && catCheckedCount < items.length;

            html += `
            <div style="margin-bottom:8px;border:1px solid #2e2e2e;border-radius:5px;overflow:hidden;">
                <div style="display:flex;align-items:center;padding:6px 10px;background:#161616;">
                    <input type="checkbox" class="sj-cfg-cat-cb" data-cat="${cat}"
                           id="sj-cfg-cat-${cat}" ${catChecked} style="${cbBase}">
                    <label for="sj-cfg-cat-${cat}"
                           style="font-weight:bold;font-size:12px;color:#c8c8c8;cursor:pointer;flex:1;">
                        ${cat}${isProtected ? ' <span style="color:#888;font-size:10px;">🛡 protected by default</span>' : ''}
                    </label>
                    <span style="font-size:11px;color:#555;">${items.length} item${items.length > 1 ? 's' : ''}</span>
                </div>
                <ul style="margin:0;padding:0;list-style:none;">
                    ${items.map(item => {
                        const itemChecked = excludedSet.has(item.id) ? '' : 'checked';
                        return `
                    <li style="display:flex;align-items:center;padding:4px 10px 4px 26px;
                               border-top:1px solid #1e1e1e;">
                        <input type="checkbox" class="sj-cfg-item-cb" data-cat="${cat}"
                               data-id="${item.id}" id="sj-cfg-itm-${item.id}"
                               ${itemChecked} style="${cbBase}">
                        <label for="sj-cfg-itm-${item.id}"
                               style="flex:1;font-size:12px;color:#999;cursor:pointer;">
                            ${item.name}
                        </label>
                        <span style="font-size:11px;color:#555;">${item.seller}</span>
                    </li>`;
                    }).join('')}
                </ul>
            </div>`;
        }

        html += `
            <div style="border-top:1px solid #383838;padding-top:12px;margin-top:4px;
                        display:flex;gap:8px;justify-content:flex-end;">
                <button id="sj-cfg-cancel" style="${btnStyle('#444','#ccc')}">Cancel</button>
                <button id="sj-cfg-save"   style="${btnStyle('#1a4fa0','#fff')}">💾 Save &amp; Rescan</button>
            </div>`;

        panel.innerHTML = html;
        ov.appendChild(panel);
        document.body.appendChild(ov);

        // Fix indeterminate state (can't be set via HTML attribute)
        panel.querySelectorAll('.sj-cfg-cat-cb').forEach(cb => {
            const cat   = cb.dataset.cat;
            const items = [...panel.querySelectorAll(`.sj-cfg-item-cb[data-cat="${cat}"]`)];
            const n     = items.filter(i => i.checked).length;
            cb.indeterminate = n > 0 && n < items.length;
        });

        /* ── helpers ── */
        function syncCatCb(cat) {
            const items = [...panel.querySelectorAll(`.sj-cfg-item-cb[data-cat="${cat}"]`)];
            const n     = items.filter(i => i.checked).length;
            const catCb = panel.querySelector(`.sj-cfg-cat-cb[data-cat="${cat}"]`);
            if (!catCb) return;
            catCb.checked       = n === items.length;
            catCb.indeterminate = n > 0 && n < items.length;
        }

        function applyDefaults() {
            panel.querySelectorAll('.sj-cfg-item-cb').forEach(cb => {
                cb.checked = !DEFAULT_OFF_CATS.has(cb.dataset.cat);
            });
            panel.querySelectorAll('.sj-cfg-cat-cb').forEach(cb => {
                cb.checked       = !DEFAULT_OFF_CATS.has(cb.dataset.cat);
                cb.indeterminate = false;
            });
        }

        /* ── events ── */
        panel.querySelectorAll('.sj-cfg-cat-cb').forEach(catCb =>
            catCb.addEventListener('change', () => {
                panel.querySelectorAll(`.sj-cfg-item-cb[data-cat="${catCb.dataset.cat}"]`)
                     .forEach(cb => { cb.checked = catCb.checked; });
                catCb.indeterminate = false;
            })
        );

        panel.querySelectorAll('.sj-cfg-item-cb').forEach(itemCb =>
            itemCb.addEventListener('change', () => syncCatCb(itemCb.dataset.cat))
        );

        panel.querySelector('#sj-cfg-reset').addEventListener('click', applyDefaults);

        panel.querySelector('#sj-cfg-cancel').addEventListener('click', removePanel);

        panel.querySelector('#sj-cfg-save').addEventListener('click', () => {
            // Build excludedIds from unchecked item checkboxes
            const excludedIds = [...panel.querySelectorAll('.sj-cfg-item-cb:not(:checked)')]
                .map(cb => Number(cb.dataset.id));
            saveConfig({ excludedIds });
            removePanel();
            fetchAndShow();   // rescan with new config
        });

        ov.addEventListener('click', e => { if (e.target === ov) removePanel(); });
        panel.querySelector('#sj-cfg-close').addEventListener('click', removePanel);
    }

    // ==================== Fetch & Display ====================
    async function fetchAndShow() {
        const key = getApiKey();
        const loading = showLoading();
        try {
            const matches = await fetchAllMatchingInventory(key);
            loading.remove();
            showResults(matches);
        } catch (err) {
            loading.remove();
            if (/API error/i.test(err.message)) {
                showApiKeyModal(`⚠ ${err.message} — please re-enter your key.`);
            } else {
                console.error('[Sell Junk]', err);
                alert(`[Sell Junk] Unexpected error:\n${err.message}`);
            }
        }
    }

    // ==================== API Key Modal ====================
    function showApiKeyModal(errorMsg = '') {
        removePanel();
        const currentKey = getApiKey();
        const masked = currentKey
            ? `${currentKey.slice(0, 6)}••••••••••${currentKey.slice(-4)}`
            : '';

        const ov = document.createElement('div');
        ov.id = 'sell-junk-panel-overlay';
        ov.style.cssText = overlayStyle();

        const modal = document.createElement('div');
        modal.style.cssText = [boxStyle(), 'width:440px', 'max-width:92vw', 'padding:22px 24px'].join(';');

        modal.innerHTML = `
            <div style="font-size:15px;font-weight:bold;margin-bottom:10px;
                        border-bottom:1px solid #383838;padding-bottom:10px;">
                🗑️ Sell Junk — API Key Setup
            </div>
            <p style="font-size:12px;color:#888;margin:0 0 12px;">
                A <strong>Limited Access</strong> key with <strong>Inventory</strong>
                permission is required.<br>
                Create one at
                <a href="/preferences.php#tab=api" target="_blank"
                   style="color:#4A90E2;">Preferences → API Keys</a>.
            </p>
            ${currentKey ? `
            <p style="font-size:12px;color:#5a8;margin:0 0 10px;">
                ✔ Stored key:
                <code style="background:#222;padding:2px 5px;border-radius:3px;
                             font-size:11px;">${masked}</code>
            </p>` : ''}
            ${errorMsg ? `
            <p style="font-size:12px;color:#e05555;margin:0 0 10px;">${errorMsg}</p>` : ''}
            <label for="sj-api-input"
                   style="font-size:12px;display:block;margin-bottom:5px;">API Key:</label>
            <input id="sj-api-input" type="text" autocomplete="off"
                placeholder="Paste your Limited Access API key here"
                value="${currentKey}"
                style="width:100%;box-sizing:border-box;padding:8px 10px;
                       background:#111;color:#eee;border:1px solid #555;
                       border-radius:4px;font-size:13px;margin-bottom:10px;outline:none;" />
            <div id="sj-modal-status"
                 style="font-size:12px;min-height:18px;margin-bottom:12px;"></div>
            <div style="display:flex;gap:8px;justify-content:flex-end;">
                <button id="sj-modal-cancel" style="${btnStyle('#555','#ccc')}">Cancel</button>
                <button id="sj-modal-save"   style="${btnStyle('#1a4fa0','#fff')}">
                    Save &amp; Scan Inventory
                </button>
            </div>`;

        ov.appendChild(modal);
        document.body.appendChild(ov);

        setTimeout(() => {
            const inp = document.getElementById('sj-api-input');
            if (inp) { inp.focus(); inp.select(); }
        }, 50);

        ov.addEventListener('click', e => { if (e.target === ov) removePanel(); });
        modal.querySelector('#sj-modal-cancel').addEventListener('click', removePanel);

        modal.querySelector('#sj-modal-save').addEventListener('click', async () => {
            const key  = document.getElementById('sj-api-input').value.trim();
            const stat = document.getElementById('sj-modal-status');
            const btn  = modal.querySelector('#sj-modal-save');

            if (!key) {
                stat.style.color = '#e05555';
                stat.textContent = '⚠ Please enter an API key.';
                return;
            }

            stat.style.color = '#888';
            stat.textContent = 'Validating…';
            btn.disabled = true;

            try {
                // Quick validation: fetch a single small category via v2
                const resp = await fetch(
                    `${API_BASE}?cat=Alcohol&offset=0&limit=1` +
                    `&key=${encodeURIComponent(key)}&comment=SellJunkValidate`
                );
                if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
                const data = await resp.json();
                if (data.error) throw new Error(`${data.error.code}: ${data.error.error}`);

                setApiKey(key);
                updateButtonIndicator();
                stat.style.color = '#5a8';
                stat.textContent = '✔ Key saved! Scanning inventory…';

                setTimeout(() => {
                    removePanel();
                    fetchAndShow();
                }, 900);

            } catch (err) {
                stat.style.color = '#e05555';
                stat.textContent = `✗ Validation failed: ${err.message}`;
                btn.disabled = false;
            }
        });
    }

    // ==================== Shared Style Helpers ====================
    function overlayStyle() {
        return [
            'position:fixed', 'top:0', 'left:0', 'width:100%', 'height:100%',
            'background:rgba(0,0,0,0.65)', 'z-index:99999',
            'display:flex', 'align-items:center', 'justify-content:center',
        ].join(';');
    }
    function boxStyle() {
        return [
            'background:var(--default-bg-panel-color,#1a1a1a)',
            'color:var(--default-color,#ddd)',
            'border:1px solid var(--default-bg-panel-border-color,#3a3a3a)',
            'border-radius:6px',
            'box-shadow:0 6px 28px rgba(0,0,0,0.6)',
            'font-family:inherit',
        ].join(';');
    }
    function btnStyle(bg, color) {
        return `cursor:pointer;background:${bg};color:${color};border:none;` +
               `border-radius:4px;padding:5px 12px;font-size:12px;font-family:inherit;`;
    }

    function removePanel() {
        document.getElementById('sell-junk-panel-overlay')?.remove();
    }

    // ==================== Button Indicator ====================
    function updateButtonIndicator() {
        const dot = document.getElementById('sell-junk-key-dot');
        if (dot) dot.style.display = getApiKey() ? 'inline' : 'none';
    }

    // ==================== Button Click ====================
    function handleClick() {
        if (!getApiKey()) {
            showApiKeyModal();
        } else {
            // Always clear any previous session and re-fetch fresh inventory
            clearSession();
            document.getElementById('sell-junk-shop-overlay')?.remove();
            fetchAndShow();
        }
    }

    // ==================== Button Injection ====================
    function injectButton() {
        const linksBar = document.getElementById('top-page-links-list');
        if (!linksBar || document.getElementById('sell-junk-btn')) return;

        const btn = document.createElement('a');
        btn.id = 'sell-junk-btn';
        btn.setAttribute('role', 'button');
        btn.setAttribute('aria-label', 'Sell Junk');
        btn.className = 'events t-clear h c-pointer m-icon line-h24 right last';
        btn.style.cssText = 'cursor:pointer;';

        btn.innerHTML = `
            <span class="icon-wrap svg-icon-wrap">
                <span class="link-icon-svg events">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"
                         width="16" height="16" style="vertical-align:middle;">
                        <path fill="#777"
                            d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0
                               1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1
                               0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5
                               0 0 0 1 0z"/>
                        <path fill="#777"
                            d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2
                               0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1
                               1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1
                               1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0
                               1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/>
                    </svg>
                </span>
            </span>
            <span id="sell-junk-btn-text">Sell Junk</span><span
                id="sell-junk-key-dot"
                title="API key stored — click to scan inventory"
                style="display:none;color:#4CAF50;margin-left:3px;font-size:10px;">●</span>`;

        btn.addEventListener('click', handleClick);

        // Mirror 冰娃.js: insert after last <a> in the bar
        const links = linksBar.querySelectorAll('a');
        const last  = links.length ? links[links.length - 1] : null;
        if (last) last.insertAdjacentElement('afterend', btn);
        else       linksBar.appendChild(btn);

        updateButtonIndicator();
    }

    // ==================== Initialisation ====================
    function waitAndInject(maxAttempts = 30, intervalMs = 400) {
        let attempts = 0;
        const timer = setInterval(() => {
            attempts++;
            if (document.getElementById('top-page-links-list')) {
                clearInterval(timer);
                injectButton();
            } else if (attempts >= maxAttempts) {
                clearInterval(timer);
                console.warn('[Sell Junk] Could not find #top-page-links-list');
            }
        }, intervalMs);
    }

    // Show shop overlay on all matched pages when a session is running.
    // Small delay so the page's own CSS variables are applied before rendering.
    if (getSession()) {
        setTimeout(showShopOverlay, 300);
    }

    // Inject the toolbar button only on item.php
    if (window.location.pathname === '/item.php') {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', waitAndInject);
        } else {
            waitAndInject();
        }
    }

})();