Traderie Tools

A browsing companion for traderie.com with bookmarking, adblock, and Diablo 2 price history

// ==UserScript==
// Filename: traderie-tools.user.js
// ==UserScript==
// @name         Traderie Tools
// @namespace    http://tampermonkey.net/
// @version      2025-05v2
// @description  A browsing companion for traderie.com with bookmarking, adblock, and Diablo 2 price history
// @match        *://*.traderie.com/*
// @grant        GM_xmlhttpRequest
// @connect      raw.githubusercontent.com
// @license MIT
// ==/UserScript==

(function() {
  'use strict';

  /**********************
   * Traderie Adblocker *
   **********************/
  const STORAGE_KEY = 'traderie-adblock-groups';
  const ENABLED_KEY = 'traderie-adblock-enabled';
  let adBlockObserver = null;

  const SELECTOR_GROUPS = {
    google: ['.GoogleActiveViewElement', '[id^="google_ads_iframe"]', 'iframe[src*="googlesyndication"]', 'iframe[src*="2mdn"]'],
    generic: ['[id*="anchor"]', '[id*="ad_unit"]', '[data-ad^="leaderboard-"]', 'div[data-ad="left-rail-3"]'],
    video: ['iframe[src*="anyclip"]', 'video[id^="ac-lre-vjs-"]', '.ac-player-wrapper'],
    styled: ['div.sc-gfoqjT.gXykUj', 'div[class*="gfoqjT"]', 'div.ns-08pl9-l-square-gmb', 'div.sc-eyvILC.pvcVG', '.sc-kbousE.dRxaoW', 'div.sc-bpUBKd.jVYraK.cool-slot'],
    tracking: ['script[src*="doubleverify"]'],
    misc: ['a[href="/akrewpro"]', 'span[style*="justify-content: space-between"]', 'svg[style*="left: 160px"]', 'svg[style*="right: 160px"]', 'div.container > div.banner-slider']
  };
  const DEFAULT_ENABLED_GROUPS = Object.keys(SELECTOR_GROUPS);
  const CRITICAL_SELECTORS = ['html', 'head', 'body', 'main', 'nav', '[class*="app"]', '[class*="root"]', '[class*="page"]', '[class*="content"]', '[class*="listing"]', '[class*="trade"]', '[class*="navigation"]', '[class*="header"]', '[class*="menu"]', '[class*="sidebar"]'];
  const BLOCKLIST_EXCEPTIONS = ['.listing-row', '.listing-wrapper', '.listing-container', '#listing-root', '[class*="listing"]', '.sc-etKGGb'];

  function isAdblockEnabled() {
    return localStorage.getItem(ENABLED_KEY) !== 'false';
  }

  function setAdblockEnabled(val) {
    localStorage.setItem(ENABLED_KEY, val ? 'true' : 'false');
  }

  function getEnabledGroups() {
    const stored = localStorage.getItem(STORAGE_KEY);
    return stored ? JSON.parse(stored) : DEFAULT_ENABLED_GROUPS;
  }

  function getActiveSelectors() {
    return getEnabledGroups().flatMap(group => SELECTOR_GROUPS[group] || []);
  }

  function injectAdBlockCSS() {
    const style = document.createElement('style');
    style.id = 'traderie-instant-adblock';
    style.textContent = getActiveSelectors().map(sel => `${sel} { display: none !important; }`).join('\n');
    document.head?.appendChild(style);
  }

  function isCriticalElement(el) {
    if (!el || !el.matches) return true;
    try {
      return CRITICAL_SELECTORS.some(sel => el.matches(sel));
    } catch {
      return true;
    }
  }

  function removeEmptyAncestors(el) {
    let parent = el?.parentNode;
    while (parent && parent !== document.body) {
      if (parent.children.length === 0 && parent.textContent.trim() === '') {
        const next = parent.parentNode;
        parent.remove();
        parent = next;
      } else {
        break;
      }
    }
  }

  function safeRemoveStyled(el) {
    if (!el || !el.parentNode) return false;
    const container = el.closest('[class*="ad"], [id*="ad"], [data-ad]') || el;
    if (BLOCKLIST_EXCEPTIONS.some(skip => container.matches?.(skip))) return false;
    if (isCriticalElement(container)) return false;
    try {
      container.remove();
      removeEmptyAncestors(container);
      return true;
    } catch {
      return false;
    }
  }

  function removeAdsFromContainer(container = document.body) {
    const selectors = getActiveSelectors();
    selectors.forEach(selector => {
      try {
        container.querySelectorAll(selector).forEach(el => {
          safeRemoveStyled(el);
        });
      } catch {}
    });

    container.querySelectorAll('div.sc-gfoqjT.gXykUj').forEach(el => {
      if (el.textContent?.trim() === 'Traderie is supported by ads') {
        const wrapper = el.closest('.sc-eqUAAy.sc-eyvILC');
        if (wrapper && !isCriticalElement(wrapper)) {
          wrapper.remove();
        }
      }
    });
  }

  function performInitialCleanup() {
    setTimeout(() => removeAdsFromContainer(), 500);
    setTimeout(() => removeAdsFromContainer(), 1000);
    setTimeout(() => removeAdsFromContainer(), 1500);
  }

  function startAdblockObserver() {
    adBlockObserver = new MutationObserver(mutations => {
      mutations.forEach(m => {
        m.addedNodes.forEach(n => {
          if (n.nodeType === 1) {
            if (getActiveSelectors().some(sel => n.matches?.(sel))) {
              safeRemoveStyled(n);
            } else {
              removeAdsFromContainer(n);
            }
          }
        });
      });
    });
    adBlockObserver.observe(document.body, { childList: true, subtree: true });
  }

  function initAdblocker() {
    injectAdBlockCSS();
    performInitialCleanup();
    setTimeout(startAdblockObserver, 200);
    setInterval(() => removeAdsFromContainer(), 5000);
  }

  function disableAdblocker() {
    document.getElementById('traderie-instant-adblock')?.remove();
    adBlockObserver?.disconnect();
  }

  window.traderieAdblock = {
    enable: () => { setAdblockEnabled(true); initAdblocker(); },
    disable: () => { setAdblockEnabled(false); disableAdblocker(); },
    isEnabled: isAdblockEnabled
  };

  /*******************************
   * Rune Pricing Functionality *
   *******************************/
  const PRICE_URL = 'https://raw.githubusercontent.com/wguDataNinja/TraderieTools/main/rune_prices.json';
  const RUNE_ENABLED_KEY = 'traderie-rune-pricing-enabled';

  let runePrices = null;
  let runeObserver = null;

  function isRunePricingEnabled() {
    return localStorage.getItem(RUNE_ENABLED_KEY) === 'true';
  }

  function setRunePricingEnabled(val) {
    localStorage.setItem(RUNE_ENABLED_KEY, val ? 'true' : 'false');
  }

  function getServerSlug() {
    const p = new URLSearchParams(window.location.search);
    const plat = (p.get('prop_Platform') || 'pc').toLowerCase();
    const mode = p.get('prop_Mode') === 'hardcore' ? 'hc' : 'sc';
    const lad = p.get('prop_Ladder') === 'true' ? 'l' : 'nl';
    return `${plat}_${mode}_${lad}`;
  }

  function parseRune(el) {
    if (!el) return null;
    const c = el.cloneNode(true);
    Array.from(c.children).forEach(ch => c.removeChild(ch));
    const m = c.textContent.trim().match(/(\d+)\s*[xX]\s*(.+)/);
    return m ? { quantity: +m[1], item: m[2].trim() } : null;
  }

  function parseAskGroups(container) {
    const lines = [...container.querySelectorAll('.price-line, .tooltiptext .price-line')];
    const groups = []; let curr = [];
    lines.forEach((ln, i) => {
      const txt = ln.textContent.trim().toUpperCase();
      const or = txt === 'OR';
      const items = [...ln.querySelectorAll('a')].map(parseRune).filter(Boolean);
      if (items.length) curr.push(...items);
      if (or || (i + 1 < lines.length && lines[i + 1].textContent.trim().toUpperCase() === 'OR')) {
        if (curr.length) { groups.push({ items: curr, element: ln }); curr = []; }
      }
    });
    if (curr.length) groups.push({ items: curr, element: lines[lines.length - 1] });
    return groups;
  }

  function buildTooltipText(offer, asks, prices, slug) {
    const val = r => prices[slug]?.[r.item]?.ist_value ?? null;
    const oVal = val(offer); if (oVal == null) return '';
    const askLines = asks.map(r => {
      const v = val(r);
      return `${r.quantity} x ${r.item} (${v != null ? (r.quantity * v).toFixed(2) : '--'} Ist)`;
    });
    const total = asks.every(r => val(r) != null) ? asks.reduce((s, r) => s + r.quantity * val(r), 0).toFixed(2) : '--';
    return `Offer: ${offer.quantity} x ${offer.item} (${(offer.quantity * oVal).toFixed(2)} Ist)\n` +
      `Ask: ${askLines.join(' + ')}${total !== '--' ? ` = ${total} Ist` : ''}`;
  }

  function showTooltip(el, txt) {
    let tip = document.getElementById('rune-tooltip');
    if (!tip) { tip = document.createElement('div'); tip.id = 'rune-tooltip'; document.body.appendChild(tip); }
    tip.textContent = txt; tip.style.display = 'block';
    const r = el.getBoundingClientRect(), w = tip.offsetWidth;
    tip.style.top = `${window.scrollY + r.top}px`;
    tip.style.left = `${window.scrollX + r.left - w - 10}px`;
  }

  function hideTooltip() { const t = document.getElementById('rune-tooltip'); if (t) t.style.display = 'none'; }

  function injectPercentAndTooltip(off, group, prices, slug) {
    const container = group.element.querySelector('div:first-child');
    if (!container || container.querySelector('.percent-injected')) return;
    const getVal = r => prices[slug]?.[r.item]?.ist_value ?? null;
    const oV = getVal(off); if (oV == null) return;
    container.style.position = 'relative';
    const allAsk = group.items.every(r => getVal(r) != null);
    const span = document.createElement('span');
    span.className = 'percent-injected';
    span.style.left = '-60px';
    span.textContent = allAsk
      ? (() => {
        const askTotal = group.items.reduce((s, r) => s + r.quantity * getVal(r), 0);
        const diff = ((off.quantity * oV - askTotal) / (off.quantity * oV)) * 100;
        return `${diff >= 0 ? '+' : ''}${Math.round(diff)}%`;
      })()
      : '(--)';
    span.style.color = allAsk
      ? (off.quantity * oV - group.items.reduce((s, r) => s + r.quantity * getVal(r), 0) >= 0
        ? 'rgb(0,200,0)' : 'rgb(220,80,80)')
      : 'gray';
    container.appendChild(span);
    const tipText = buildTooltipText(off, group.items, prices, slug);
    span.addEventListener('mouseenter', () => showTooltip(span, tipText));
    span.addEventListener('mouseleave', hideTooltip);
  }

  function injectAll(prices) {
    const slug = getServerSlug();
    document.querySelectorAll('a.listing-name.selling-listing:not([data-injected])').forEach(a => {
      a.setAttribute('data-injected', 'true');
      const cnt = a.closest('div[class*="sc-eqUAAy"]');
      if (!cnt) return;
      const off = parseRune(a);
      if (!off || prices[slug]?.[off.item]?.ist_value == null) return;
      parseAskGroups(cnt).forEach(g => injectPercentAndTooltip(off, g, prices, slug));
    });
  }

  function fetchRunePrices(cb) {
    GM_xmlhttpRequest({
      method: 'GET',
      url: PRICE_URL,
      onload(resp) {
        try {
          cb(JSON.parse(resp.responseText));
        } catch (e) {
          console.error('Failed to parse rune prices', e);
        }
      },
      onerror(err) {
        console.error('Failed to fetch rune prices', err);
      }
    });
  }

  function setupRuneObserver() {
    if (runeObserver) runeObserver.disconnect();
    runeObserver = new MutationObserver(() => {
      if (isRunePricingEnabled() && runePrices) {
        injectAll(runePrices);
      }
    });
    runeObserver.observe(document.body, { childList: true, subtree: true });
    if (isRunePricingEnabled() && runePrices) {
      if (document.readyState !== 'loading') {
        injectAll(runePrices);
      } else {
        document.addEventListener('DOMContentLoaded', () => injectAll(runePrices));
      }
    }
  }

  function startRunePricing() {
    setRunePricingEnabled(true);
    if (!runePrices) {
      fetchRunePrices(prices => {
        runePrices = prices;
        setupRuneObserver();
      });
    } else {
      setupRuneObserver();
    }
  }

  function stopRunePricing() {
    setRunePricingEnabled(false);
    runeObserver?.disconnect();
    runeObserver = null;
    document.querySelectorAll('.percent-injected').forEach(e => e.remove());
    document.querySelectorAll('[data-injected]').forEach(e => e.removeAttribute('data-injected'));
    hideTooltip();
  }

  /***********************
   * UI & Bookmarking   *
   ***********************/
  const font = document.createElement('link');
  font.href = 'https://fonts.googleapis.com/css2?family=Alegreya+Sans&display=swap';
  font.rel = 'stylesheet';
  document.head.appendChild(font);

  const style = document.createElement('style');
  style.textContent = `
    .traderie-tools {
      position: fixed;
      top: 120px;
      left: 20px;
      width: 300px;
      background: #202224;
      color: #e4e6eb;
      border-radius: 20px;
      box-shadow: 0 0 10px rgba(0,0,0,0.3);
      font-family: 'Alegreya Sans', sans-serif;
      z-index: 9999;
      resize: both;
      overflow: auto;
    }
    .tools-header {
      background: #2a2b2e;
      padding: 10px 14px;
      border-radius: 20px 20px 0 0;
      cursor: move;
      display: flex;
      justify-content: space-between;
      align-items: center;
      font-size: 17px;
      font-weight: bold;
      user-select: none;
    }
    .tools-toggle {
      cursor: pointer;
      background: none !important;
      border: none;
      color: inherit;
      font-size: 18px;
    }
    .tools-toggle:hover {
      background: none !important;
    }
    .tools-content {
      padding: 12px 14px;
      display: none;
    }
    .tools-content.show {
      display: block;
    }
    .tab-buttons {
      display: flex;
      gap: 6px;
      margin-bottom: 10px;
    }
    .tab-buttons button {
      flex: 1;
      padding: 6px;
      border: none;
      border-radius: 12px;
      background: #2f3135;
      color: #e4e6eb;
      cursor: pointer;
      font-family: inherit;
    }
    .tab-buttons button.active {
      background: #444;
      font-weight: bold;
    }
    .tab-pane {
      display: none;
    }
    .tab-pane.active {
      display: block;
    }
    .tools-options {
      display: flex !important;
      flex-direction: column !important;
      gap: 6px !important;
      margin-bottom: 12px !important;
      align-items: flex-start !important;
    }
    .tools-options label {
      display: flex !important;
      align-items: center !important;
      gap: 6px !important;
      width: auto !important;
      text-align: left !important;
      font-size: 14px;
      margin: 0 !important;
      padding: 0 !important;
    }
    .tools-options input[type="checkbox"] {
      margin: 0 !important;
      padding: 0 !important;
      flex: 0 0 auto !important;
      width: auto !important;
    }
    .bookmark-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      cursor: pointer;
      margin-bottom: 6px;
      font-weight: normal;
      font-size: 14px;
    }
    .bookmark-header .arrow {
      transition: transform 0.2s ease;
    }
    .bookmark-header.expanded .arrow {
      transform: rotate(0deg);
    }
    .bookmark-header.collapsed .arrow {
      transform: rotate(-90deg);
    }
    .bookmark-section {
      display: none;
      flex-direction: column;
      gap: 6px;
      margin-left: 10px;
    }
    .bookmark-label {
      font-weight: bold;
      margin-top: 10px;
      margin-left: 10px;
    }
    .bookmark-item {
      display: flex;
      align-items: center;
      background: #2f3135;
      padding: 4px 10px;
      border-radius: 6px;
      font-size: 14px;
      justify-content: space-between;
    }
    .bookmark-item span.text {
      flex-grow: 1;
      cursor: pointer;
      margin-right: 10px;
    }
    .edit-btn, .delete-btn {
      color: #fff;
      font-size: 13px;
      margin-left: 6px;
      cursor: pointer;
    }
    .delete-btn {
      font-weight: bold;
    }
    .bookmark-edit-input {
      width: 100%;
      padding: 2px 6px;
      background: #444;
      color: #fff;
      border: none;
      border-radius: 4px;
    }
    .bookmark-name-input {
      width: 100%;
      padding: 6px;
      border-radius: 6px;
      background: #333;
      color: #fff;
      border: none;
      margin-bottom: 8px;
      display: none;
    }
    #rune-tooltip {
      position: absolute;
      background: #222;
      color: #fff;
      padding: 6px;
      border-radius: 4px;
      font-size: 12px;
      white-space: pre;
      z-index: 9999;
      max-width: 300px;
      display: none;
    }
    .percent-injected {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      width: 50px;
      text-align: right;
      font-size: 14px;
      font-family: 'Alegreya Sans', sans-serif;
      white-space: nowrap;
    }
    .traderie-tools.collapsed {
      height: auto !important;
      overflow: hidden !important;
      resize: none !important;
    }

    .traderie-tools.collapsed .tools-content {
      display: none !important;
    }
  `;
  document.head.appendChild(style);

  const STORAGE_KEY_OPEN = 'traderieAppOpen';
  const STORAGE_BOOKMARKS = 'traderieBookmarks';
  const STORAGE_SIZE = 'traderieAppSize';
  const STORAGE_POSITION = 'traderieAppPosition';
  const STORAGE_BOOKMARKS_OPEN = 'traderieBookmarksOpen';

  const panel = document.createElement('div');
  panel.className = 'traderie-tools';
  panel.innerHTML = `
    <div class="tools-header" id="dragHandle">
      <span>🔖 Traderie Tools</span>
      <button class="tools-toggle" id="expandBtn">➕</button>
    </div>
    <div class="tools-content" id="panelContent">
      <div class="tab-buttons">
        <button class="tab-btn active" data-tab="mainTab">Bookmarks</button>
        <button class="tab-btn" data-tab="optionsTab">Options</button>
      </div>
      <div id="mainTab" class="tab-pane active">
        <div class="bookmark-header collapsed" id="bookmarkToggle">
          <span>
            <svg class="arrow" stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 448 512" height="1em" width="1em">
              <path d="M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z"></path>
            </svg> Bookmarks
          </span>
          <button id="saveSearchBtn" style="background: none; border: none; color: #e4e6eb; font-size: 14px; cursor: pointer;">+ Add to bookmarks</button>
        </div>
        <input id="bookmarkName" class="bookmark-name-input" placeholder="Name bookmark...">
        <div id="bookmarkSection" class="bookmark-section">
          <div class="bookmark-label">Listings</div>
          <div id="listingList"></div>
          <div class="bookmark-label">Searches</div>
          <div id="searchList"></div>
        </div>
      </div>
      <div id="optionsTab" class="tab-pane">
        <div class="tools-options">
          <label><input type="checkbox" id="cb1"> Remove Ads</label>
          <label><input type="checkbox" id="cb2"> Inject Rune Pricing</label>
        </div>
      </div>
    </div>
  `;
  document.body.appendChild(panel);

  const saveSize = () => {
    localStorage.setItem(STORAGE_SIZE, JSON.stringify({
      width: panel.style.width,
      height: panel.style.height
    }));
  };
  const loadSize = () => {
    const size = JSON.parse(localStorage.getItem(STORAGE_SIZE) || '{}');
    if (size.width) panel.style.width = size.width;
    if (size.height) panel.style.height = size.height;
  };

  const savePosition = () => {
    localStorage.setItem(STORAGE_POSITION, JSON.stringify({
      left: panel.style.left,
      top: panel.style.top
    }));
  };
  const loadPosition = () => {
    const pos = JSON.parse(localStorage.getItem(STORAGE_POSITION) || '{}');
    if (pos.left) panel.style.left = pos.left;
    if (pos.top) panel.style.top = pos.top;
  };

  const saveBookmarkToggle = (state) => {
    localStorage.setItem(STORAGE_BOOKMARKS_OPEN, JSON.stringify(state));
  };
  const loadBookmarkToggle = () => {
    return JSON.parse(localStorage.getItem(STORAGE_BOOKMARKS_OPEN) || 'false');
  };

  loadSize();
  loadPosition();

  const expandBtn = document.getElementById('expandBtn');
  const content = document.getElementById('panelContent');
  const saveState = () => localStorage.setItem(STORAGE_KEY_OPEN, content.classList.contains('show'));
  const loadState = () => localStorage.getItem(STORAGE_KEY_OPEN) === 'true';

  if (loadState()) {
    content.classList.add('show');
    expandBtn.textContent = '➖';
  }

    expandBtn.onclick = () => {
      const isCollapsed = panel.classList.toggle('collapsed');
      expandBtn.textContent = isCollapsed ? '➕' : '➖';
      saveState();
    };

  document.querySelectorAll('.tab-btn').forEach(btn => {
    btn.onclick = () => {
      document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
      document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
      btn.classList.add('active');
      document.getElementById(btn.dataset.tab).classList.add('active');
    };
  });

  const bookmarkToggle = document.getElementById('bookmarkToggle');
  const bookmarkSection = document.getElementById('bookmarkSection');
  bookmarkToggle.addEventListener('click', () => {
    const expanded = bookmarkToggle.classList.toggle('expanded');
    bookmarkToggle.classList.toggle('collapsed', !expanded);
    bookmarkSection.style.display = expanded ? 'flex' : 'none';
    saveBookmarkToggle(expanded);
  });

  if (loadBookmarkToggle()) {
    bookmarkToggle.classList.add('expanded');
    bookmarkToggle.classList.remove('collapsed');
    bookmarkSection.style.display = 'flex';
  }

  const getBookmarks = () => JSON.parse(localStorage.getItem(STORAGE_BOOKMARKS) || '[]');
  const saveBookmarks = (bms) => localStorage.setItem(STORAGE_BOOKMARKS, JSON.stringify(bms));

  const createEditButton = (callback) => {
    const btn = document.createElement('span');
    btn.className = 'edit-btn';
    btn.textContent = '✎';
    btn.onclick = callback;
    return btn;
  };

  const createDeleteButton = (callback) => {
    const btn = document.createElement('span');
    btn.className = 'delete-btn';
    btn.textContent = '🗑';
    btn.onclick = callback;
    return btn;
  };

  const renderBookmarks = () => {
    const bookmarks = getBookmarks();
    const listings = bookmarks.filter(b => b.type === 'listing');
    const searches = bookmarks.filter(b => b.type === 'search');
    const listingList = document.getElementById('listingList');
    const searchList = document.getElementById('searchList');
    listingList.innerHTML = '';
    searchList.innerHTML = '';

    const makeItem = (b, idx, all) => {
      const item = document.createElement('div');
      item.className = 'bookmark-item';

      const text = document.createElement('span');
      text.className = 'text';
      text.textContent = b.name;
      text.onclick = () => window.location.href = b.url;

      const edit = createEditButton(() => {
        const input = document.createElement('input');
        input.className = 'bookmark-edit-input';
        input.value = b.name;
        input.onkeypress = (e) => {
          if (e.key === 'Enter') {
            all[idx].name = input.value.trim();
            saveBookmarks(all);
            renderBookmarks();
          }
        };
        item.innerHTML = '';
        item.appendChild(input);
        input.focus();
        input.select();
      });

      const del = createDeleteButton(() => {
        const index = all.findIndex(x => x.name === b.name && x.url === b.url && x.type === b.type);
        if (index !== -1) {
          all.splice(index, 1);
          saveBookmarks(all);
          renderBookmarks();
        }
      });

      item.appendChild(text);
      item.appendChild(edit);
      item.appendChild(del);
      return item;
    };

    listings.forEach((b, i) => listingList.appendChild(makeItem(b, i, bookmarks)));
    searches.forEach((b, i) => searchList.appendChild(makeItem(b, i + listings.length, bookmarks)));
  };

  renderBookmarks();

  const input = document.getElementById('bookmarkName');
  const saveSearchBtn = document.getElementById('saveSearchBtn');

  saveSearchBtn.onclick = () => {
    input.style.display = 'block';
    input.value = '';
    input.focus();
    input.select();
  };

  input.onkeypress = (e) => {
    if (e.key === 'Enter') {
      const name = input.value.trim();
      if (!name) return;
      const url = window.location.href;
      const isSearch = !url.includes('/listing');
      const bookmarks = getBookmarks();
      bookmarks.push({
        type: isSearch ? 'search' : 'listing',
        name,
        url
      });
      saveBookmarks(bookmarks);
      renderBookmarks();
      input.style.display = 'none';
      if (!bookmarkSection.style.display || bookmarkSection.style.display === 'none') {
        bookmarkToggle.classList.add('expanded');
        bookmarkToggle.classList.remove('collapsed');
        bookmarkSection.style.display = 'flex';
        saveBookmarkToggle(true);
      }
    }
  };

  // Adblock & Rune checkbox logic
  const adBox = document.getElementById('cb1');
  const runeBox = document.getElementById('cb2');

  adBox.checked = window.traderieAdblock.isEnabled();
  runeBox.checked = isRunePricingEnabled();

  if (adBox.checked) window.traderieAdblock.enable(); else window.traderieAdblock.disable();
  if (runeBox.checked) startRunePricing();

  adBox.addEventListener('change', () => {
    if (adBox.checked) window.traderieAdblock.enable(); else window.traderieAdblock.disable();
  });
  runeBox.addEventListener('change', () => {
    if (runeBox.checked) startRunePricing(); else stopRunePricing();
  });

  // Drag & resize persistence
  const dragHandle = document.getElementById('dragHandle');
  let isDragging = false, offsetX = 0, offsetY = 0;

  dragHandle.addEventListener('mousedown', (e) => {
    isDragging = true;
    offsetX = e.clientX - panel.offsetLeft;
    offsetY = e.clientY - panel.offsetTop;
    e.preventDefault();
  });

  document.addEventListener('mousemove', (e) => {
    if (isDragging) {
      panel.style.left = `${e.clientX - offsetX}px`;
      panel.style.top = `${e.clientY - offsetY}px`;
    }
  });

  document.addEventListener('mouseup', () => {
    if (isDragging) savePosition();
    isDragging = false;
  });

  panel.addEventListener('mouseup', saveSize);
})();