Loot Finder

No color for ≤50k; Orange 50,001–89,999; Red ≥90k; BLUE for Mastercrafted-like items. Fast Scan, legend, progress bar, top-row fix, MC confirmation, mouse-movement shield. Slot-sticky highlights with icon-signature guards and debounced forgetting so colors persist correctly after moves.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Loot Finder
// @namespace    Zega
// @version      1.2.1
// @description  No color for ≤50k; Orange 50,001–89,999; Red ≥90k; BLUE for Mastercrafted-like items. Fast Scan, legend, progress bar, top-row fix, MC confirmation, mouse-movement shield. Slot-sticky highlights with icon-signature guards and debounced forgetting so colors persist correctly after moves.
// @match        https://fairview.deadfrontier.com/onlinezombiemmo/index.php*
// @match        https://www.fairview.deadfrontier.com/onlinezombiemmo/index.php*
// @run-at       document-start
// @all-frames   true
// @grant        none
//
// Changes in 1.2.1:
// - Moving one “identical-looking” item could unhighlight another identical item. Fixed by:
//   (a) Migrating slot memory when indices shift (slot-key refresh).
//   (b) Debouncing mastercrafted “forget” on temporary signature mismatches during drag/reflow.
// ==/UserScript==

(function () {
  'use strict';

  /* ---------- config & constants ---------- */
  const GREEN_MAX  = 50000;
  const ORANGE_MAX = 89999;
  const COLORS = {
    orange: 'rgba(255,165,0,.55)',
    red:    'rgba(220,40,40,.55)',
    blue:   'rgba(60,140,255,.55)',
    border: 'rgba(255,255,255,.95)'
  };

  /* ---------- storage keys ---------- */
  const LS_KEY_TYPES_V2 = 'df_scrap_color_types_v2'; // key: `${type}||${iconSig}` -> 'orange'|'red'
  const LS_KEY_ENABLED  = 'df_scrap_enabled_v1';
  const LS_KEY_MC_SLOTS = 'df_mc_slots_v3';         // { [pageKey]: { [slotKey]: {sig} } }
  const SS_KEY_SLOT_COL = 'df_slot_colors_v2';      // sessionStorage: { [pageKey]: { [slotKey]: {sig, type, col, val} } }

  // retire old broad type memory
  try { localStorage.removeItem('df_scrap_color_types_v1'); } catch {}

  /* ---------- page key (separate per DF page) ---------- */
  const PAGE_KEY = (()=>{ try{
    const u = new URL(location.href);
    const gp = new URLSearchParams(u.search).get('page') || '';
    return u.pathname + '?page=' + gp;
  }catch{ return location.pathname; } })();

  /* ---------- tiny JSON helpers ---------- */
  const jget  = (s,k,def)=>{ try{ const v=s.getItem(k); return v?JSON.parse(v):def; }catch{ return def; } };
  const jset  = (s,k,v)=>{ try{ s.setItem(k, JSON.stringify(v)); }catch{} };

  /* ---------- enable/disable ---------- */
  function loadEnabled(){ try{ const v = localStorage.getItem(LS_KEY_ENABLED); return v==null? true : v==='1'; }catch{ return true; } }
  function saveEnabled(v){ try{ localStorage.setItem(LS_KEY_ENABLED, v?'1':'0'); }catch{} }
  let ENABLED = loadEnabled();

  /* ---------- memories ---------- */
  const COLOR_TYPES = jget(localStorage, LS_KEY_TYPES_V2, {}); // `${type}||${sig}` -> 'orange'|'red'
  const ALL_MC      = jget(localStorage, LS_KEY_MC_SLOTS, {});
  if (!ALL_MC[PAGE_KEY]) ALL_MC[PAGE_KEY] = {};
  const MC_SLOTS = ALL_MC[PAGE_KEY]; // slotKey -> { sig }
  const saveMc   = ()=>{ ALL_MC[PAGE_KEY] = MC_SLOTS; jset(localStorage, LS_KEY_MC_SLOTS, ALL_MC); };

  const ALL_SLOT_COL = jget(sessionStorage, SS_KEY_SLOT_COL, {});
  if (!ALL_SLOT_COL[PAGE_KEY]) ALL_SLOT_COL[PAGE_KEY] = {};
  const SLOT_COL = ALL_SLOT_COL[PAGE_KEY]; // slotKey -> { sig, type, col, val }
  const saveSlot = ()=>{ ALL_SLOT_COL[PAGE_KEY] = SLOT_COL; jset(sessionStorage, SS_KEY_SLOT_COL, ALL_SLOT_COL); };

  /* ---------- mouse + scan gate ---------- */
  let mouseX=0, mouseY=0, SCANNING=false;
  const onMove = e => { if (!SCANNING){ mouseX=e.clientX; mouseY=e.clientY; } };
  document.addEventListener('mousemove', onMove, true);
  document.addEventListener('mouseover', onMove, true);

  /* ---------- tooltip parsing ---------- */
  const htmlToText = s => { const t=document.createElement('textarea'); t.innerHTML=s||''; return t.value.replace(/\u00A0/g,' ').replace(/<br\s*\/?>/gi,'\n').replace(/<[^>]+>/g,' '); };
  function readTooltipFromDoc(doc){
    const ids=['dhtmltooltip','tiplayer','toolTip','tooltip'];
    for(const id of ids){
      const el = doc.getElementById && doc.getElementById(id);
      if(el && el.offsetParent !== null && (el.textContent||'').length) return el.innerHTML||el.textContent;
    }
    const cand = Array.from(doc.querySelectorAll('div')).find(d =>
      d.offsetParent !== null && (d.textContent||'').length < 800 && /Scrap\s*(Price|Value)/i.test(d.textContent||'')
    );
    return cand ? (cand.innerHTML||cand.textContent) : null;
  }
  function liveTooltipHTML(){
    let h = readTooltipFromDoc(document); if(h) return h;
    try{ if (window.parent && window.parent!==window){ h=readTooltipFrom(window.parent.document); if(h) return h; } }catch{}
    try{
      const top = window.top||window;
      h=readTooltipFrom(top.document); if(h) return h;
      for(let i=0;i<top.frames.length;i++){ try{ h=readTooltipFrom(top.frames[i].document); if(h) return h; }catch{} }
    }catch{}
    return null;
  }
  function parseScrapStrict(text){
    if(!text) return null;
    const s = htmlToText(text);
    const m = s.match(/Scrap\s*(?:Price|Value)\s*:\s*\$?\s*([0-9][0-9,.\s]*)/i);
    if (!m) return null;
    const n = Number(String(m[1]).replace(/[,.\s]/g,''));
    return Number.isFinite(n) ? n : null;
  }
  function isMastercrafted(html){
    if(!html) return false;
    if (/(color\s*[:=]\s*["']?\s*(?:#?ffff00|#?ff0|yellow))/i.test(html)) return true;
    const txt = htmlToText(html);
    if (/\+\s*\d+\s+(Accuracy|Critical|Reload|Reload Speed|Recoil|Damage|Fortitude|Agility|Dodging|Blocking|Running|Searching|Looting|SPR|DPS)/i.test(txt)) return true;
    return false;
  }

  /* ---------- geometry/helpers ---------- */
  const isSquareish = r => r && r.width>=35 && r.width<=160 && r.height>=35 && r.height<=160 && (r.width/r.height)>0.75 && (r.width/r.height)<1.33;
  function pickBoxFrom(el){
    let p=el;
    for(let i=0;i<8 && p && p!==document.body;i++,p=p.parentElement){
      const r=p.getBoundingClientRect(); if(isSquareish(r)) return p;
    }
    return el;
  }
  function hasBgImage(node){ if(!node) return false; const bg=getComputedStyle(node).backgroundImage; return !!(bg && bg!=='none'); }
  function boxHasIconFromItem(itemEl){ return !!(itemEl && hasBgImage(itemEl)); }

  // Robust icon signature: image + position + size
  function getIconSig(itemEl){
    try{
      const cs = getComputedStyle(itemEl);
      const img = cs.backgroundImage || '';
      const pos = (cs.backgroundPosition || ((cs.backgroundPositionX||'')+' '+(cs.backgroundPositionY||''))).trim();
      const size = cs.backgroundSize || '';
      return [img, pos, size].join('|');
    }catch{ return null; }
  }

  function colorFor(scrap){
    if (scrap <= GREEN_MAX) return 'none';
    if (scrap <= ORANGE_MAX) return 'orange';
    return 'red';
  }

  /* ---------- slot identity ---------- */
  function computeSlotKey(itemEl){
    const container = itemEl.closest('.playerInv, .inventory, .storage, .invGrid, td, div') || itemEl.parentElement || document.body;
    let list = Array.from(container.querySelectorAll('div.item'));
    let idx  = list.indexOf(itemEl);
    if (idx < 0) { list = Array.from(document.querySelectorAll('div.item')); idx = list.indexOf(itemEl); }
    const tag = (container.tagName||'DIV');
    const id  = (container.id||'').slice(0,40);
    const cls = (container.className||'').toString().split(/\s+/).slice(0,3).join('.');
    return `${tag}#${id}.${cls}::${idx}`;
  }

  /* ---------- MC slot memory ---------- */
  function rememberMaster(itemEl){
    const slotKey = computeSlotKey(itemEl);
    const sig = getIconSig(itemEl) || '';
    itemEl.dataset.dfSlotKey = slotKey;
    itemEl.dataset.dfMaster  = '1';
    MC_SLOTS[slotKey] = { sig };
    saveMc();
  }
  function forgetMasterAtSlot(slotKey){
    if (MC_SLOTS[slotKey]){ delete MC_SLOTS[slotKey]; saveMc(); }
  }

  /* ---------- non-MC slot memory ---------- */
  function rememberSlotColor(itemEl, col, val){
    if (!itemEl) return;
    if (col==='none' || col==='blue') return; // only persist orange/red
    const slotKey = computeSlotKey(itemEl);
    const sig = getIconSig(itemEl) || '';
    const type = (itemEl.dataset && itemEl.dataset.type ? String(itemEl.dataset.type).toLowerCase() : '');
    itemEl.dataset.dfSlotKey = slotKey;
    SLOT_COL[slotKey] = { sig, type, col, val: Number(val)||null };
    saveSlot();
  }
  function slotEntryMatches(itemEl, entry){
    if (!entry) return false;
    const sigNow = getIconSig(itemEl) || '';
    const typeNow = (itemEl.dataset && itemEl.dataset.type ? String(itemEl.dataset.type).toLowerCase() : '');
    if (entry.sig && entry.sig !== sigNow) return false;
    if (entry.type && entry.type !== typeNow) return false;
    return true;
  }

  /* ---------- painter ---------- */
  function clearBox(box){
    if (!box) return;
    box.style.outline=''; box.style.boxShadow=''; box.style.borderRadius='';
    box.removeAttribute('data-df-scrap-painted');
    box.removeAttribute('data-df-scrap-color');
    const pill = box.querySelector('.df-scrap-pill'); if (pill) pill.remove();
  }
  function paintBox(box, scrap, color){
    if (!box) return;
    if (color === 'none'){ clearBox(box); return; }
    if (getComputedStyle(box).position === 'static') box.style.position='relative';
    box.dataset.dfScrapPainted='1';
    box.dataset.dfScrapValue  = String(scrap);
    box.dataset.dfScrapColor  = color;
    const col = COLORS[color];
    box.style.outline      = `2px solid ${COLORS.border}`;
    box.style.boxShadow    = `0 0 0 4px ${col} inset, 0 0 10px 0 ${col}`;
    box.style.borderRadius = '6px';
    let tag = box.querySelector('.df-scrap-pill');
    if(!tag){ tag = document.createElement('div'); tag.className='df-scrap-pill'; Object.assign(tag.style,{display:'none'}); box.appendChild(tag); }
  }

  /* ---------- migrate slot keys when indices shift ---------- */
  function refreshSlotKeys(){
    document.querySelectorAll('div.item').forEach(item=>{
      const oldKey = item.dataset.dfSlotKey;
      const newKey = computeSlotKey(item);
      if (!oldKey){ item.dataset.dfSlotKey = newKey; return; }
      if (oldKey === newKey) return;

      const sig = getIconSig(item) || '';

      // Move MC memory if signature still matches
      const mc = MC_SLOTS[oldKey];
      if (mc && mc.sig === sig){
        delete MC_SLOTS[oldKey];
        MC_SLOTS[newKey] = mc;
        item.dataset.dfMaster = '1';
        saveMc();
      }

      // Move non-MC slot color if signature still matches
      const sc = SLOT_COL[oldKey];
      if (sc && sc.sig === sig){
        delete SLOT_COL[oldKey];
        SLOT_COL[newKey] = sc;
        saveSlot();
      }

      item.dataset.dfSlotKey = newKey;
    });
  }

  /* ---------- re-appliers ---------- */
  const MC_MISMATCH_SINCE = {}; // slotKey -> timestamp
  const MC_FORGET_DELAY = 800;  // ms

  function paintAllMasters(){
    document.querySelectorAll('div.item').forEach(item=>{
      const slotKey = computeSlotKey(item);               // use current key
      const sigNow  = getIconSig(item) || '';
      const entry   = MC_SLOTS[slotKey];

      // if nothing stored for this slot, clear debounce and skip
      if (!entry){ delete MC_MISMATCH_SINCE[slotKey]; return; }

      // If icon temporarily blank/hidden during drag, don't forget yet
      const box = pickBoxFrom(item);
      const rect = box && box.getBoundingClientRect();
      const visible = !!(rect && rect.width>0 && rect.height>0);
      if (!visible || !sigNow){ return; }

      // Debounced forgetting when signature differs
      if (entry.sig !== sigNow){
        const now = performance.now();
        if (!MC_MISMATCH_SINCE[slotKey]){ MC_MISMATCH_SINCE[slotKey] = now; return; }
        if (now - MC_MISMATCH_SINCE[slotKey] < MC_FORGET_DELAY) return;
        delete MC_MISMATCH_SINCE[slotKey];
        forgetMasterAtSlot(slotKey);
        return;
      }
      delete MC_MISMATCH_SINCE[slotKey];

      // Paint if matches
      item.dataset.dfMaster = '1';
      item.dataset.dfSlotKey = slotKey;
      if (rect && isSquareish(rect) && boxHasIconFromItem(item)){
        const val = Number(item.dataset.dfScrapValue) || GREEN_MAX;
        paintBox(box, val, 'blue');
      }
    });
  }

  function paintAllBySlot(){
    document.querySelectorAll('div.item').forEach(item=>{
      const slotKey = computeSlotKey(item);
      const entry = SLOT_COL[slotKey];
      if (!entry) return;
      if (!slotEntryMatches(item, entry)){ delete SLOT_COL[slotKey]; saveSlot(); return; }
      const box = pickBoxFrom(item);
      const rect = box && box.getBoundingClientRect();
      if (rect && isSquareish(rect) && boxHasIconFromItem(item)){
        const val = Number(item.dataset.dfScrapValue);
        paintBox(box, Number.isFinite(val)? val : (entry.val||GREEN_MAX), entry.col);
      }
    });
  }

  function paintAllByType(type, color){
    if (!type || color==='none') return;
    const key = String(type).toLowerCase();
    document.querySelectorAll('div.item').forEach(item=>{
      const sk = computeSlotKey(item);
      if (MC_SLOTS[sk]) return;                            // don't override MC
      if (SLOT_COL[sk] && slotEntryMatches(item, SLOT_COL[sk])) return; // nor explicit slot color

      const t = item.dataset && item.dataset.type ? item.dataset.type.toLowerCase() : '';
      if (t !== key) return;

      const box = pickBoxFrom(item);
      const rect = box && box.getBoundingClientRect();
      if (rect && isSquareish(rect) && boxHasIconFromItem(item)){
        const val = Number(item.dataset.dfScrapValue) || GREEN_MAX;
        paintBox(box, val, color);
      }
    });
  }

  /* ---------- hover-driven loop ---------- */
  function tick(){
    try{
      if (!ENABLED || SCANNING) return;
      const tipHTML = liveTooltipHTML(); if(!tipHTML) return;
      const target = document.elementFromPoint(mouseX, mouseY); if(!target) return;
      const itemEl = target.closest && target.closest('div.item'); if(!itemEl) return;

      const scrap = parseScrapStrict(tipHTML); if(scrap==null) return;

      const box = pickBoxFrom(itemEl);
      const rect = box && box.getBoundingClientRect();
      if(!(rect && isSquareish(rect) && boxHasIconFromItem(itemEl))) return;

      const master = isMastercrafted(tipHTML);
      itemEl.dataset.dfScrapValue = String(scrap);

      const sig = getIconSig(itemEl) || '';
      const type = (itemEl.dataset && itemEl.dataset.type) ? itemEl.dataset.type.toLowerCase() : '';

      if (master){
        rememberMaster(itemEl);
        paintBox(box, scrap, 'blue');
        const sk = itemEl.dataset.dfSlotKey || computeSlotKey(itemEl);
        if (SLOT_COL[sk]){ delete SLOT_COL[sk]; saveSlot(); }
      } else {
        const col = colorFor(scrap);
        paintBox(box, scrap, col);
        if (col!=='none') rememberSlotColor(itemEl, col, scrap);

        if (col!=='none' && type){
          const tkey = `${type}||${sig}`;
          if (COLOR_TYPES[tkey] !== col){
            COLOR_TYPES[tkey] = col;
            jset(localStorage, LS_KEY_TYPES_V2, COLOR_TYPES);
          }
        }
      }
    }catch{}
  }
  setInterval(tick, 120);

  /* ---------- reapply flow ---------- */
  function reapplyAll(){
    if (!ENABLED) return;
    refreshSlotKeys(); // NEW: keep memories lined up with shifted indices
    paintAllMasters();
    paintAllBySlot();
    Object.entries(COLOR_TYPES).forEach(([k,col])=>{
      if (!col || col==='none') return;
      const [type, sig] = k.split('||');
      document.querySelectorAll('div.item').forEach(item=>{
        const t = (item.dataset && item.dataset.type) ? item.dataset.type.toLowerCase() : '';
        if (t !== (type||'')) return;
        const sk = computeSlotKey(item);
        if (MC_SLOTS[sk]) return;
        if (SLOT_COL[sk] && slotEntryMatches(item, SLOT_COL[sk])) return;
        if ((getIconSig(item)||'') !== (sig||'')) return;

        const box = pickBoxFrom(item);
        const rect = box && box.getBoundingClientRect();
        if (rect && isSquareish(rect) && boxHasIconFromItem(item)){
          const val = Number(item.dataset.dfScrapValue) || GREEN_MAX;
          paintBox(box, val, col);
        }
      });
    });
  }

  /* ---------- debounced observer ---------- */
  let reapplyScheduled=false;
  function scheduleReapply(){
    if (reapplyScheduled) return;
    reapplyScheduled=true;
    requestAnimationFrame(()=>{ reapplyScheduled=false; reapplyAll(); });
  }
  const mo = new MutationObserver(()=>{ try{ scheduleReapply(); }catch{} });

  /* ---------- scan helpers / quick scan ---------- */
  function sleep(ms){ return new Promise(r=>setTimeout(r,ms)); }
  function dispatchMouse(el, type, x, y){
    const ev=new MouseEvent(type,{bubbles:true,cancelable:true,clientX:x,clientY:y,view:window});
    try{ el.dispatchEvent(ev); }catch{}
  }
  async function hoverForTooltip(el, baseWait=20, duringScan=false){
    const r=el.getBoundingClientRect();
    if(!r || r.width===0 || r.height===0) return null;
    let x=Math.max(2,Math.min(window.innerWidth-2,Math.floor(r.left+r.width*0.5)));
    let y=Math.max(2,Math.min(window.innerHeight-2,Math.floor(r.top +r.height*0.6)));
    const target = duringScan ? el : (document.elementFromPoint(x,y) || el);
    dispatchMouse(target,'mouseover',x,y);
    dispatchMouse(target,'mousemove',x,y);
    await sleep(baseWait);
    let best=liveTooltipHTML();
    if(!best || !isMastercrafted(best)){
      for(let k=0;k<2;k++){
        y=Math.min(window.innerHeight-2, y+1+k);
        dispatchMouse(target,'mousemove',x,y);
        await sleep(baseWait+8);
        const h=liveTooltipHTML();
        if ((h && (!best || h.length>best.length)) || isMastercrafted(h)) best=h;
        if (isMastercrafted(best)) break;
      }
    }
    return { html: best };
  }
  async function confirmMastercrafted(el, baseWait=20){
    const first = await hoverForTooltip(el, baseWait, true);
    const tip1 = first && first.html;
    const isMC1 = !!tip1 && isMastercrafted(tip1);
    if (!isMC1) return { tip: tip1, master:false };
    const tip2 = liveTooltipHTML();
    const isMC2 = !!tip2 && isMastercrafted(tip2);
    const s1 = parseScrapStrict(tip1);
    const s2 = parseScrapStrict(tip2);
    const ok = (s1==null || s2==null) ? true : (s1===s2);
    return { tip: tip2||tip1, master: (isMC1 && isMC2 && ok) };
  }

  let scanning=false;
  async function quickScan(updateLabel){
    if (!ENABLED || scanning) return;
    scanning=true; SCANNING=true;

    const items = Array.from(document.querySelectorAll('div.item')).filter(it=>{
      const b=pickBoxFrom(it); const r=b&&b.getBoundingClientRect();
      return r && r.width>0 && r.height>0 && r.bottom>0 && r.top<window.innerHeight;
    });
    const MAX = Math.min(items.length, 120);
    const HOVER=20, PAUSE=2;

    for(let i=0;i<MAX;i++){
      const it=items[i];
      try{
        const { tip, master } = await confirmMastercrafted(it, HOVER);
        if (tip){
          const scrap = parseScrapStrict(tip);
          if (scrap!=null){
            const box = pickBoxFrom(it);
            if (box && boxHasIconFromItem(it)){
              it.dataset.dfScrapValue = String(scrap);
              if (master){
                rememberMaster(it);
                paintBox(box, scrap, 'blue');
                const sk = it.dataset.dfSlotKey || computeSlotKey(it);
                if (SLOT_COL[sk]){ delete SLOT_COL[sk]; saveSlot(); }
              }else{
                const col = colorFor(scrap);
                paintBox(box, scrap, col);
                if (col!=='none') rememberSlotColor(it, col, scrap);

                const sig = getIconSig(it) || '';
                const type = (it.dataset && it.dataset.type) ? it.dataset.type.toLowerCase() : '';
                if (col!=='none' && type){
                  const tkey = `${type}||${sig}`;
                  if (COLOR_TYPES[tkey] !== col){
                    COLOR_TYPES[tkey] = col;
                    jset(localStorage, LS_KEY_TYPES_V2, COLOR_TYPES);
                  }
                }
              }
            }
          }
        }
      }catch{}
      if (updateLabel) updateLabel(`Scanning ${i+1}/${MAX}…`);
      await sleep(PAUSE);
    }

    reapplyAll();
    SCANNING=false; scanning=false;
    if (updateLabel) updateLabel('Scan Items');
  }

  /* ---------- toggle + boot UI ---------- */
  function toggleEnabled(btn){
    ENABLED=!ENABLED; saveEnabled(ENABLED);
    if (ENABLED){
      try{ if (document.body) mo.observe(document.body, {subtree:true, childList:true}); }catch{}
      reapplyAll();
    }else{
      try{ mo.disconnect(); }catch{}
      document.querySelectorAll('[data-df-scrap-painted="1"]').forEach(el=>{
        el.style.outline=''; el.style.boxShadow=''; el.style.borderRadius='';
        el.removeAttribute('data-df-scrap-painted'); el.removeAttribute('data-df-scrap-color');
      });
    }
    if (btn) btn.textContent = ENABLED ? 'Scrap Highlight: ON' : 'Scrap Highlight: OFF';
  }

  document.addEventListener('DOMContentLoaded', ()=>{
    try{ if (ENABLED && document.body) mo.observe(document.body, {subtree:true, childList:true}); }catch{}
    if (ENABLED) reapplyAll();

    // Recalculate after player moves items
    ['dragstart','dragend','drop','mouseup'].forEach(ev =>
      document.addEventListener(ev, ()=>{ scheduleReapply(); }, true)
    );

    const wrap=document.createElement('div');
    Object.assign(wrap.style,{
      position:'fixed', left:'8px', bottom:'8px', zIndex:2147483647,
      display:'flex', gap:'6px', alignItems:'center'
    });

    const toggleBtn=document.createElement('button');
    toggleBtn.textContent = ENABLED ? 'Scrap Highlight: ON' : 'Scrap Highlight: OFF';
    Object.assign(toggleBtn.style,{
      font:'12px system-ui, Arial, sans-serif', padding:'6px 10px',
      background:'#111', color:'#fff', border:'1px solid rgba(255,255,255,.25)',
      borderRadius:'6px', opacity:'0.9', cursor:'pointer'
    });
    toggleBtn.addEventListener('mouseenter',()=>toggleBtn.style.opacity='1');
    toggleBtn.addEventListener('mouseleave',()=>toggleBtn.style.opacity='0.9');
    toggleBtn.addEventListener('click',()=>toggleEnabled(toggleBtn));

    const scanBtn=document.createElement('button');
    scanBtn.textContent='Scan Items';
    Object.assign(scanBtn.style,{
      font:'12px system-ui, Arial, sans-serif', padding:'6px 10px',
      background:'#153b7a', color:'#fff', border:'1px solid rgba(255,255,255,.25)',
      borderRadius:'6px', opacity:'0.9', cursor:'pointer'
    });
    const setScanLabel=(t)=>{ scanBtn.textContent=t; };
    scanBtn.addEventListener('mouseenter',()=>scanBtn.style.opacity='1');
    scanBtn.addEventListener('mouseleave',()=>scanBtn.style.opacity='0.9');
    scanBtn.addEventListener('click', async ()=>{
      if (scanning) return;
      const prev=scanBtn.textContent;
      setScanLabel('Scanning…');
      scanBtn.disabled=true;
      try{ await quickScan(setScanLabel); } finally { scanBtn.disabled=false; setScanLabel(prev); }
    });

    wrap.appendChild(toggleBtn);
    wrap.appendChild(scanBtn);
    document.body.appendChild(wrap);
  });

})();