Greasy Fork is available in English.

Asset Hunter

Search Ripper.Store for assets (DL detection, watchlist, LF post system, etc)

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Asset Hunter
// @namespace    https://github.com/xedinho/Asset-Hunter
// @version      6.4.1
// @description  Search Ripper.Store for assets (DL detection, watchlist, LF post system, etc)
// @author       Xedinho
// @license      MIT
// @match        *://booth.pm/*
// @match        *://*.booth.pm/*
// @match        *://gumroad.com/*
// @match        *://*.gumroad.com/*
// @match        *://jinxxy.com/*
// @match        *://*.jinxxy.com/*
// @match        *://payhip.com/*
// @match        *://itch.io/*
// @match        *://*.itch.io/*
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addValueChangeListener
// @connect      forum.ripper.store
// @connect      raw.githubusercontent.com
// @connect      api.github.com
// @run-at       document-idle
// ==/UserScript==

(function () {
  "use strict";

  // ─── Version & Changelog ──────────────────────────────────────────────────
  const CURRENT_VERSION = "6.4.1";

  const CHANGELOGS = [
    {
      version: "6.4.1",
      fixes: [
        "changelog.fix.frenchTranslations",
      ],
    },
    {
      version: "6.4.0",
      additions: [
        "changelog.add.frenchLang",
        "changelog.add.itchio",
        "changelog.add.siteFilter",
        "changelog.add.startMinimized",
        "changelog.add.urlSearchMode",
        "changelog.add.minimizeHotkey",
        "changelog.add.matchWordsToggle",
      ],
      tips: [
        "changelog.tip.searchWorkaround",
      ],
    },
    {
      version: "6.3.0",
      additions: [
        "changelog.add.bumpTopicPopup",
        "changelog.add.kofiRandomPopup",
      ],
      fixes: [
        "changelog.fix.kofiThreshold",
      ],
    },
    {
      version: "6.2.0",
      additions: [
        "changelog.add.bumpButton",
        "changelog.add.bumpAll",
        "changelog.add.postCooldown",
      ],
      info: [
        "changelog.info.bumpSetting",
      ],
      removals: [
        "changelog.remove.autoUpdate",
      ],
    },
    {
      version: "6.1.2",
      fixes: [
        "changelog.fix.nameParsingDash",
      ],
    },
    {
      version: "6.1.1",
      fixes: [
        "changelog.fix.boothSanitizationRemoved",
        "changelog.fix.previewBtnRemoved",
      ],
    },
    {
      version: "6.1.0",
      fixes: [
        "changelog.fix.boothLink",
      ],
      additions: [
        "changelog.add.changelogPopup",
        "changelog.add.changelogBtn",
      ],
    },
  ];

  // ─── API Endpoints ────────────────────────────────────────────────────────
  const API_URL        = "https://forum.ripper.store/api/search?term={query}&in=posts&matchWords={matchWords}&by=&categories=&searchChildren=false&hasTags=&replies=&repliesFilter=atleast&timeFilter=newer&timeRange=&sortBy=relevance&sortDirection=desc&showAs=topics";
  const TOPIC_API      = "https://forum.ripper.store/api/topic/{tid}";
  const POST_API       = "https://forum.ripper.store/api/v3/topics";
  const POST_REPLY_API = "https://forum.ripper.store/api/v3/topics/{tid}";
  const CONFIG_API     = "https://forum.ripper.store/api/config";
  const SITE_URL       = "https://forum.ripper.store";
  const DONORS_URL     = "https://api.github.com/repos/xedinho/Asset-Hunter/contents/donos.txt";
  const AH_TOPIC_TID   = 108432; // Main Asset Hunter topic for community bump

  // ─── Download Detection Patterns ─────────────────────────────────────────
  const DL_PATTERNS = [
    // Original hosts
    /mega\.nz/i, /mega\.io/i, /mediafire/i, /drive\.google/i, /gofile\.io/i,
    /pixeldrain/i, /anonfiles/i, /anonfile\.la/i, /workupload/i, /1fichier/i,
    /dropbox/i, /onedrive/i, /terabox/i, /bowfile/i,
    // New hosts from community list
    /1cloudfile\.com/i, /archive\.org\/download/i, /app\.bunkrr\.su/i,
    /buzzheavier\.com/i, /clicknupload\.click/i, /cyberfile\.me/i,
    /dailyuploads\.net/i, /datanodes\.to/i, /disk\.yandex\.com/i,
    /fastupload\.io/i, /filebin\.net/i, /fileditch\.com/i,
    /filepost\.io/i, /files\.fm/i, /filetransfer\.io/i,
    /fuckingfast\.net/i, /hexload\.com/i, /mixdrop\.ag/i,
    /send\.cm/i, /terminal\.lc/i, /transfer\.it/i,
    /uploadfile\.pl/i, /uploadhaven\.com/i, /uploadnow\.io/i,
    /wdho\.ru/i, /wetransfer\.com/i, /axfc\.net/i,
    /filemail\.com/i, /sendspace\.com/i, /swisstransfer\.com/i,
    /zippyshare\.day/i,
    // File extensions
    /\.zip\b/i, /\.rar\b/i, /\.7z\b/i,
    // Keywords
    /\bdownload\s*(?:link|here|now|file|this)\b/i,
    /(?:^|\s)dl\s*(?:link|here|:)/im,
    /baixar/i, /descargar/i,
    /\/hidelinks\/r\//i, /🔗[\s]*DL/,
  ];

  // ─── Platform detection ───────────────────────────────────────────────────
  const HOST = location.hostname.replace(/^www\./, "");

  // ─── Booth Adapter ────────────────────────────────────────────────────────
  const BOOTH = {
    getId: () => {
      const m = window.location.pathname.match(/items\/(\d+)/);
      return m ? m[1] : "";
    },
    getName: () => {
      let name = document.title.replace(/\s*[|]\s*BOOTH.*$/i, "").replace(/\s*[-–]\s*BOOTH.*$/i, "").trim();
      if (!name) {
        const el = document.querySelector("h1.u-tpg-title1") || document.querySelector("h1");
        name = el ? el.textContent.trim() : "";
      }
      return name;
    },
    buildQuery: (id, name) => id || name,
    isItemPage: () => /\/items\/\d+/.test(window.location.pathname),
  };

  // ─── Gumroad Adapter ──────────────────────────────────────────────────────
  const GUMROAD = {
    getId: () => {
      const m = window.location.pathname.match(/\/l\/([^/?#]+)/);
      return m ? m[1] : "";
    },
    getName: () => {
      const og = document.querySelector('meta[property="og:title"]');
      if (og && og.getAttribute("content")) {
        return og.getAttribute("content").trim();
      }
      return document.title.replace(/\s*[|]\s*Gumroad.*$/i, "").replace(/\s*[-–]\s*Gumroad.*$/i, "").trim();
    },
    buildQuery: (id, name) => id || name,
    isItemPage: () => /\/l\/[^/?#]+/.test(window.location.pathname),
  };

  // ─── Jinxxy Adapter ───────────────────────────────────────────────────────
  const JINXXY = {
    getId: () => {
      const m = window.location.pathname.match(/^\/[^/]+\/([^/?#]+)/);
      return m ? m[1] : "";
    },
    getName: () => {
      const h1 = document.querySelector("h1");
      if (h1) return h1.textContent.trim();
      const og = document.querySelector('meta[property="og:title"]');
      if (og && og.getAttribute("content")) {
        return og.getAttribute("content").replace(/\s+by\s+.+?\s+on\s+Jinxxy$/i, "").trim();
      }
      return document.title.replace(/\s*[|]\s*Jinxxy.*$/i, "").replace(/\s*[-–]\s*Jinxxy.*$/i, "").trim();
    },
    buildQuery: (id, name) => id || name,
    isItemPage: () => {
      const parts = window.location.pathname.replace(/^\/|\/$/g, "").split("/");
      if (parts.length !== 2) return false;
      const skip = ["market", "my", "about", "terms-of-service", "privacy-policy",
                    "refund-policy", "cart", "search"];
      if (skip.includes(parts[0])) return false;
      return true;
    },
  };

  // ─── Payhip Adapter ───────────────────────────────────────────────────────
  const PAYHIP = {
    getId: () => {
      const m = window.location.pathname.match(/\/b\/([^/?#]+)/);
      return m ? m[1] : "";
    },
    getName: () => {
      const h1 = document.querySelector("h1.font-section-product-name");
      if (h1) return h1.textContent.trim();
      const h1g = document.querySelector("h1");
      if (h1g) return h1g.textContent.trim();
      const og = document.querySelector('meta[property="og:title"]');
      if (og && og.getAttribute("content")) {
        return og.getAttribute("content").trim();
      }
      return document.title.replace(/\s*[|]\s*Payhip.*$/i, "").replace(/\s*[-–]\s*Payhip.*$/i, "").trim();
    },
    buildQuery: (id, name) => id || name,
    isItemPage: () => /\/b\/[^/?#]+/.test(window.location.pathname),
  };

  // ─── itch.io Adapter ──────────────────────────────────────────────────────
  const ITCH = {
    getId: () => {
      const m = window.location.pathname.match(/^\/([^/?#]+)/);
      return m ? m[1] : "";
    },
    getName: () => {
      const og = document.querySelector('meta[property="og:title"]');
      if (og && og.getAttribute("content")) {
        return og.getAttribute("content").replace(/\s+by\s+.+?\s+on\s+itch\.io\s*$/i, "").trim();
      }
      const h1 = document.querySelector("h1.game_title") || document.querySelector(".game_title h1") || document.querySelector("h1");
      if (h1) return h1.textContent.trim();
      return document.title.replace(/\s*[|]\s*itch\.io.*$/i, "").replace(/\s+on itch\.io.*$/i, "").trim();
    },
    buildQuery: (id, name) => name || id,
    isItemPage: () => {
      if (!HOST.endsWith(".itch.io")) return false;
      const m = window.location.pathname.match(/^\/([^/?#]+)/);
      if (!m || !m[1]) return false;
      const skip = ["login", "register", "profile", "dashboard", "search", "tags", "game", "games",
                    "updates", "feed", "purchases", "bundles", "download", "devlog", "community"];
      return !skip.includes(m[1]);
    },
  };

  // ─── Site key helpers ─────────────────────────────────────────────────────
  const SITE_DEFS = [
    { key: "booth",   label: "Booth" },
    { key: "gumroad", label: "Gumroad" },
    { key: "jinxxy",  label: "Jinxxy" },
    { key: "payhip",  label: "Payhip" },
    { key: "itch",    label: "itch.io" },
  ];

  function getCurrentSiteKey() {
    if (HOST === "booth.pm"   || HOST.endsWith(".booth.pm"))   return "booth";
    if (HOST === "gumroad.com"|| HOST.endsWith(".gumroad.com"))return "gumroad";
    if (HOST === "jinxxy.com" || HOST.endsWith(".jinxxy.com")) return "jinxxy";
    if (HOST === "payhip.com" || HOST.endsWith(".payhip.com")) return "payhip";
    if (HOST === "itch.io"    || HOST.endsWith(".itch.io"))    return "itch";
    return null;
  }

  function isSiteEnabled(siteKey) {
    const key = siteKey != null ? siteKey : getCurrentSiteKey();
    if (!key) return true;
    return GM_getValue("ah-site-enabled-" + key, "1") !== "0";
  }

  // ─── Active adapter ───────────────────────────────────────────────────────
  function getAdapter() {
    if (HOST === "booth.pm" || HOST.endsWith(".booth.pm")) return BOOTH;
    if (HOST === "gumroad.com" || HOST.endsWith(".gumroad.com")) return GUMROAD;
    if (HOST === "jinxxy.com" || HOST.endsWith(".jinxxy.com")) return JINXXY;
    if (HOST === "payhip.com" || HOST.endsWith(".payhip.com")) return PAYHIP;
    if (HOST === "itch.io" || HOST.endsWith(".itch.io")) return ITCH;
    return null;
  }

  // ─── Watermark ────────────────────────────────────────────────────────────
  const WATERMARK = "\n\n---\n# Posted via [Asset Hunter](https://forum.ripper.store/topic/108432/asset-hunter)";

  // ─── Category color map ───────────────────────────────────────────────────
  const CAT_COLORS = {
    "open":              { color: "#ff9540", bg: "rgba(255,149,64,.1)",  bd: "rgba(255,149,64,.25)"  },
    "solved":            { color: "#72f0a8", bg: "rgba(114,240,168,.1)", bd: "rgba(114,240,168,.25)" },
    "gifts":             { color: "#72f0a8", bg: "rgba(114,240,168,.1)", bd: "rgba(114,240,168,.25)" },
    "downloads":         { color: "#72f0a8", bg: "rgba(114,240,168,.1)", bd: "rgba(114,240,168,.25)" },
    "gifts / downloads": { color: "#72f0a8", bg: "rgba(114,240,168,.1)", bd: "rgba(114,240,168,.25)" },
    "looking for":       { color: "#ff9540", bg: "rgba(255,149,64,.1)",  bd: "rgba(255,149,64,.25)"  },
    "general assets":    { color: "#60c8ff", bg: "rgba(96,200,255,.1)",  bd: "rgba(96,200,255,.25)"  },
    "found avatars":     { color: "#72f0a8", bg: "rgba(114,240,168,.1)", bd: "rgba(114,240,168,.25)" },
    "booth avatars":     { color: "#ff6eb4", bg: "rgba(255,110,180,.1)", bd: "rgba(255,110,180,.25)" },
    "gumroad":           { color: "#ff8c69", bg: "rgba(255,140,105,.1)", bd: "rgba(255,140,105,.25)" },
    "payhip":            { color: "#ff8c69", bg: "rgba(255,140,105,.1)", bd: "rgba(255,140,105,.25)" },
    "furry":             { color: "#ffb347", bg: "rgba(255,179,71,.1)",  bd: "rgba(255,179,71,.25)"  },
    "nsfw":              { color: "#ff5577", bg: "rgba(255,85,119,.1)",  bd: "rgba(255,85,119,.25)"  },
    "scripts":           { color: "#a8e6cf", bg: "rgba(168,230,207,.1)", bd: "rgba(168,230,207,.25)" },
    "tools":             { color: "#a8e6cf", bg: "rgba(168,230,207,.1)", bd: "rgba(168,230,207,.25)" },
    "clothes":           { color: "#dda0dd", bg: "rgba(221,160,221,.1)", bd: "rgba(221,160,221,.25)" },
    "hair":              { color: "#f0e68c", bg: "rgba(240,230,140,.1)", bd: "rgba(240,230,140,.25)" },
    "textures":          { color: "#87ceeb", bg: "rgba(135,206,235,.1)", bd: "rgba(135,206,235,.25)" },
    "worlds":            { color: "#9b8ec4", bg: "rgba(155,142,196,.1)", bd: "rgba(155,142,196,.25)" },
    "live2d":            { color: "#ffaad4", bg: "rgba(255,170,212,.1)", bd: "rgba(255,170,212,.25)" },
    "general discussions":{ color: "#9898ff", bg: "rgba(152,152,255,.1)", bd: "rgba(152,152,255,.25)" },
    "uncategorized":     { color: "#6b6b80", bg: "rgba(107,107,128,.1)", bd: "rgba(107,107,128,.25)" },
    "other":             { color: "#6b6b80", bg: "rgba(107,107,128,.1)", bd: "rgba(107,107,128,.25)" },
    "accessories":       { color: "#ffd700", bg: "rgba(255,215,0,.1)",   bd: "rgba(255,215,0,.25)"   },
    "props":             { color: "#cd853f", bg: "rgba(205,133,63,.1)",  bd: "rgba(205,133,63,.25)"  },
    "shaders":           { color: "#00ced1", bg: "rgba(0,206,209,.1)",   bd: "rgba(0,206,209,.25)"   },
    "animations":        { color: "#ff7f50", bg: "rgba(255,127,80,.1)",  bd: "rgba(255,127,80,.25)"  },
  };

  const CAT_DEFAULT = { color: "#9898ff", bg: "rgba(152,152,255,.1)", bd: "rgba(152,152,255,.25)" };

  function getCatStyle(catName) {
    if (!catName) return CAT_DEFAULT;
    const lower = catName.toLowerCase();
    if (CAT_COLORS[lower]) return CAT_COLORS[lower];
    for (const [key, val] of Object.entries(CAT_COLORS)) {
      if (lower.includes(key)) return val;
    }
    return CAT_DEFAULT;
  }

  // ─── Settings Defaults & Helpers ─────────────────────────────────────────
  const DEFAULTS = {
    titleTpl:          "LF: {name}",
    bodyTpl:           "Looking for: **{name}**\n\n{url}\n\nPlease share if you have this item! 🙏",
    defaultTags:       "looking-for, lf, unsolved, asset-hunter",
    autoWatch:         true,
    autoSearch:        true,
    bumpMessage:       "bump",
    startMinimized:    false,
    defaultSearchMode: "name",
    minimizeHotkey:    "",
  };
  // ─── Global post cooldown (10s after any successful post) ─────────────────
  const POST_COOLDOWN_MS = 10000;
  let _lastPostTime = parseInt(GM_getValue("ah-last-post-time", "0"), 10) || 0;

  function recordPost() {
    _lastPostTime = Date.now();
    GM_setValue("ah-last-post-time", String(_lastPostTime));
  }

  // Listen for changes made in other tabs
  GM_addValueChangeListener("ah-last-post-time", (_key, _oldVal, newVal) => {
    _lastPostTime = parseInt(newVal, 10) || 0;
    applyCooldownToAllButtons();
  });

  function cooldownRemaining() {
    return Math.max(0, POST_COOLDOWN_MS - (Date.now() - _lastPostTime));
  }

  function applyCooldownToAllButtons() {
    const remaining = cooldownRemaining();
    if (remaining <= 0) return;

    // Collect all post-action buttons anywhere in the document
    const candidates = Array.from(document.querySelectorAll(
      ".ah-bump-btn:not(.ah-bump-btn--done), #ah-wl-bump-all, #ah-lf-submit"
    ));

    candidates.forEach(btn => {
      if (btn.dataset.cooldownActive === "1") return;
      btn.dataset.cooldownActive = "1";
      const origText = btn.textContent;
      btn.disabled = true;
      const tick = () => {
        const r = cooldownRemaining();
        if (r <= 0) {
          btn.disabled = false;
          btn.textContent = origText;
          delete btn.dataset.cooldownActive;
        } else {
          btn.textContent = `${Math.ceil(r / 1000)}s`;
          setTimeout(tick, 250);
        }
      };
      tick();
    });
  }
  const WATCHLIST_RECHECK_DELAY_MS = 25;

  function getSetting(key) {
    const val = GM_getValue("ah-cfg-" + key, null);
    if (val === null) return DEFAULTS[key];
    if (key === "autoWatch" || key === "autoSearch" || key === "startMinimized") return val === "1";
    return val;
  }
  function setSetting(key, val) {
    if (key === "autoWatch" || key === "autoSearch" || key === "startMinimized") {
      GM_setValue("ah-cfg-" + key, val ? "1" : "0");
    } else {
      GM_setValue("ah-cfg-" + key, String(val));
    }
  }

  function buildTitle(name) {
    return getSetting("titleTpl").replace(/\{name\}/g, name);
  }
  function buildBody(name, url) {
    let body = getSetting("bodyTpl")
      .replace(/\{name\}/g, name)
      .replace(/\{url\}/g, url);
    return body + WATERMARK;
  }

  // ─── Localisation ─────────────────────────────────────────────────────────
  const STRINGS = {
    en: {
      title: "ASSET HUNTER", minimize: "Minimize", expand: "Expand", unknown: "Unknown", search: "Search",
      placeholder: "Search query...", noResult: "No results",
      hits: " hits", solved: "Solved", unsolved: "Open", untitled: "Untitled",
      dlFound: "Download Found", secDiscussions: "Discussions", secOther: "Other",
      errParse: "Failed to parse response", errNetwork: "Network error", errTimeout: "Request timed out",
      errPostFailed: "HTTP {status}: Post failed. Make sure you are logged in.",
      errReplyFailed: "HTTP {status}: Reply failed. Make sure you are logged in to forum.ripper.store.",
      lfBtn: "Post LF Request", lfTitle: "LF Request", lfNotFound: "Not found on Ripper. Want to request it?",
      lfPost: "Post Request", lfPosting: "Posting...",
      lfSuccess: "Posted!", lfViewPost: "View post ->", lfLoginWarn: "You must be logged in at forum.ripper.store.",
      openRipper: "Open Ripper.Store",
      watchlist: "Watchlist", addWatch: "Add to Watchlist", inWatch: "Watching",
      noWatch: "No items in watchlist", recheck: "Re-check all", settings: "Settings",
      langLabel: "Language",
      secLfTemplates: "LF Post Templates", secBehaviour: "Behaviour", secDataMgmt: "Data Management",
      labelTitleTpl: "Title Template", hintTitleTpl: "Use <code>{name}</code> for the asset name",
      labelBodyTpl: "Body Template", hintBodyTpl: "Use <code>{name}</code> for the asset name, <code>{url}</code> for the item link",
      labelDefaultTags: "Default Tags", hintDefaultTags: "Comma separated -- the current platform tag (booth, gumroad, etc.) and smart content tags are appended automatically",
      labelInterval: "Update interval", intervalEvery: "Every", intervalMin: "minutes",
      labelAutoWatch: "Auto-watch posted topics", hintAutoWatch: "Automatically follow your LF posts so you get notified when someone replies",
      labelAutoUpdate: "Auto-update watchlist", hintAutoUpdate: "Automatically re-check all watchlist items on a timer",
      wmLabel: "Watermark -- always appended, not editable",
      btnSave: "Save Settings", btnExport: "Export Data", btnImport: "Import Data",
      btnResetDef: "Reset Defaults", btnDeleteData: "Delete Data", btnReset: "↺ Reset",
      savedMsg: "✓ Saved",
      lfLabelTitle: "Title", lfLabelCategory: "Category", lfLabelTags: "Tags",
      lfLabelTagsHint: "(comma separated)", lfLabelContent: "Content",
      lfLabelContentHint: "(Markdown -- watermark auto-appended)",
      btnCancel: "Cancel",
      importTitle: "Import Data", importDrop: "Drop your JSON here", importDropSub: "or click to browse",
      importOk: "✓ Imported successfully!", importErr: "Failed to parse JSON -- is this a valid export file?",
      importInvalid: "Please drop a valid .json file.",
      lfErrTitle: "Please enter a title.", lfErrContent: "Please enter content.",
      lfConnecting: "Connecting to Ripper.Store...",
      modalResetTitle: "Reset to Defaults", modalResetMsg: "This will reset all settings to their default values. Your watchlist will not be affected.",
      modalResetProceed: "Reset",
      modalDeleteTitle: "Delete Watchlist", modalDeleteMsg: "This will permanently delete all items in your watchlist. This cannot be undone.",
      modalDeleteProceed: "Delete",
      searching: "Searching...",
      openPost: "↗ Open post", openItem: "↗ Open item",
      warnNoName: "<strong>Title template</strong> is missing <code>{name}</code> -- the asset name won't appear in your post title.",
      warnBadUrl: "<strong>Body template</strong> has <code>{url}</code> on a line with other text -- Ripper.Store won't generate a link preview embed unless <code>{url}</code> is alone on its own line.",
      kofiCardMsg: "Enjoying Asset Hunter? Consider supporting!",
      kofiModalTitle: "Before you close this...",
      kofiModalBody: "Asset Hunter is free and took a lot of effort, time, and sanity to build. Donations are never necessary, but please consider supporting if it has helped you.",
      kofiKeepBtn: "I'll consider",
      kofiCloseBtn: "Close anyway",
      kofiDontAsk: "Don't ask again",
      labelAutoSearch: "Auto-search on page load", hintAutoSearch: "Automatically search Ripper.Store when you open an item page",
      manualSearchBtn: "Search Ripper.Store",
      supTitle: "Top Supporters",
      supSubtitle: "Donate any amount to have your name here",
      supEmpty: "No donations yet.",
      supEmptySub: "Be the first -- your name will appear right here.",
      supLoadErr: "Could not load supporter data.",
      // Bump
      bumpBtn: "Bump", bumpBtnSending: "Bumping...", bumpBtnDone: "Bumped!",
      bumpBtnErr: "Failed",
      bumpAllBtn: "Bump All Unsolved",
      labelBumpMessage: "Bump Message", hintBumpMessage: "The text posted when you click Bump on a result",
      bumpProgressRunning: "{done}/{total} bumped{failed} · ~{secs}s left",
      bumpProgressFailed: " ({n} failed)",
      bumpProgressDone: "Done -- {bumped} bumped{failed}",
      bumpProgressDoneFailed: ", {n} failed",
      warnModalTitle: "Check your templates", warnModalOk: "Understood",
      changelogBadgeNew: "NEW",
      // Changelog
      changelogBtn: "Changelogs",
      changelogTitle: "Asset Hunter updated!",
      changelogSubtitle: "here are the changes:",
      changelogSectionFixes: "Fixes",
      changelogSectionAdditions: "Additions",
      changelogSectionRemovals: "Removals",
      changelogSectionInfo: "Info",
      changelogSectionTips: "Tip",
      changelogClose: "Got it",
      changelogKofiTitle: "Enjoying Asset Hunter?",
      changelogKofiSub: "Donate on Ko-fi to support updates and suggest new features",
      // Bump topic popup
      bumpTopicTitle: "Keep Asset Hunter visible!",
      bumpTopicBody: "Consider bumping the main Asset Hunter topic, that way more people can find and use the tool!",
      bumpTopicSub: "Clicking \"Sure!\" will automatically bump the main topic. If you don't want to do that, simply click the other option.",
      bumpTopicYes: "Sure!",
      bumpTopicNo: "No thanks",
      bumpTopicSending: "Bumping...",
      bumpTopicDone: "Bumped! Thanks!",
      bumpTopicErr: "Failed to bump",
      // Kofi random popup
      kofiPopupTitle: "Enjoying Asset Hunter?",
      kofiPopupBody: "Asset Hunter is free and takes real effort to keep updated. If it's been useful to you, consider buying me a coffee, it means a lot and helps keep the tool alive!",
      kofiPopupBtn: "&#9749; Support on Ko-fi",
      kofiPopupDismiss: "Maybe later",
      // New settings strings
      labelStartMinimized: "Start Minimized", hintStartMinimized: "Open the panel minimized by default",
      secActiveSites: "Active Platforms", hintActiveSites: "Uncheck to disable the script entirely on that platform",
      labelDefaultSearchMode: "Default Search Mode", hintDefaultSearchMode: "Whether searches use the detected name or the full page URL by default",
      searchModeToggle_name: "Mode: Name", searchModeToggle_url: "Mode: URL",
      matchModeToggle_any: "Match: Any", matchModeToggle_all: "Match: All",
      labelMinimizeHotkey: "Minimize Hotkey", hintMinimizeHotkey: "Key combo to toggle the panel open and closed. Example: Alt+H",
      hotkeyPlaceholder: "e.g. Alt+H",
      // Changelog entries -- 6.4.1
      "changelog.fix.frenchTranslations": "Fixed minor French translation errors.",
      // Changelog entries -- 6.4.0
      "changelog.add.frenchLang": "Added French language support.",
      "changelog.add.itchio": "Added itch.io support.",
      "changelog.add.siteFilter": "Added per-platform toggles in settings to completely disable the script on specific sites.",
      "changelog.add.startMinimized": "Added a setting to start the panel minimized by default. The Ko-fi banner also hides when minimized.",
      "changelog.add.urlSearchMode": "Added a URL search mode button to search by the full page URL instead of the detected name. A default mode setting is available in Settings.",
      "changelog.add.minimizeHotkey": "Added a customizable hotkey to toggle the panel open and closed.",
      "changelog.add.matchWordsToggle": "Added a Match: Any / Match: All toggle next to the search mode button. Flip to All to require every word in your query to appear in a post, which cuts out a lot of unrelated results.",
      "changelog.tip.searchWorkaround": "Getting a flood of random results? Two small buttons above the search bar can help. Switch to Match: All to require every word to show up in a post, or flip to Mode: URL to search by the full page address instead of the product name. These are workarounds for a fundamental limitation of Ripper.Store's built-in keyword search -- there is no perfect fix, but they usually clean things up a lot.",
      // Changelog entries -- 6.3.0
      "changelog.add.bumpTopicPopup": "Added a bump prompt that appears after the changelogs.",
      "changelog.add.kofiRandomPopup": "Added a small donation reminder popup with a 1 in 10 chance of appearing on page load.",
      "changelog.fix.kofiThreshold": "The \"don't show again\" checkbox on the Ko-fi banner now appears after 2 closures instead of 5",
      // Changelog entries -- 6.2.0
      "changelog.info.bumpSetting": "The bump message can be customized in Settings, check the Settings tab to configure it",
      "changelog.add.bumpButton": "Added Bump buttons to search result cards and watchlist items, lets you post a reply directly from Asset Hunter",
      "changelog.add.bumpSetting": "Added a Bump Message setting, customize what text gets posted when you bump (default: \"bump\")",
      "changelog.add.bumpWatchlist": "Bump buttons now appear on watchlist items that have a linked forum topic and are not marked as downloaded",
      "changelog.add.bumpAll": "Added a Bump All button to the watchlist tab, queues up every eligible topic and bumps them one by one with an 11-second gap and a live progress indicator",
      "changelog.add.postCooldown": "Added a 10-second post cooldown shared across all open tabs, any successful post locks all post buttons everywhere until the cooldown expires",
      "changelog.add.crossTabCooldown": "The post cooldown is now shared across all open tabs in real time, posting in one tab immediately locks the buttons in every other tab",
      "changelog.remove.autoUpdate": "Removed the auto-update watchlist setting and its interval timer, it was not working correctly and may be re-added in a future update",
      // Changelog entries -- 6.1.2
      "changelog.fix.nameParsingDash": "Fixed asset names being cut off at dashes, products like \"potato - for whatever\" now correctly use their full name instead of just \"potato\"",
      // Changelog entries - 6.1.1
      "changelog.fix.boothSanitizationRemoved": "Removed the Booth URL sanitization feature introduced in 6.1.0. It did not work as intended and has been fully reverted",
      "changelog.fix.previewBtnRemoved": "Removed the Preview on Site button from the LF post modal, it was redundant",
      // Changelog entries - 6.1.0
      "changelog.fix.boothLink": "Booth subdomain links (like user.booth.pm/items/...) were automatically stripped to the clean canonical URL (booth.pm/items/...) when posting",
      "changelog.add.changelogPopup": "Added this changelog popup! It shows up once after each update, and you can re-open it from Settings anytime",
      "changelog.add.changelogBtn": "Added a Changelogs button in the Settings tab so you never lose track of what changed",
    },
    ja: {
      title: "ASSET HUNTER", minimize: "最小化", expand: "展開", unknown: "不明", search: "検索",
      placeholder: "検索ワード...", noResult: "結果なし",
      hits: "件ヒット", solved: "解決済", unsolved: "未解決", untitled: "無題",
      dlFound: "ダウンロード発見", secDiscussions: "ディスカッション", secOther: "その他",
      errParse: "解析失敗", errNetwork: "通信エラー", errTimeout: "タイムアウト",
      errPostFailed: "HTTP {status}: 投稿に失敗しました。ログインしているか確認してください。",
      errReplyFailed: "HTTP {status}: 返信に失敗しました。forum.ripper.storeにログインしているか確認してください。",
      lfBtn: "LFリクエスト投稿", lfTitle: "LFリクエスト", lfNotFound: "Ripperで見つかりません。リクエストしますか?",
      lfPost: "投稿する", lfPosting: "投稿中...",
      lfSuccess: "投稿成功!", lfViewPost: "投稿を見る ->", lfLoginWarn: "forum.ripper.store にログインが必要です。",
      openRipper: "Ripper.Storeを開く",
      watchlist: "ウォッチリスト", addWatch: "監視リストに追加", inWatch: "監視中",
      noWatch: "監視アイテムなし", recheck: "再チェック", settings: "設定",
      langLabel: "言語",
      secLfTemplates: "LFテンプレート", secBehaviour: "動作設定", secDataMgmt: "データ管理",
      labelTitleTpl: "タイトルテンプレート", hintTitleTpl: "<code>{name}</code> でアセット名を挿入",
      labelBodyTpl: "本文テンプレート", hintBodyTpl: "<code>{name}</code> でアセット名、<code>{url}</code> でリンクを挿入",
      labelDefaultTags: "デフォルトタグ", hintDefaultTags: "カンマ区切り -- プラットフォームタグ(booth, gumroadなど)とスマートタグが自動付与されます",
      labelInterval: "更新間隔", intervalEvery: "毎", intervalMin: "分",
      labelAutoWatch: "投稿を自動ウォッチ", hintAutoWatch: "LF投稿に返信があると通知を受け取るため自動フォロー",
      labelAutoUpdate: "ウォッチリスト自動更新", hintAutoUpdate: "タイマーでウォッチリストを自動再チェック",
      wmLabel: "ウォーターマーク -- 常に追記されます(編集不可)",
      btnSave: "設定を保存", btnExport: "データ書き出し", btnImport: "データ読み込み",
      btnResetDef: "デフォルトに戻す", btnDeleteData: "データ削除", btnReset: "↺ リセット",
      savedMsg: "✓ 保存",
      lfLabelTitle: "タイトル", lfLabelCategory: "カテゴリ", lfLabelTags: "タグ",
      lfLabelTagsHint: "(カンマ区切り)", lfLabelContent: "本文",
      lfLabelContentHint: "(Markdown -- ウォーターマーク自動付与)",
      btnCancel: "キャンセル",
      importTitle: "データ読み込み", importDrop: "JSONをここにドロップ", importDropSub: "またはクリックして選択",
      importOk: "✓ 読み込み完了!", importErr: "JSON解析失敗 -- 正しいエクスポートファイルですか?",
      importInvalid: "有効な .json ファイルをドロップしてください。",
      lfErrTitle: "タイトルを入力してください。", lfErrContent: "本文を入力してください。",
      lfConnecting: "Ripper.Store に接続中...",
      modalResetTitle: "デフォルトにリセット", modalResetMsg: "すべての設定がデフォルト値に戻ります。ウォッチリストはそのままです。",
      modalResetProceed: "リセット",
      modalDeleteTitle: "ウォッチリスト削除", modalDeleteMsg: "ウォッチリストのすべての項目が完全に削除されます。元に戻せません。",
      modalDeleteProceed: "削除",
      searching: "検索中...",
      openPost: "↗ 投稿を開く", openItem: "↗ アイテムを開く",
      warnNoName: "<strong>タイトルテンプレート</strong>に <code>{name}</code> がありません。",
      warnBadUrl: "<strong>本文テンプレート</strong>の <code>{url}</code> は単独行に配置してください。",
      kofiCardMsg: "Asset Hunterを楽しんでいますか? よければサポートをご検討ください!",
      kofiModalTitle: "閉じる前に...",
      kofiModalBody: "Asset Hunter は無料ですが、作成と維持には多くの時間・労力・正気が必要でした。寄付は必須ではありませんが、役に立ったならぜひご支援をご検討ください。",
      kofiKeepBtn: "検討します",
      kofiCloseBtn: "それでも閉じる",
      kofiDontAsk: "今後は表示しない",
      labelAutoSearch: "ページ読み込み時に自動検索", hintAutoSearch: "アイテムページを開いたときに自動的にRipper.Storeを検索",
      manualSearchBtn: "Ripper.Storeを検索",
      supTitle: "トップサポーター",
      supSubtitle: "金額問わず寄付するとここに名前が載ります",
      supEmpty: "まだ寄付はありません。",
      supEmptySub: "最初の支援者になりましょう -- お名前がここに表示されます。",
      supLoadErr: "サポーターデータを読み込めませんでした。",
      // Bump
      bumpBtn: "バンプ", bumpBtnSending: "送信中...", bumpBtnDone: "完了!",
      bumpBtnErr: "失敗",
      bumpAllBtn: "未解決を全バンプ",
      labelBumpMessage: "バンプメッセージ", hintBumpMessage: "バンプボタンを押したときに投稿されるテキスト",
      bumpProgressRunning: "{done}/{total} バンプ済{failed} · 残り約{secs}秒",
      bumpProgressFailed: " ({n}件失敗)",
      bumpProgressDone: "完了 -- {bumped}件バンプ済{failed}",
      bumpProgressDoneFailed: "、{n}件失敗",
      warnModalTitle: "テンプレートを確認してください", warnModalOk: "了解しました",
      changelogBadgeNew: "NEW",
      // Changelog
      changelogBtn: "更新履歴",
      changelogTitle: "Asset Hunter が更新されました!",
      changelogSubtitle: "変更内容はこちら:",
      changelogSectionFixes: "修正",
      changelogSectionAdditions: "追加",
      changelogSectionRemovals: "削除",
      changelogSectionInfo: "情報",
      changelogSectionTips: "ヒント",
      changelogClose: "了解",
      changelogKofiTitle: "Asset Hunterを楽しんでいますか?",
      changelogKofiSub: "Ko-fiで支援して、更新や新機能のリクエストを後押ししましょう",
      // Bump topic popup
      bumpTopicTitle: "Asset Hunterを広めよう!",
      bumpTopicBody: "Asset Hunterのメイントピックをバンプして、もっと多くの人がツールを見つけられるようにしましょう!",
      bumpTopicSub: "「もちろん!」をクリックするとメイントピックが自動でバンプされます。不要な場合はもう一方のボタンを押してください。",
      bumpTopicYes: "もちろん!",
      bumpTopicNo: "いいえ、結構です",
      bumpTopicSending: "バンプ中...",
      bumpTopicDone: "バンプ完了!ありがとう!",
      bumpTopicErr: "バンプに失敗しました",
      // Kofi random popup
      kofiPopupTitle: "Asset Hunterを楽しんでいますか?",
      kofiPopupBody: "Asset Hunterは無料ですが、維持するには本当の努力が必要です。役に立っていると感じたら、コーヒー一杯の支援をご検討ください!",
      kofiPopupBtn: "&#9749; Ko-fiでサポート",
      kofiPopupDismiss: "また今度",
      // 設定の新しい文字列
      labelStartMinimized: "最初から最小化", hintStartMinimized: "デフォルトでパネルを最小化して開く",
      secActiveSites: "対応プラットフォーム", hintActiveSites: "チェックを外すと特定のサイトでスクリプトを無効化",
      labelDefaultSearchMode: "デフォルト検索モード", hintDefaultSearchMode: "デフォルトで名前またはURLで検索するかを選択",
      searchModeToggle_name: "モード:名前", searchModeToggle_url: "モード:URL",
      matchModeToggle_any: "一致:いずれか", matchModeToggle_all: "一致:すべて",
      labelMinimizeHotkey: "最小化ホットキー", hintMinimizeHotkey: "パネルの開閉に使うキー。例:Alt+H",
      hotkeyPlaceholder: "例:Alt+H",
      // Changelog entries -- 6.4.1
      "changelog.fix.frenchTranslations": "フランス語の翻訳の一部を修正しました。",
      // Changelog entries -- 6.4.0
      "changelog.add.frenchLang": "フランス語サポートを追加しました。",
      "changelog.add.itchio": "itch.ioのサポートを追加しました。",
      "changelog.add.siteFilter": "設定に各プラットフォームの有効・無効の切り替えを追加しました。",
      "changelog.add.startMinimized": "デフォルトでパネルを最小化して開始する設定を追加しました。最小化時はKo-fiバナーも非表示になります。",
      "changelog.add.urlSearchMode": "名前ではなくURLで検索するURLモードボタンを追加しました。デフォルトモードの設定も利用可能です。",
      "changelog.add.minimizeHotkey": "パネルの開閉に使えるカスタムホットキーを追加しました。",
      "changelog.add.matchWordsToggle": "検索モードボタンの横に「一致:いずれか / 一致:すべて」トグルを追加しました。「すべて」に切り替えると、クエリのすべての単語が投稿に含まれていないと表示されません。",
      "changelog.tip.searchWorkaround": "関係のない結果が大量に表示される場合、検索バー上の2つのボタンが役立ちます。「一致:すべて」に切り替えるとすべての単語を含む投稿だけが表示され、「モード:URL」に切り替えると製品名の代わりにページのURLで検索します。これはRipper.Storeの検索エンジンの根本的な制限に対する回避策です。完璧な解決策はありませんが、通常は大幅に改善されます。",
      // Changelog entries -- 6.3.0
      "changelog.add.kofiRandomPopup": "ページ読み込み時に10分の1の確率で表示される寄付リマインダーポップアップを追加しました。",
      "changelog.fix.kofiThreshold": "Ko-fiバナーの「今後は表示しない」チェックボックスが5回の閉鎖後ではなく2回後に表示されるようになりました",
      // Changelog entries -- 6.2.0
      "changelog.info.bumpSetting": "バンプメッセージは設定からカスタマイズできます。設定タブを確認してみてください",
      "changelog.add.bumpButton": "検索結果カードとウォッチリストアイテムにバンプボタンを追加しました。Asset Hunterから直接返信を投稿できます",
      "changelog.add.bumpSetting": "バンプメッセージの設定を追加しました。バンプ時に投稿される内容をカスタマイズできます(デフォルト:「bump」)",
      "changelog.add.bumpWatchlist": "フォーラムトピックが紐付いており、ダウンロード済みでないウォッチリストアイテムにもバンプボタンを表示するようになりました",
      "changelog.add.bumpAll": "ウォッチリストタブに「全バンプ」ボタンを追加しました。対象トピックを11秒間隔で順番にバンプし、進捗状況をリアルタイム表示します",
      "changelog.add.postCooldown": "投稿クールダウンを追加しました。投稿が成功すると10秒間、すべての開いているタブの投稿ボタンがロックされます",
      "changelog.add.crossTabCooldown": "投稿クールダウンはすべての開いているタブでリアルタイム共有されます。あるタブで投稿すると、他のすべてのタブのボタンも即座にロックされます",
      "changelog.remove.autoUpdate": "ウォッチリスト自動更新の設定とタイマーを削除しました。正常に動作していなかったため、将来的に再追加される可能性があります",
      // Changelog entries -- 6.1.2
      "changelog.fix.nameParsingDash": "アセット名がダッシュで途切れるバグを修正しました。「potato - for whatever」のような名前が「potato」に短縮されず、正しく全体が使われるようになりました",
      // Changelog entries -- 6.1.1
      "changelog.fix.boothSanitizationRemoved": "6.1.0で導入したBoothのURL変換機能を削除しました。意図した通りに動作しなかったため、完全に元に戻しました",
      "changelog.fix.previewBtnRemoved": "LFリクエストモーダルの「サイトでプレビュー」ボタンを削除しました。不要なボタンだったため",
      // Changelog entries -- 6.1.0
      "changelog.fix.boothLink": "投稿時にBoothのサブドメインリンク(例:user.booth.pm/items/...)が自動的に正規URL(booth.pm/items/...)に変換されるようになりました",
      "changelog.add.changelogPopup": "この更新履歴ポップアップを追加しました!更新のたびに1回表示され、設定からいつでも再表示できます",
      "changelog.add.changelogBtn": "設定タブに「更新履歴」ボタンを追加しました。変更内容をいつでも確認できます",
    },
    ru: {
      title: "ASSET HUNTER", minimize: "Свернуть", expand: "Развернуть", unknown: "Неизвестно", search: "Поиск",
      placeholder: "Поисковый запрос...", noResult: "Нет результатов",
      hits: " совпадений", solved: "Решено", unsolved: "Открыто", untitled: "Без названия",
      dlFound: "Загрузка найдена", secDiscussions: "Обсуждения", secOther: "Другое",
      errParse: "Ошибка разбора ответа", errNetwork: "Ошибка сети", errTimeout: "Время запроса истекло",
      errPostFailed: "HTTP {status}: Не удалось опубликовать. Убедитесь, что вы авторизованы.",
      errReplyFailed: "HTTP {status}: Ответ не отправлен. Убедитесь, что вы авторизованы на forum.ripper.store.",
      lfBtn: "Создать LF запрос", lfTitle: "LF Запрос", lfNotFound: "Не найдено на Ripper. Хотите запросить?",
      lfPost: "Опубликовать", lfPosting: "Публикация...",
      lfSuccess: "Опубликовано!", lfViewPost: "Открыть пост ->", lfLoginWarn: "Необходимо войти на forum.ripper.store.",
      openRipper: "Открыть Ripper.Store",
      watchlist: "Список слежения", addWatch: "Добавить в список", inWatch: "Отслеживается",
      noWatch: "Список слежения пуст", recheck: "Проверить всё", settings: "Настройки",
      langLabel: "Язык",
      secLfTemplates: "Шаблоны LF постов", secBehaviour: "Поведение", secDataMgmt: "Управление данными",
      labelTitleTpl: "Шаблон заголовка", hintTitleTpl: "Используйте <code>{name}</code> для названия ассета",
      labelBodyTpl: "Шаблон текста", hintBodyTpl: "Используйте <code>{name}</code> для названия, <code>{url}</code> для ссылки",
      labelDefaultTags: "Теги по умолчанию", hintDefaultTags: "Через запятую -- тег платформы (booth, gumroad и др.) и умные теги добавляются автоматически",
      labelInterval: "Интервал обновления", intervalEvery: "Каждые", intervalMin: "минут",
      labelAutoWatch: "Авто-слежение за постами", hintAutoWatch: "Автоматически следить за LF постами для получения уведомлений",
      labelAutoUpdate: "Авто-обновление списка", hintAutoUpdate: "Автоматически перепроверять список слежения по таймеру",
      wmLabel: "Водяной знак -- всегда добавляется, не редактируется",
      btnSave: "Сохранить настройки", btnExport: "Экспорт данных", btnImport: "Импорт данных",
      btnResetDef: "Сброс по умолчанию", btnDeleteData: "Удалить данные", btnReset: "↺ Сброс",
      savedMsg: "✓ Сохранено",
      lfLabelTitle: "Заголовок", lfLabelCategory: "Категория", lfLabelTags: "Теги",
      lfLabelTagsHint: "(через запятую)", lfLabelContent: "Содержание",
      lfLabelContentHint: "(Markdown -- водяной знак добавляется автоматически)",
      btnCancel: "Отмена",
      importTitle: "Импорт данных", importDrop: "Перетащите JSON сюда", importDropSub: "или нажмите для выбора",
      importOk: "✓ Импорт выполнен!", importErr: "Ошибка разбора JSON -- это верный файл экспорта?",
      importInvalid: "Перетащите корректный .json файл.",
      lfErrTitle: "Введите заголовок.", lfErrContent: "Введите содержание.",
      lfConnecting: "Подключение к Ripper.Store...",
      modalResetTitle: "Сброс настроек", modalResetMsg: "Все настройки будут сброшены до значений по умолчанию. Список слежения не затронут.",
      modalResetProceed: "Сбросить",
      modalDeleteTitle: "Удалить список слежения", modalDeleteMsg: "Все элементы списка слежения будут удалены безвозвратно.",
      modalDeleteProceed: "Удалить",
      searching: "Поиск...",
      openPost: "↗ Открыть пост", openItem: "↗ Открыть элемент",
      warnNoName: "<strong>Шаблон заголовка</strong> не содержит <code>{name}</code>.",
      warnBadUrl: "<strong>Шаблон текста</strong>: <code>{url}</code> должен быть на отдельной строке.",
      kofiCardMsg: "Нравится Asset Hunter? Подумайте о поддержке!",
      kofiModalTitle: "Перед тем как закрыть...",
      kofiModalBody: "Asset Hunter бесплатный, но на его создание ушло очень много сил, времени и нервов. Донаты не обязательны, но, пожалуйста, подумайте о поддержке, если он вам помог.",
      kofiKeepBtn: "Я подумаю",
      kofiCloseBtn: "Все равно закрыть",
      kofiDontAsk: "Больше не спрашивать",
      labelAutoSearch: "Автопоиск при загрузке страницы", hintAutoSearch: "Автоматически искать на Ripper.Store при открытии страницы товара",
      manualSearchBtn: "Искать на Ripper.Store",
      supTitle: "Топ поддержавших",
      supSubtitle: "Задонатьте любую сумму -- ваше имя появится здесь",
      supEmpty: "Пока нет донатов.",
      supEmptySub: "Будьте первым -- ваше имя появится прямо здесь.",
      supLoadErr: "Не удалось загрузить данные о поддержавших.",
      // Bump
      bumpBtn: "Бамп", bumpBtnSending: "Отправка...", bumpBtnDone: "Отправлено!",
      bumpBtnErr: "Ошибка",
      bumpAllBtn: "Бамп всех нерешённых",
      labelBumpMessage: "Сообщение бампа", hintBumpMessage: "Текст, который публикуется при нажатии кнопки Бамп",
      bumpProgressRunning: "{done}/{total} забампено{failed} · ~{secs}с осталось",
      bumpProgressFailed: " ({n} не удалось)",
      bumpProgressDone: "Готово -- {bumped} забампено{failed}",
      bumpProgressDoneFailed: ", {n} не удалось",
      warnModalTitle: "Проверьте шаблоны", warnModalOk: "Понятно",
      changelogBadgeNew: "NEW",
      // Changelog
      changelogBtn: "Список изменений",
      changelogTitle: "Asset Hunter обновлён!",
      changelogSubtitle: "вот что изменилось:",
      changelogSectionFixes: "Исправления",
      changelogSectionAdditions: "Добавлено",
      changelogSectionRemovals: "Удалено",
      changelogSectionInfo: "Информация",
      changelogSectionTips: "Совет",
      changelogClose: "Понял",
      changelogKofiTitle: "Нравится Asset Hunter?",
      changelogKofiSub: "Поддержите на Ko-fi, чтобы помочь с обновлениями и предложить новые функции",
      // Bump topic popup
      bumpTopicTitle: "Помогите Asset Hunter стать заметнее!",
      bumpTopicBody: "Подумайте о бампе главной темы Asset Hunter, чтобы больше людей могли найти и использовать инструмент!",
      bumpTopicSub: "Нажав «Конечно!», вы автоматически забампите главную тему. Если не хотите, просто нажмите другую кнопку.",
      bumpTopicYes: "Конечно!",
      bumpTopicNo: "Нет, спасибо",
      bumpTopicSending: "Бампим...",
      bumpTopicDone: "Забампили! Спасибо!",
      bumpTopicErr: "Не удалось забампить",
      // Kofi random popup
      kofiPopupTitle: "Нравится Asset Hunter?",
      kofiPopupBody: "Asset Hunter бесплатный, но поддерживать его требует реальных усилий. Если он оказался полезным, подумайте о небольшой поддержке, это очень важно!",
      kofiPopupBtn: "&#9749; Поддержать на Ko-fi",
      kofiPopupDismiss: "Может, позже",
      // Новые строки настроек
      labelStartMinimized: "Запуск свёрнутым", hintStartMinimized: "Открывать панель свёрнутой по умолчанию",
      secActiveSites: "Активные платформы", hintActiveSites: "Снимите флажок, чтобы отключить скрипт на конкретной платформе",
      labelDefaultSearchMode: "Режим поиска по умолчанию", hintDefaultSearchMode: "Поиск по имени или по полному URL по умолчанию",
      searchModeToggle_name: "Режим: Имя", searchModeToggle_url: "Режим: URL",
      matchModeToggle_any: "Слова: любые", matchModeToggle_all: "Слова: все",
      labelMinimizeHotkey: "Горячая клавиша", hintMinimizeHotkey: "Сочетание клавиш для сворачивания/разворачивания панели. Пример: Alt+H",
      hotkeyPlaceholder: "напр. Alt+H",
      // Changelog entries -- 6.4.1
      "changelog.fix.frenchTranslations": "Исправлены небольшие ошибки во французском переводе.",
      // Changelog entries -- 6.4.0
      "changelog.add.frenchLang": "Добавлена поддержка французского языка.",
      "changelog.add.itchio": "Добавлена поддержка itch.io.",
      "changelog.add.siteFilter": "Добавлены переключатели в настройках для полного отключения скрипта на конкретных платформах.",
      "changelog.add.startMinimized": "Добавлена настройка запуска панели свёрнутой по умолчанию. Баннер Ko-fi также скрывается при сворачивании.",
      "changelog.add.urlSearchMode": "Добавлена кнопка режима URL для поиска по полному URL страницы вместо определённого имени. Доступна настройка режима по умолчанию.",
      "changelog.add.minimizeHotkey": "Добавлена настраиваемая горячая клавиша для сворачивания/разворачивания панели.",
      "changelog.add.matchWordsToggle": "Добавлена кнопка «Слова: любые / Слова: все» рядом с переключателем режима поиска. В режиме «все» результаты будут содержать только посты со всеми словами запроса.",
      "changelog.tip.searchWorkaround": "Слишком много лишних результатов? Два переключателя над строкой поиска помогут. «Слова: все» требует, чтобы все слова запроса присутствовали в посте. «Режим: URL» ищет по полному адресу страницы вместо названия товара. Это обходные решения для фундаментального ограничения поиска Ripper.Store -- идеального решения нет, но обычно становится значительно лучше.",
      // Changelog entries -- 6.3.0
      "changelog.add.bumpTopicPopup": "Добавлен запрос бампа после чейнджлогов.",
      "changelog.add.kofiRandomPopup": "Добавлено небольшое напоминание о донате с вероятностью 1 из 10 при загрузке страницы.",
      "changelog.fix.kofiThreshold": "Чекбокс «Больше не спрашивать» на баннере Ko-fi теперь появляется после 2 закрытий вместо 5",
      // Changelog entries -- 6.2.0
      "changelog.info.bumpSetting": "Сообщение бампа можно настроить в разделе Настроек, загляните туда",
      "changelog.add.bumpButton": "Добавлены кнопки Бамп на карточки результатов и элементы списка наблюдения, позволяет публиковать ответ прямо из Asset Hunter",
      "changelog.add.bumpSetting": "Добавлена настройка сообщения бампа, теперь можно изменить текст, который публикуется при бампе (по умолчанию: «bump»)",
      "changelog.add.bumpWatchlist": "Кнопки бампа теперь отображаются на элементах списка наблюдения, у которых есть связанная тема на форуме и которые не отмечены как загруженные",
      "changelog.add.bumpAll": "Добавлена кнопка «Бамп всех» на вкладке списка наблюдения, ставит все подходящие темы в очередь и бампит их по одной с интервалом 11 секунд и индикатором прогресса",
      "changelog.add.postCooldown": "Добавлен кулдаун публикации на 10 секунд, после успешной публикации все кнопки публикации во всех открытых вкладках блокируются до окончания кулдауна",
      "changelog.add.crossTabCooldown": "Кулдаун публикации теперь синхронизируется между всеми открытыми вкладками, публикация в одной вкладке мгновенно блокирует кнопки во всех остальных",
      "changelog.remove.autoUpdate": "Удалена настройка автообновления списка наблюдения и таймер, функция работала некорректно и может быть возвращена в будущем",
      // Changelog entries -- 6.1.2
      "changelog.fix.nameParsingDash": "Исправлено обрезание названий ассетов по дефису, теперь названия вроде «potato - for whatever» сохраняются полностью, а не урезаются до «potato»",
      // Changelog entries -- 6.1.1
      "changelog.fix.boothSanitizationRemoved": "Удалена функция нормализации URL Booth, добавленная в 6.1.0. Она работала некорректно и была полностью отменена",
      "changelog.fix.previewBtnRemoved": "Убрана кнопка «Предпросмотр на сайте» из окна LF-запроса, так как она была лишней",
      // Changelog entries -- 6.1.0
      "changelog.fix.boothLink": "Ссылки на Booth с поддоменом (например, user.booth.pm/items/...) теперь автоматически заменялись чистым каноническим URL (booth.pm/items/...) при публикации",
      "changelog.add.changelogPopup": "Добавлено это всплывающее окно со списком изменений! Оно появляется один раз после каждого обновления, и его можно снова открыть из Настроек",
      "changelog.add.changelogBtn": "Добавлена кнопка «Список изменений» на вкладке Настроек, чтобы всегда можно было посмотреть что изменилось",
    },
    "pt-BR": {
      title: "ASSET HUNTER", minimize: "Minimizar", expand: "Expandir", unknown: "Desconhecido", search: "Buscar",
      placeholder: "Termo de busca...", noResult: "Sem resultados",
      hits: " resultados", solved: "Resolvido", unsolved: "Aberto", untitled: "Sem título",
      dlFound: "Download encontrado", secDiscussions: "Discussões", secOther: "Outros",
      errParse: "Falha ao processar resposta", errNetwork: "Erro de rede", errTimeout: "Tempo de requisição esgotado",
      errPostFailed: "HTTP {status}: Falha ao publicar. Verifique se você está logado.",
      errReplyFailed: "HTTP {status}: Falha ao responder. Verifique se você está logado em forum.ripper.store.",
      lfBtn: "Postar pedido LF", lfTitle: "Pedido LF", lfNotFound: "Não encontrado no Ripper. Deseja solicitar?",
      lfPost: "Publicar", lfPosting: "Publicando...",
      lfSuccess: "Publicado!", lfViewPost: "Ver post ->", lfLoginWarn: "Você precisa estar logado no forum.ripper.store.",
      openRipper: "Abrir Ripper.Store",
      watchlist: "Lista de observação", addWatch: "Adicionar à lista", inWatch: "Monitorando",
      noWatch: "Nenhum item na lista", recheck: "Verificar tudo", settings: "Configurações",
      langLabel: "Idioma",
      secLfTemplates: "Modelos de post LF", secBehaviour: "Comportamento", secDataMgmt: "Gerenciar dados",
      labelTitleTpl: "Modelo de título", hintTitleTpl: "Use <code>{name}</code> para o nome do asset",
      labelBodyTpl: "Modelo de corpo", hintBodyTpl: "Use <code>{name}</code> para o nome, <code>{url}</code> para o link",
      labelDefaultTags: "Tags padrão", hintDefaultTags: "Separadas por vírgula -- a tag da plataforma (booth, gumroad, etc.) e tags inteligentes são adicionadas automaticamente",
      labelInterval: "Intervalo de atualização", intervalEvery: "A cada", intervalMin: "minutos",
      labelAutoWatch: "Monitorar posts automaticamente", hintAutoWatch: "Seguir seus posts LF automaticamente para receber notificações de resposta",
      labelAutoUpdate: "Atualizar lista automaticamente", hintAutoUpdate: "Verificar todos os itens da lista por temporizador",
      wmLabel: "Marca d'água -- sempre adicionada, não editável",
      btnSave: "Salvar configurações", btnExport: "Exportar dados", btnImport: "Importar dados",
      btnResetDef: "Restaurar padrões", btnDeleteData: "Apagar dados", btnReset: "↺ Restaurar",
      savedMsg: "✓ Salvo",
      lfLabelTitle: "Título", lfLabelCategory: "Categoria", lfLabelTags: "Tags",
      lfLabelTagsHint: "(separadas por vírgula)", lfLabelContent: "Conteúdo",
      lfLabelContentHint: "(Markdown -- marca d'água adicionada automaticamente)",
      btnCancel: "Cancelar",
      importTitle: "Importar dados", importDrop: "Arraste seu JSON aqui", importDropSub: "ou clique para selecionar",
      importOk: "✓ Importado com sucesso!", importErr: "Falha ao analisar JSON -- é um arquivo de exportação válido?",
      importInvalid: "Arraste um arquivo .json válido.",
      lfErrTitle: "Por favor insira um título.", lfErrContent: "Por favor insira o conteúdo.",
      lfConnecting: "Conectando ao Ripper.Store...",
      modalResetTitle: "Restaurar padrões", modalResetMsg: "Todas as configurações serão restauradas. Sua lista de observação não será afetada.",
      modalResetProceed: "Restaurar",
      modalDeleteTitle: "Apagar lista de observação", modalDeleteMsg: "Todos os itens da lista serão apagados permanentemente. Isso não pode ser desfeito.",
      modalDeleteProceed: "Apagar",
      searching: "Buscando...",
      openPost: "↗ Abrir post", openItem: "↗ Abrir item",
      warnNoName: "<strong>Modelo de título</strong> sem <code>{name}</code> -- o nome do asset não aparecerá no título.",
      warnBadUrl: "<strong>Modelo de corpo</strong>: <code>{url}</code> deve estar em uma linha sozinho.",
      kofiCardMsg: "Gostando do Asset Hunter? Considere apoiar!",
      kofiModalTitle: "Antes de fechar isso...",
      kofiModalBody: "O Asset Hunter é gratuito e exigiu muito esforço, tempo e sanidade para ser feito. Doações nunca são necessárias, mas por favor considere apoiar se ele te ajudou.",
      kofiKeepBtn: "Vou considerar",
      kofiCloseBtn: "Fechar mesmo assim",
      kofiDontAsk: "Não perguntar novamente",
      labelAutoSearch: "Busca automática ao carregar a página", hintAutoSearch: "Buscar automaticamente no Ripper.Store ao abrir uma página de item",
      manualSearchBtn: "Buscar no Ripper.Store",
      supTitle: "Top Apoiadores",
      supSubtitle: "Doe qualquer valor para ter seu nome aqui",
      supEmpty: "Nenhuma doação ainda.",
      supEmptySub: "Seja o primeiro -- seu nome aparecerá bem aqui.",
      supLoadErr: "Não foi possível carregar os dados de apoiadores.",
      // Bump
      bumpBtn: "Bump", bumpBtnSending: "Enviando...", bumpBtnDone: "Enviado!",
      bumpBtnErr: "Falhou",
      bumpAllBtn: "Bump em todos não resolvidos",
      labelBumpMessage: "Mensagem de Bump", hintBumpMessage: "O texto postado quando você clica em Bump em um resultado",
      bumpProgressRunning: "{done}/{total} bumpados{failed} · ~{secs}s restantes",
      bumpProgressFailed: " ({n} falhou)",
      bumpProgressDone: "Feito -- {bumped} bumpados{failed}",
      bumpProgressDoneFailed: ", {n} falhou",
      warnModalTitle: "Verifique seus modelos", warnModalOk: "Entendido",
      changelogBadgeNew: "NOVO",
      // Changelog
      changelogBtn: "Atualizações",
      changelogTitle: "Asset Hunter foi atualizado!",
      changelogSubtitle: "aqui estão as mudanças:",
      changelogSectionFixes: "Correções",
      changelogSectionAdditions: "Adições",
      changelogSectionRemovals: "Removidos",
      changelogSectionInfo: "Informação",
      changelogSectionTips: "Dica",
      changelogClose: "Entendi",
      changelogKofiTitle: "Gostando do Asset Hunter?",
      changelogKofiSub: "Doe no Ko-fi para apoiar as atualizações e sugerir novas funções",
      // Bump topic popup
      bumpTopicTitle: "Ajude o Asset Hunter a aparecer!",
      bumpTopicBody: "Considere dar bump no tópico principal do Asset Hunter, assim mais pessoas podem encontrar e usar a ferramenta!",
      bumpTopicSub: "Clicar em \"Claro!\" vai dar bump no tópico principal automaticamente. Se não quiser, é só clicar na outra opção.",
      bumpTopicYes: "Claro!",
      bumpTopicNo: "Não, obrigado",
      bumpTopicSending: "Dando bump...",
      bumpTopicDone: "Bump feito! Valeu!",
      bumpTopicErr: "Falha ao dar bump",
      // Kofi random popup
      kofiPopupTitle: "Gostando do Asset Hunter?",
      kofiPopupBody: "O Asset Hunter é gratuito, mas manter ele atualizado dá trabalho de verdade. Se foi útil pra você, considere me pagar um café, significa muito e ajuda a manter a ferramenta viva!",
      kofiPopupBtn: "&#9749; Apoiar no Ko-fi",
      kofiPopupDismiss: "Quem sabe depois",
      // Novas strings de configuração
      labelStartMinimized: "Iniciar minimizado", hintStartMinimized: "Abrir o painel minimizado por padrão",
      secActiveSites: "Plataformas ativas", hintActiveSites: "Desmarque para desativar o script em uma plataforma específica",
      labelDefaultSearchMode: "Modo de busca padrão", hintDefaultSearchMode: "Escolha se a busca usa o nome ou a URL completa por padrão",
      searchModeToggle_name: "Modo: Nome", searchModeToggle_url: "Modo: URL",
      matchModeToggle_any: "Palavras: qualquer", matchModeToggle_all: "Palavras: todas",
      labelMinimizeHotkey: "Atalho de teclado", hintMinimizeHotkey: "Teclas para abrir/fechar o painel. Exemplo: Alt+H",
      hotkeyPlaceholder: "ex. Alt+H",
      // Changelog entries -- 6.4.1
      "changelog.fix.frenchTranslations": "Corrigidos pequenos erros na tradução francesa.",
      // Changelog entries -- 6.4.0
      "changelog.add.frenchLang": "Adicionado suporte ao idioma francês.",
      "changelog.add.itchio": "Adicionado suporte ao itch.io.",
      "changelog.add.siteFilter": "Adicionados controles por plataforma nas configurações para desativar o script em sites específicos.",
      "changelog.add.startMinimized": "Adicionada configuração para iniciar o painel minimizado por padrão. O banner Ko-fi também se oculta quando minimizado.",
      "changelog.add.urlSearchMode": "Adicionado botão de modo URL para buscar pela URL completa em vez do nome detectado. Configuração de modo padrão disponível.",
      "changelog.add.minimizeHotkey": "Adicionado atalho de teclado personalizável para abrir/fechar o painel.",
      "changelog.add.matchWordsToggle": "Adicionado o botão Palavras: qualquer / Palavras: todas ao lado do botão de modo. Com Todas, a busca exige que todos os termos estejam no post, filtrando resultados sem relação.",
      "changelog.tip.searchWorkaround": "Apareceram muitos resultados sem relação? Os dois novos botões acima da barra de busca podem ajudar. Mude para Palavras: todas para exigir todos os termos no post, ou use Modo: URL para buscar pelo endereço completo da página em vez do nome do produto. Sao contornos de uma limitacao fundamental do sistema de busca do Ripper.Store -- nao existe solucao perfeita, mas geralmente melhora bastante.",
      // Changelog entries -- 6.3.0
      "changelog.add.bumpTopicPopup": "Adicionado um prompt de bump que aparece após os changelogs.",
      "changelog.add.kofiRandomPopup": "Adicionado um popup de doação com chance de 1 em 10 de aparecer ao carregar a página.",
      "changelog.fix.kofiThreshold": "O checkbox \"não perguntar novamente\" no banner do Ko-fi agora aparece após 2 fechamentos em vez de 5",
      // Changelog entries -- 6.2.0
      "changelog.info.bumpSetting": "A mensagem de bump pode ser personalizada nas Configurações, dá uma olhada lá",
      "changelog.add.bumpButton": "Adicionados botoes Bump nos cards de resultado e itens da lista de observacao, permite postar uma resposta direto pelo Asset Hunter",
      "changelog.add.bumpSetting": "Adicionada a configuracao de Mensagem de Bump para personalizar o que e postado quando voce faz bump (padrao: \"bump\")",
      "changelog.add.bumpWatchlist": "Botoes de Bump agora aparecem nos itens da lista de observacao que tem um topico do forum vinculado e nao estao marcados como baixados",
      "changelog.add.bumpAll": "Adicionado o botao Bump em Todos na aba da lista de observacao, coloca todos os topicos elegiveis em fila e os bumpa um por um com 11 segundos de intervalo e indicador de progresso",
      "changelog.add.postCooldown": "Adicionado um cooldown de 10 segundos, apos qualquer postagem bem-sucedida todos os botoes de postagem em todas as abas ficam bloqueados ate o cooldown acabar",
      "changelog.add.crossTabCooldown": "O cooldown de postagem agora e compartilhado entre todas as abas abertas em tempo real, postar em uma aba bloqueia os botoes em todas as outras imediatamente",
      "changelog.remove.autoUpdate": "Removida a configuracao de atualizacao automatica da lista de observacao e seu temporizador, nao estava funcionando corretamente e pode ser re-adicionada no futuro",
      // Changelog entries -- 6.1.2
      "changelog.fix.nameParsingDash": "Corrigido o corte de nomes de assets com traço, produtos como \"potato - for whatever\" agora usam o nome completo em vez de só \"potato\"",
      // Changelog entries -- 6.1.1
      "changelog.fix.boothSanitizationRemoved": "Removida a função de sanitização de URL do Booth introduzida na 6.1.0. Ela não funcionou como planejado e foi completamente revertida",
      "changelog.fix.previewBtnRemoved": "Removido o botão Pré-visualizar no Site do modal de LF, ele era desnecessário",
      // Changelog entries -- 6.1.0
      "changelog.fix.boothLink": "Links do Booth com subdomínio (como user.booth.pm/items/...) eram convertidos automaticamente para a URL canônica limpa (booth.pm/items/...) ao postar",
      "changelog.add.changelogPopup": "Adicionado este popup de atualizações! Aparece uma vez após cada atualização, e você pode abrir de novo pela aba de Configurações",
      "changelog.add.changelogBtn": "Adicionado o botão de Atualizações na aba de Configurações, pra você nunca perder o que mudou",
    },
    fr: {
      title: "ASSET HUNTER", minimize: "Réduire", expand: "Agrandir", unknown: "Inconnu", search: "Rechercher",
      placeholder: "Recherche...", noResult: "Aucun résultat",
      hits: " résultats", solved: "Résolu", unsolved: "Ouvert", untitled: "Sans titre",
      dlFound: "Téléchargement trouvé", secDiscussions: "Discussions", secOther: "Autres",
      errParse: "Échec de l'analyse", errNetwork: "Erreur réseau", errTimeout: "Délai dépassé",
      errPostFailed: "HTTP {status} : Échec de la publication. Vérifiez que vous êtes connecté.",
      errReplyFailed: "HTTP {status} : Échec de la réponse. Vérifiez que vous êtes connecté à forum.ripper.store.",
      lfBtn: "Poster une demande LF", lfTitle: "Demande LF", lfNotFound: "Introuvable sur Ripper. Faire une demande ?",
      lfPost: "Publier", lfPosting: "Publication...",
      lfSuccess: "Publié !", lfViewPost: "Voir le post ->", lfLoginWarn: "Vous devez être connecté sur forum.ripper.store.",
      openRipper: "Ouvrir Ripper.Store",
      watchlist: "Suivi", addWatch: "Ajouter au suivi", inWatch: "Suivi",
      noWatch: "Aucun élément dans la liste", recheck: "Tout revérifier", settings: "Paramètres",
      langLabel: "Langue",
      secLfTemplates: "Modèles de posts LF", secBehaviour: "Comportement", secDataMgmt: "Gestion des données",
      labelTitleTpl: "Titre du modèle", hintTitleTpl: "Utilisez <code>{name}</code> pour le nom de l'asset",
      labelBodyTpl: "Corps du modèle", hintBodyTpl: "Utilisez <code>{name}</code> pour le nom, <code>{url}</code> pour le lien",
      labelDefaultTags: "Tags par défaut", hintDefaultTags: "Séparés par des virgules -- le tag de la plateforme (booth, gumroad, etc.) et les tags intelligents sont ajoutés automatiquement",
      labelInterval: "Intervalle", intervalEvery: "Toutes les", intervalMin: "minutes",
      labelAutoWatch: "Suivre les posts automatiquement", hintAutoWatch: "Suivre vos posts LF automatiquement pour être notifié des réponses",
      labelAutoUpdate: "Mise à jour automatique", hintAutoUpdate: "Revérifier automatiquement tous les éléments de la liste",
      wmLabel: "Filigrane -- toujours ajouté, non modifiable",
      btnSave: "Enregistrer", btnExport: "Exporter les données", btnImport: "Importer les données",
      btnResetDef: "Rétablir les valeurs par défaut", btnDeleteData: "Supprimer les données", btnReset: "↺ Réinitialiser",
      savedMsg: "✓ Enregistré",
      lfLabelTitle: "Titre", lfLabelCategory: "Catégorie", lfLabelTags: "Tags",
      lfLabelTagsHint: "(séparés par des virgules)", lfLabelContent: "Contenu",
      lfLabelContentHint: "(Markdown -- filigrane ajouté automatiquement)",
      btnCancel: "Annuler",
      importTitle: "Importer les données", importDrop: "Déposez votre fichier JSON ici", importDropSub: "ou cliquez pour parcourir",
      importOk: "✓ Importé avec succès !", importErr: "Échec de l'analyse JSON -- fichier d'export valide ?",
      importInvalid: "Veuillez déposer un fichier .json valide.",
      lfErrTitle: "Veuillez entrer un titre.", lfErrContent: "Veuillez entrer un contenu.",
      lfConnecting: "Connexion à Ripper.Store...",
      modalResetTitle: "Rétablir les valeurs par défaut", modalResetMsg: "Tous les paramètres seront réinitialisés. Votre liste de suivi ne sera pas affectée.",
      modalResetProceed: "Réinitialiser",
      modalDeleteTitle: "Supprimer la liste de suivi", modalDeleteMsg: "Tous les éléments de la liste seront définitivement supprimés. Cette action est irréversible.",
      modalDeleteProceed: "Supprimer",
      searching: "Recherche...",
      openPost: "↗ Ouvrir le post", openItem: "↗ Ouvrir l'élément",
      warnNoName: "<strong>Titre du modèle</strong> sans <code>{name}</code> -- le nom de l'asset n'apparaîtra pas dans le titre.",
      warnBadUrl: "<strong>Corps du modèle</strong> : <code>{url}</code> doit être seul sur sa ligne.",
      kofiCardMsg: "Vous aimez Asset Hunter ? Pensez à soutenir !",
      kofiModalTitle: "Avant de fermer...",
      kofiModalBody: "Asset Hunter est gratuit et a demandé beaucoup de travail. Les dons ne sont pas obligatoires, mais pensez à soutenir si l'outil vous a aidé.",
      kofiKeepBtn: "Je vais y réfléchir",
      kofiCloseBtn: "Fermer quand même",
      kofiDontAsk: "Ne plus demander",
      labelAutoSearch: "Recherche automatique au chargement", hintAutoSearch: "Rechercher automatiquement sur Ripper.Store à l'ouverture d'une page d'article",
      manualSearchBtn: "Rechercher sur Ripper.Store",
      supTitle: "Meilleurs soutiens",
      supSubtitle: "Faites un don de n'importe quel montant pour voir votre nom ici",
      supEmpty: "Aucun don pour le moment.",
      supEmptySub: "Soyez le premier -- votre nom apparaîtra ici.",
      supLoadErr: "Impossible de charger les données des soutiens.",
      // Bump
      bumpBtn: "Bump", bumpBtnSending: "Envoi...", bumpBtnDone: "Envoyé !",
      bumpBtnErr: "Échec",
      bumpAllBtn: "Bumper tous les non résolus",
      labelBumpMessage: "Message de bump", hintBumpMessage: "Le texte publié quand vous cliquez sur Bump",
      bumpProgressRunning: "{done}/{total} bumpé(s){failed} · ~{secs}s restantes",
      bumpProgressFailed: " ({n} échoué(s))",
      bumpProgressDone: "Terminé -- {bumped} bumpé(s){failed}",
      bumpProgressDoneFailed: ", {n} échoué(s)",
      warnModalTitle: "Vérifiez vos modèles", warnModalOk: "Compris",
      changelogBadgeNew: "NOUVEAU",
      // Changelog
      changelogBtn: "Historique",
      changelogTitle: "Asset Hunter a été mis à jour !",
      changelogSubtitle: "voici les changements :",
      changelogSectionFixes: "Corrections",
      changelogSectionAdditions: "Ajouts",
      changelogSectionRemovals: "Suppressions",
      changelogSectionInfo: "Informations",
      changelogSectionTips: "Conseil",
      changelogClose: "Compris",
      changelogKofiTitle: "Vous aimez Asset Hunter ?",
      changelogKofiSub: "Soutenez sur Ko-fi pour contribuer aux mises à jour et suggérer de nouvelles fonctions",
      // Bump topic popup
      bumpTopicTitle: "Rendre Asset Hunter plus visible !",
      bumpTopicBody: "Pensez à bumper le sujet principal d'Asset Hunter pour que plus de monde puisse le découvrir !",
      bumpTopicSub: "Cliquer sur \"Bien sûr !\" va bumper le sujet principal automatiquement. Si vous ne le souhaitez pas, cliquez l'autre bouton.",
      bumpTopicYes: "Bien sûr !",
      bumpTopicNo: "Non merci",
      bumpTopicSending: "Envoi...",
      bumpTopicDone: "Bumpé ! Merci !",
      bumpTopicErr: "Échec du bump",
      // Kofi random popup
      kofiPopupTitle: "Vous aimez Asset Hunter ?",
      kofiPopupBody: "Asset Hunter est gratuit, mais le maintenir demande de vrais efforts. Si l'outil vous a été utile, pensez à me payer un café, ça aide vraiment !",
      kofiPopupBtn: "&#9749; Soutenir sur Ko-fi",
      kofiPopupDismiss: "Peut-être plus tard",
      // Paramètres
      labelStartMinimized: "Démarrer minimisé", hintStartMinimized: "Ouvrir le panneau minimisé par défaut",
      secActiveSites: "Plateformes actives", hintActiveSites: "Décochez pour désactiver le script sur une plateforme",
      labelDefaultSearchMode: "Mode de recherche par défaut", hintDefaultSearchMode: "Choisir si la recherche utilise le nom ou l'URL complète par défaut",
      searchModeToggle_name: "Mode : Nom", searchModeToggle_url: "Mode : URL",
      matchModeToggle_any: "Mots : partiel", matchModeToggle_all: "Mots : tous",
      labelMinimizeHotkey: "Raccourci clavier", hintMinimizeHotkey: "Combinaison de touches pour réduire/agrandir le panneau. Exemple : Alt+H",
      hotkeyPlaceholder: "ex. Alt+H",
      // Changelog entries -- 6.4.1
      "changelog.fix.frenchTranslations": "Correction de petites erreurs de traduction.",
      // Changelog entries -- 6.4.0
      "changelog.add.frenchLang": "Ajout de la langue française.",
      "changelog.add.itchio": "Ajout de la prise en charge d'itch.io.",
      "changelog.add.siteFilter": "Ajout d'une option dans les paramètres pour désactiver le script sur des plateformes spécifiques.",
      "changelog.add.startMinimized": "Ajout d'un paramètre pour démarrer le panneau minimisé par défaut. Le bandeau Ko-fi se masque aussi en mode minimisé.",
      "changelog.add.urlSearchMode": "Ajout d'un bouton de mode URL pour rechercher par URL complète plutôt que par nom. Paramètre de mode par défaut disponible dans les paramètres.",
      "changelog.add.minimizeHotkey": "Ajout d'un raccourci clavier personnalisable pour réduire/agrandir le panneau.",
      "changelog.add.matchWordsToggle": "Ajout d'un bouton Mots : partiel / Mots : tous à côté du bouton de mode. Passer à Tous exige que chaque mot de la recherche apparaisse dans le post, réduisant les résultats sans rapport.",
      "changelog.tip.searchWorkaround": "Des résultats sans rapport envahissent la liste ? Les deux nouveaux boutons au-dessus de la barre de recherche peuvent aider. Passez à Mots : tous pour exiger chaque mot dans les posts, ou utilisez Mode : URL pour chercher par l'adresse complète de la page plutôt que par le nom du produit. Ce sont des contournements d'une limitation fondamentale du moteur de recherche de Ripper.Store -- il n'y a pas de solution parfaite, mais ca ameliore generalement beaucoup les resultats.",
      // Changelog entries -- 6.3.0
      "changelog.add.bumpTopicPopup": "Ajout d'une invite de bump qui apparaît après les changelogs.",
      "changelog.add.kofiRandomPopup": "Ajout d'un rappel de don avec 1 chance sur 10 d'apparaître au chargement de la page.",
      "changelog.fix.kofiThreshold": "La case \"Ne plus demander\" sur le bandeau Ko-fi apparaît maintenant après 2 fermetures au lieu de 5.",
      // Changelog entries -- 6.2.0
      "changelog.info.bumpSetting": "Le message de bump est personnalisable dans les paramètres.",
      "changelog.add.bumpButton": "Ajout de boutons Bump sur les cartes de résultats et les éléments de la liste de suivi.",
      "changelog.add.bumpSetting": "Ajout d'un paramètre de message de bump pour personnaliser le texte (par défaut : \"bump\").",
      "changelog.add.bumpWatchlist": "Les boutons Bump apparaissent sur les éléments de la liste avec un sujet lié et non marqués comme téléchargés.",
      "changelog.add.bumpAll": "Ajout d'un bouton Bumper tous dans l'onglet liste de suivi, avec indicateur de progression.",
      "changelog.add.postCooldown": "Ajout d'un délai de 10 secondes après chaque publication, partagé entre tous les onglets ouverts.",
      "changelog.add.crossTabCooldown": "Le délai de publication est synchronisé entre tous les onglets en temps réel.",
      "changelog.remove.autoUpdate": "Suppression du paramètre de mise à jour automatique de la liste de suivi.",
      // Changelog entries -- 6.1.2
      "changelog.fix.nameParsingDash": "Correction du découpage des noms d'assets au niveau des tirets.",
      // Changelog entries -- 6.1.1
      "changelog.fix.boothSanitizationRemoved": "Suppression de la normalisation des URL Booth introduite en 6.1.0.",
      "changelog.fix.previewBtnRemoved": "Suppression du bouton Aperçu du modal de post LF.",
      // Changelog entries -- 6.1.0
      "changelog.fix.boothLink": "Les liens Booth avec sous-domaine étaient automatiquement convertis en URL canonique lors de la publication.",
      "changelog.add.changelogPopup": "Ajout de ce popup d'historique des changements.",
      "changelog.add.changelogBtn": "Ajout d'un bouton Historique dans l'onglet Paramètres.",
    },
  };

  const LANG_OPTIONS = [
    { value: "en",    label: "English" },
    { value: "ja",    label: "日本語" },
    { value: "ru",    label: "Русский" },
    { value: "pt-BR", label: "Português (BR)" },
    { value: "fr",    label: "Français" },
  ];

  let currentLang = (function() {
    const saved = GM_getValue("ah-cfg-lang", null);
    return (saved && STRINGS[saved]) ? saved : "en";
  })();
  function t(key) { return (STRINGS[currentLang] || STRINGS.en)[key] || key; }

  // ─── Migrate stale default cid ────────────────────────────────────────────
  (function migrateCid() {
    const saved = GM_getValue("bs-lf-cid", null);
    if (saved === null || saved === 2 || saved === 3) GM_setValue("bs-lf-cid", 42);
  })();

  // ─── Migrate old defaultTags ──────────────────────────────────────────────
  (function migrateDefaultTags() {
    const saved = GM_getValue("ah-cfg-defaultTags", null);
    if (saved === null) return;
    const tags = saved.split(",").map(s => s.trim().toLowerCase()).filter(Boolean);
    let changed = false;
    const withoutBooth = tags.filter(t => t !== "booth");
    if (withoutBooth.length !== tags.length) changed = true;
    if (!withoutBooth.includes("asset-hunter")) {
      withoutBooth.push("asset-hunter");
      changed = true;
    }
    if (changed) GM_setValue("ah-cfg-defaultTags", withoutBooth.join(", "));
  })();

  GM_registerMenuCommand("☠ LF Post Category ID", () => {
    const cur = GM_getValue("bs-lf-cid", 42);
    const input = prompt(
      "Enter the category ID for LF/Request posts on forum.ripper.store\n(Check the URL when browsing that category, e.g. /category/42)",
      cur
    );
    const n = parseInt(input, 10);
    if (!isNaN(n) && n > 0) { GM_setValue("bs-lf-cid", n); alert(`Category ID set to ${n}. Reload.`); }
  });

  // ─── Helpers ──────────────────────────────────────────────────────────────
  function esc(s) { const d = document.createElement("div"); d.textContent = String(s); return d.innerHTML; }
  function dec(s) { const d = document.createElement("textarea"); d.innerHTML = s; return d.value; }

  function stripHTML(html) {
    const tmp = document.createElement("div");
    tmp.innerHTML = html;
    return tmp.textContent || tmp.innerText || "";
  }

  // ─── Active match mode (toggled per session) ─────────────────────────────
  let _matchWords = "any";

  // ─── Core API Calls ───────────────────────────────────────────────────────
  function doSearch(query, cb) {
    GM_xmlhttpRequest({
      method: "GET",
      url: API_URL.replace("{query}", encodeURIComponent(query)).replace("{matchWords}", _matchWords),
      responseType: "json",
      timeout: 12000,
      onload: (r) => {
        try {
          const d = typeof r.response === "string" ? JSON.parse(r.response) : r.response;
          cb(null, d);
        } catch(e) { cb(t("errParse")); }
      },
      onerror:  () => cb(t("errNetwork")),
      ontimeout: () => cb(t("errTimeout")),
    });
  }

  const URL_EXTRACT_PATTERNS = [
    /https?:\/\/mega\.nz\/[^\s"'<>)]+/gi,
    /https?:\/\/mega\.io\/[^\s"'<>)]+/gi,
    /https?:\/\/(?:www\.)?mediafire\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/drive\.google\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/gofile\.io\/[^\s"'<>)]+/gi,
    /https?:\/\/pixeldrain\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/workupload\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/1fichier\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/(?:www\.)?dropbox\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/onedrive\.live\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/terabox\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/bowfile\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/forum\.ripper\.store\/hidelinks\/r\/[^\s"'<>)]+/gi,
    /https?:\/\/forum\.ripper\.store\/[^\s"'<>)]*hidelinks[^\s"'<>)]*/gi,
    /https?:\/\/1cloudfile\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/archive\.org\/download\/[^\s"'<>)]+/gi,
    /https?:\/\/anonfile\.la\/[^\s"'<>)]+/gi,
    /https?:\/\/app\.bunkrr\.su\/[^\s"'<>)]+/gi,
    /https?:\/\/buzzheavier\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/clicknupload\.click\/[^\s"'<>)]+/gi,
    /https?:\/\/cyberfile\.me\/[^\s"'<>)]+/gi,
    /https?:\/\/dailyuploads\.net\/[^\s"'<>)]+/gi,
    /https?:\/\/datanodes\.to\/[^\s"'<>)]+/gi,
    /https?:\/\/disk\.yandex\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/fastupload\.io\/[^\s"'<>)]+/gi,
    /https?:\/\/filebin\.net\/[^\s"'<>)]+/gi,
    /https?:\/\/fileditch\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/filepost\.io\/[^\s"'<>)]+/gi,
    /https?:\/\/files\.fm\/[^\s"'<>)]+/gi,
    /https?:\/\/filetransfer\.io\/[^\s"'<>)]+/gi,
    /https?:\/\/fuckingfast\.net\/[^\s"'<>)]+/gi,
    /https?:\/\/hexload\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/mixdrop\.ag\/[^\s"'<>)]+/gi,
    /https?:\/\/send\.cm\/[^\s"'<>)]+/gi,
    /https?:\/\/terminal\.lc\/[^\s"'<>)]+/gi,
    /https?:\/\/transfer\.it\/[^\s"'<>)]+/gi,
    /https?:\/\/uploadfile\.pl\/[^\s"'<>)]+/gi,
    /https?:\/\/uploadhaven\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/uploadnow\.io\/[^\s"'<>)]+/gi,
    /https?:\/\/wdho\.ru\/[^\s"'<>)]+/gi,
    /https?:\/\/(?:www\.)?wetransfer\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/axfc\.net\/[^\s"'<>)]+/gi,
    /https?:\/\/filemail\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/(?:www\.)?sendspace\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/swisstransfer\.com\/[^\s"'<>)]+/gi,
    /https?:\/\/zippyshare\.day\/[^\s"'<>)]+/gi,
  ];

  function extractFirstURL(text) {
    for (const pat of URL_EXTRACT_PATTERNS) {
      pat.lastIndex = 0;
      const m = pat.exec(text);
      if (m) return m[0].replace(/[.,;!?)]+$/, "");
    }
    return null;
  }

  function checkDL(tid, cb) {
    GM_xmlhttpRequest({
      method: "GET",
      url: TOPIC_API.replace("{tid}", tid),
      responseType: "json",
      timeout: 8000,
      onload: (r) => {
        try {
          const d = typeof r.response === "string" ? JSON.parse(r.response) : r.response;
          for (const p of (d.posts || [])) {
            const htmlContent = p.content || "";
            const rawContent  = p.rawContent || "";
            if (DL_PATTERNS.some(x => x.test(rawContent))) return cb(true);
            if (htmlContent) {
              const plain = stripHTML(htmlContent);
              if (DL_PATTERNS.some(x => x.test(plain))) return cb(true);
              const tmp = document.createElement("div");
              tmp.innerHTML = htmlContent;
              const anchors = tmp.querySelectorAll("a[href]");
              for (const a of anchors) {
                const href = a.getAttribute("href") || "";
                if (DL_PATTERNS.some(x => x.test(href))) return cb(true);
              }
            }
            if (p.attachments && p.attachments.length) return cb(true);
          }
          cb(false);
        } catch(e) { cb(false); }
      },
      onerror:  () => cb(false),
      ontimeout: () => cb(false),
    });
  }

  // ─── LF Post API ──────────────────────────────────────────────────────────
  function getCSRFToken(cb) {
    GM_xmlhttpRequest({
      method: "GET",
      url: CONFIG_API,
      responseType: "json",
      timeout: 8000,
      onload: (r) => {
        try {
          const d = typeof r.response === "string" ? JSON.parse(r.response) : r.response;
          cb(null, d.csrf_token || d["csrf-token"] || d.csrfToken || "");
        } catch(e) { cb("Failed to get CSRF token"); }
      },
      onerror:  () => cb("Network error getting CSRF"),
      ontimeout: () => cb("Timeout getting CSRF"),
    });
  }

  function postLFTopic(title, content, tags, cid, cb) {
    getCSRFToken((err, csrf) => {
      if (err) return cb(err);
      GM_xmlhttpRequest({
        method: "POST",
        url: POST_API,
        headers: { "Content-Type": "application/json", "x-csrf-token": csrf },
        data: JSON.stringify({ cid: parseInt(cid, 10), title, content, tags }),
        responseType: "json",
        timeout: 20000,
        onload: (r) => {
          try {
            const d = typeof r.response === "string" ? JSON.parse(r.response) : r.response;
            if (r.status === 200 && d && (d.tid || (d.response && d.response.tid))) {
              cb(null, d);
            } else if (d && d.status && d.status.code === "ok") {
              cb(null, d);
            } else {
              cb((d && d.status && d.status.message) || (d && d.message) ||
                 t("errPostFailed").replace("{status}", r.status));
            }
          } catch(e) { cb("Failed to parse post response"); }
        },
        onerror:  () => cb("Network error while posting"),
        ontimeout: () => cb("Post request timed out"),
      });
    });
  }

  function postReply(tid, content, cb) {
    getCSRFToken((err, csrf) => {
      if (err) return cb(err);
      GM_xmlhttpRequest({
        method: "POST",
        url: POST_REPLY_API.replace("{tid}", tid),
        headers: { "Content-Type": "application/json", "x-csrf-token": csrf },
        data: JSON.stringify({ content }),
        responseType: "json",
        timeout: 15000,
        onload: (r) => {
          try {
            const d = typeof r.response === "string" ? JSON.parse(r.response) : r.response;
            // NodeBB v3 API: success is indicated by status.code === "ok" OR a pid in the response
            const isOk = (d && d.status && d.status.code === "ok") ||
                         (r.status >= 200 && r.status < 300 && d && (
                           d.pid ||
                           (d.response && d.response.pid) ||
                           (d.payload && d.payload.pid)
                         ));
            if (isOk) {
              cb(null, d);
            } else {
              cb((d && d.status && d.status.message) || (d && d.message) ||
                 t("errReplyFailed").replace("{status}", r.status));
            }
          } catch(e) { cb("Failed to parse reply response"); }
        },
        onerror:  () => cb("Network error while posting reply"),
        ontimeout: () => cb("Reply request timed out"),
      });
    });
  }

  function watchTopic(tid, csrf) {
    GM_xmlhttpRequest({
      method: "PUT",
      url: `${SITE_URL}/api/v3/topics/${tid}/follow`,
      headers: { "Content-Type": "application/json", "x-csrf-token": csrf },
      data: JSON.stringify({}),
      responseType: "json",
      timeout: 8000,
      onload: (r) => {
        if (r.status !== 200) {
          GM_xmlhttpRequest({
            method: "POST",
            url: `${SITE_URL}/topic/${tid}/follow`,
            headers: { "Content-Type": "application/json", "x-csrf-token": csrf },
            data: JSON.stringify({ tid }),
            responseType: "json", timeout: 8000,
            onload: () => {}, onerror: () => {}, ontimeout: () => {},
          });
        }
      },
      onerror: () => {}, ontimeout: () => {},
    });
  }

  // ─── Forum Category Map ───────────────────────────────────────────────────
  const FORUM_CATEGORIES = [
    { cid: 20, name: "General Discussions",        depth: 0 },
    { cid: 25, name: "Assets",                     depth: 0 },
    { cid: 28, name: "Looking for...",             depth: 1 },
    { cid: 29, name: "General Assets",             depth: 2 },
    { cid: 31, name: "Found Avatars & Assets",     depth: 2 },
    { cid: 33, name: "Booth Avatars",              depth: 2 },
    { cid: 34, name: "Gumroad/Payhip Avatars",     depth: 2 },
    { cid: 35, name: "Furry Avatars",              depth: 2 },
    { cid: 36, name: "NSFW",                       depth: 2 },
    { cid: 37, name: "Scripts & Tools",            depth: 2 },
    { cid: 38, name: "Clothes",                    depth: 2 },
    { cid: 39, name: "Hair",                       depth: 2 },
    { cid: 40, name: "Textures",                   depth: 2 },
    { cid: 41, name: "Uncategorized",              depth: 2 },
    { cid: 42, name: "Other Assets",               depth: 2 },
    { cid: 43, name: "Worlds",                     depth: 2 },
    { cid: 47, name: "Live2D",                     depth: 2 },
    { cid: 44, name: "Gifts / Downloads",          depth: 1 },
  ];

  const GIFTS_CIDS = new Set([44]);

  function isGiftsCategory(category) {
    const catName = dec((category && category.name) || "").toLowerCase();
    return catName.includes("gifts") || catName.includes("downloads") || GIFTS_CIDS.has(category && category.cid);
  }

  function getAutoTags(name) {
    const base  = getSetting("defaultTags").split(",").map(s => s.trim().toLowerCase()).filter(Boolean);
    const n     = (name || "").toLowerCase();
    const extra = [];
    if (HOST === "booth.pm" || HOST.endsWith(".booth.pm"))       extra.push("booth");
    else if (HOST === "gumroad.com" || HOST.endsWith(".gumroad.com")) extra.push("gumroad");
    else if (HOST === "jinxxy.com" || HOST.endsWith(".jinxxy.com"))   extra.push("jinxxy");
    else if (HOST === "payhip.com" || HOST.endsWith(".payhip.com"))   extra.push("payhip");
    else if (HOST === "itch.io"    || HOST.endsWith(".itch.io"))      extra.push("itch");
    if (/avatar|アバター/.test(n))                   extra.push("avatar");
    if (/cloth|clothes|outfit|wear|衣装|服/.test(n)) extra.push("clothing");
    if (/hair|髪/.test(n))                            extra.push("hair");
    if (/access|アクセ/.test(n))                      extra.push("accessory");
    if (/shader|シェーダー/.test(n))                   extra.push("shader");
    if (/vrchat|vrc/.test(n))                         extra.push("vrchat");
    if (/unity/.test(n))                              extra.push("unity");
    if (/prop|武器|weapon/.test(n))                    extra.push("prop");
    return [...new Set([...base, ...extra])].slice(0, 8);
  }

  // ─── Watchlist Helpers ────────────────────────────────────────────────────
  function wlGet()       { try { return JSON.parse(GM_getValue("bs-watchlist", "[]")); } catch(e) { return []; } }
  function wlSave(list)  { GM_setValue("bs-watchlist", JSON.stringify(list)); }
  function wlAdd(item)   { const l = wlGet(); if (!l.find(x => x.url === item.url)) { l.push(item); wlSave(l); } }
  function wlRemove(url) { wlSave(wlGet().filter(x => x.url !== url)); }

  function timeAgo(ts) {
    if (!ts) return "";
    const s = Math.floor((Date.now() - ts) / 1000);
    if (s < 60)   return `${s}s ago`;
    if (s < 3600) return `${Math.floor(s / 60)}m ago`;
    return `${Math.floor(s / 3600)}h ago`;
  }

  // ─── Donors API ───────────────────────────────────────────────────────────
  function fetchDonors(cb) {
    GM_xmlhttpRequest({
      method: "GET",
      url: DONORS_URL,
      headers: {
        "Accept": "application/vnd.github.v3.raw",
        "Cache-Control": "no-cache",
      },
      timeout: 10000,
      onload: (r) => {
        try {
          const lines = r.responseText.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
          const map = {};
          for (const line of lines) {
            const idx = line.lastIndexOf(":");
            if (idx === -1) continue;
            const name   = line.slice(0, idx).trim();
            const amount = parseFloat(line.slice(idx + 1).trim());
            if (!name || isNaN(amount) || amount <= 0) continue;
            const key = name.toLowerCase();
            if (!map[key]) map[key] = { name, total: 0 };
            map[key].total += amount;
          }
          const sorted = Object.values(map).sort((a, b) => b.total - a.total);
          cb(null, sorted);
        } catch(e) { cb("Failed to parse donors"); }
      },
      onerror:  () => cb("Network error"),
      ontimeout: () => cb("Timeout"),
    });
  }

  let _recheckFn = null;

  // ─── Changelog Modal ──────────────────────────────────────────────────────
  function showChangelogModal(isForced) {
    document.getElementById("ah-changelog-modal")?.remove();

    const modal = document.createElement("div");
    modal.id = "ah-changelog-modal";

    function renderVersionBlock(entry, isLatest) {
      const fixes     = (entry.fixes     || []).map(k => t(k)).filter(Boolean);
      const additions = (entry.additions || []).map(k => t(k)).filter(Boolean);
      const removals  = (entry.removals  || []).map(k => t(k)).filter(Boolean);
      const info      = (entry.info      || []).map(k => t(k)).filter(Boolean);
      const tips      = (entry.tips      || []).map(k => t(k)).filter(Boolean);

      let html = `<div class="ah-cl-version-block${isLatest ? " ah-cl-version-block--latest" : ""}">`;
      html += `<div class="ah-cl-version-tag">
        <span class="ah-cl-version-star">${isLatest ? "✦" : "·"}</span>
        v${esc(entry.version)}
        ${isLatest ? `<span class="ah-cl-version-new">${t("changelogBadgeNew")}</span>` : ""}
      </div>`;

      if (additions.length) {
        html += `<div class="ah-cl-section-label ah-cl-section-label--add">
          <span class="ah-cl-bullet">&#9656;</span> ${t("changelogSectionAdditions")}
        </div>`;
        html += `<ul class="ah-cl-list">`;
        for (const item of additions) {
          html += `<li class="ah-cl-item ah-cl-item--add"><span class="ah-cl-dot">&#9670;</span><span>${esc(item)}</span></li>`;
        }
        html += `</ul>`;
      }

      if (fixes.length) {
        html += `<div class="ah-cl-section-label ah-cl-section-label--fix">
          <span class="ah-cl-bullet">&#9656;</span> ${t("changelogSectionFixes")}
        </div>`;
        html += `<ul class="ah-cl-list">`;
        for (const item of fixes) {
          html += `<li class="ah-cl-item ah-cl-item--fix"><span class="ah-cl-dot">&#9670;</span><span>${esc(item)}</span></li>`;
        }
        html += `</ul>`;
      }

      if (info.length) {
        html += `<div class="ah-cl-section-label ah-cl-section-label--info">
          <span class="ah-cl-bullet">&#9656;</span> ${t("changelogSectionInfo")}
        </div>`;
        html += `<ul class="ah-cl-list">`;
        for (const item of info) {
          html += `<li class="ah-cl-item ah-cl-item--info"><span class="ah-cl-dot">&#9670;</span><span>${esc(item)}</span></li>`;
        }
        html += `</ul>`;
      }

      if (removals.length) {
        html += `<div class="ah-cl-section-label ah-cl-section-label--rem">
          <span class="ah-cl-bullet">&#9656;</span> ${t("changelogSectionRemovals")}
        </div>`;
        html += `<ul class="ah-cl-list">`;
        for (const item of removals) {
          html += `<li class="ah-cl-item ah-cl-item--rem"><span class="ah-cl-dot">&#9670;</span><span>${esc(item)}</span></li>`;
        }
        html += `</ul>`;
      }

      if (tips.length) {
        for (const tip of tips) {
          html += `<div class="ah-cl-tip-card">
            <div class="ah-cl-tip-label">&#9654; ${t("changelogSectionTips")}</div>
            <div class="ah-cl-tip-text">${esc(tip)}</div>
          </div>`;
        }
      }

      html += `</div>`;
      return html;
    }

    const kofiHtml = `<a href="https://ko-fi.com/xedinho" target="_blank" rel="noopener" class="ah-cl-kofi-banner">
      <span class="ah-cl-kofi-icon">&#9749;</span>
      <span class="ah-cl-kofi-text">
        <span class="ah-cl-kofi-title">${t("changelogKofiTitle")}</span>
        <span class="ah-cl-kofi-sub">${t("changelogKofiSub")}</span>
      </span>
      <span class="ah-cl-kofi-arrow">&#8599;</span>
    </a>`;

    let bodyHtml = kofiHtml;
    CHANGELOGS.forEach((entry, idx) => {
      bodyHtml += renderVersionBlock(entry, idx === 0);
    });

    modal.innerHTML = `
      <div class="ah-cl-backdrop"></div>
      <div class="ah-cl-dialog" role="dialog" aria-modal="true">
        <div class="ah-cl-header">
          <div class="ah-cl-header-left">
            <svg class="ah-cl-logo" width="14" height="14" viewBox="0 0 16 16" fill="none">
              <path d="M8 1L9.5 6H15L10.5 9L12 14L8 11L4 14L5.5 9L1 6H6.5L8 1Z"
                stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/>
            </svg>
            <div class="ah-cl-title-block">
              <div class="ah-cl-title">${t("changelogTitle")}</div>
              <div class="ah-cl-subtitle">${t("changelogSubtitle")}</div>
            </div>
          </div>
          <button class="ah-cl-close" id="ah-cl-close-btn">&#10005;</button>
        </div>
        <div class="ah-cl-body">
          ${bodyHtml}
        </div>
        <div class="ah-cl-footer">
          <button class="ah-cl-ok-btn" id="ah-cl-ok-btn">${t("changelogClose")}</button>
        </div>
      </div>`;

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

    const close = () => {
      modal.remove();
      if (isForced) showBumpTopicModal();
    };
    modal.querySelector(".ah-cl-backdrop").addEventListener("click", close);
    modal.querySelector("#ah-cl-close-btn").addEventListener("click", close);
    modal.querySelector("#ah-cl-ok-btn").addEventListener("click", close);
  }

  // ─── Bump Topic Popup ─────────────────────────────────────────────────────
  function showBumpTopicModal() {
    document.getElementById("ah-bumptopic-modal")?.remove();

    const modal = document.createElement("div");
    modal.id = "ah-bumptopic-modal";
    modal.innerHTML = `
      <div class="ah-bt-backdrop"></div>
      <div class="ah-bt-dialog" role="dialog" aria-modal="true">
        <div class="ah-bt-icon-row">
          <svg width="22" height="22" viewBox="0 0 22 22" fill="none">
            <path d="M11 2L13 8H20L14.5 11.5L16.5 18L11 14.5L5.5 18L7.5 11.5L2 8H9L11 2Z"
              stroke="#c8a8ff" stroke-width="1.4" stroke-linejoin="round" fill="rgba(200,168,255,.08)"/>
          </svg>
          <span class="ah-bt-title">${t("bumpTopicTitle")}</span>
        </div>
        <div class="ah-bt-body">${esc(t("bumpTopicBody"))}</div>
        <div class="ah-bt-sub">${esc(t("bumpTopicSub"))}</div>
        <div class="ah-bt-actions">
          <button class="ah-bt-no" id="ah-bt-no">${t("bumpTopicNo")}</button>
          <button class="ah-bt-yes" id="ah-bt-yes">${t("bumpTopicYes")}</button>
        </div>
        <div class="ah-bt-status" id="ah-bt-status"></div>
      </div>`;

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

    const close = () => modal.remove();
    modal.querySelector(".ah-bt-backdrop").addEventListener("click", close);
    modal.querySelector("#ah-bt-no").addEventListener("click", close);

    modal.querySelector("#ah-bt-yes").addEventListener("click", () => {
      const yesBtn = modal.querySelector("#ah-bt-yes");
      const noBtn  = modal.querySelector("#ah-bt-no");
      const status = modal.querySelector("#ah-bt-status");
      yesBtn.disabled = true;
      noBtn.disabled  = true;
      yesBtn.textContent = t("bumpTopicSending");
      const msg = (getSetting("bumpMessage") || "bump").trim() || "bump";
      postReply(String(AH_TOPIC_TID), msg, (err) => {
        if (err) {
          status.textContent = t("bumpTopicErr");
          status.className   = "ah-bt-status ah-bt-status--err";
          yesBtn.disabled = false;
          noBtn.disabled  = false;
          yesBtn.textContent = t("bumpTopicYes");
        } else {
          status.textContent = t("bumpTopicDone");
          status.className   = "ah-bt-status ah-bt-status--ok";
          yesBtn.textContent = "✓";
          setTimeout(close, 2200);
        }
      });
    });
  }

  // ─── Random Ko-fi Popup ───────────────────────────────────────────────────
  function maybeShowKofiRandomPopup() {
    if (GM_getValue("ah-kofi-dont-ask", "0") === "1") return;
    if (Math.random() > 1 / 10) return;
    document.getElementById("ah-kofi-random-modal")?.remove();

    const modal = document.createElement("div");
    modal.id = "ah-kofi-random-modal";
    modal.innerHTML = `
      <div class="ah-kr-backdrop"></div>
      <div class="ah-kr-dialog" role="dialog" aria-modal="true">
        <div class="ah-kr-header">
          <span class="ah-kr-heart">&#9829;</span>
          <span class="ah-kr-title">${t("kofiPopupTitle")}</span>
          <button class="ah-kr-x" id="ah-kr-x">&#10005;</button>
        </div>
        <div class="ah-kr-body">${esc(t("kofiPopupBody"))}</div>
        <div class="ah-kr-actions">
          <button class="ah-kr-dismiss" id="ah-kr-dismiss">${t("kofiPopupDismiss")}</button>
          <a class="ah-kr-kofi-btn" href="https://ko-fi.com/xedinho" target="_blank" rel="noopener">${t("kofiPopupBtn")}</a>
        </div>
      </div>`;

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

    const close = () => modal.remove();
    modal.querySelector(".ah-kr-backdrop").addEventListener("click", close);
    modal.querySelector("#ah-kr-x").addEventListener("click", close);
    modal.querySelector("#ah-kr-dismiss").addEventListener("click", close);
    modal.querySelector(".ah-kr-kofi-btn").addEventListener("click", () => setTimeout(close, 300));
  }

  // ─── Changelog first-run check ────────────────────────────────────────────
  function maybeShowChangelog() {
    const seenVersion = GM_getValue("ah-changelog-seen", "");
    if (seenVersion !== CURRENT_VERSION) {
      GM_setValue("ah-changelog-seen", CURRENT_VERSION);
      setTimeout(() => showChangelogModal(true), 1800);
    }
  }

  // ─── LF Modal ─────────────────────────────────────────────────────────────
  function showLFModal() {
    const adapter = getAdapter();
    const id   = adapter ? adapter.getId()   : "";
    const name = adapter ? adapter.getName() : "";
    const url  = window.location.href;
    const cid  = GM_getValue("bs-lf-cid", 42);

    document.getElementById("ah-lf-modal")?.remove();

    const modal = document.createElement("div");
    modal.id = "ah-lf-modal";
    modal.innerHTML = `
      <div class="ah-lf-backdrop"></div>
      <div class="ah-lf-dialog">
        <div class="ah-lf-dialog-header">
          <div class="ah-lf-dialog-title">
            <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
              <path d="M7 1L8.5 5H13L9.5 7.5L11 11.5L7 9L3 11.5L4.5 7.5L1 5H5.5L7 1Z"
                stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>
            </svg>
            ${t("lfTitle")}
          </div>
          <button class="ah-lf-close">&#10005;</button>
        </div>
        <div class="ah-lf-dialog-body">
          <div class="ah-lf-field">
            <label class="ah-lf-label">${t("lfLabelTitle")}</label>
            <input id="ah-lf-title" type="text" value="${esc(buildTitle(name))}" />
          </div>
          <div class="ah-lf-row-2">
            <div class="ah-lf-field">
              <label class="ah-lf-label">${t("lfLabelCategory")}</label>
              <select id="ah-lf-cid"></select>
            </div>
            <div class="ah-lf-field">
              <label class="ah-lf-label">${t("lfLabelTags")} <span class="ah-lf-hint">${t("lfLabelTagsHint")}</span></label>
              <input id="ah-lf-tags" type="text" value="${esc(getAutoTags(name).join(", "))}" />
            </div>
          </div>
          <div class="ah-lf-field">
            <label class="ah-lf-label">${t("lfLabelContent")} <span class="ah-lf-hint">${t("lfLabelContentHint")}</span></label>
            <textarea id="ah-lf-content" rows="5">${esc(buildBody(name, url).replace(WATERMARK, "").trimEnd())}</textarea>
          </div>
          <div class="ah-lf-notice">
            <svg width="13" height="13" viewBox="0 0 13 13" fill="none">
              <circle cx="6.5" cy="6.5" r="5.5" stroke="currentColor" stroke-width="1.2"/>
              <path d="M6.5 5.5V9.5M6.5 3.5H6.51" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
            </svg>
            ${t("lfLoginWarn")}
            <a href="${SITE_URL}" target="_blank" rel="noopener">${t("openRipper")} &#8599;</a>
          </div>
          <div class="ah-lf-actions">
            <button id="ah-lf-submit">${t("lfPost")}</button>
          </div>
          <div id="ah-lf-status"></div>
        </div>
      </div>`;
    document.body.appendChild(modal);
    injectModalCSS();
    applyCooldownToAllButtons();

    modal.querySelector("#ah-lf-cid").innerHTML = FORUM_CATEGORIES.map(c => {
      const pad      = "\u00a0\u00a0\u00a0".repeat(c.depth);
      const selected = c.cid === cid ? " selected" : "";
      return `<option value="${c.cid}"${selected}>${pad}${c.name}</option>`;
    }).join("");

    modal.querySelector(".ah-lf-close").addEventListener("click",   () => modal.remove());
    modal.querySelector(".ah-lf-backdrop").addEventListener("click", () => modal.remove());

    modal.querySelector("#ah-lf-submit").addEventListener("click", () => {
      const postTitle   = modal.querySelector("#ah-lf-title").value.trim();
      const rawContent  = modal.querySelector("#ah-lf-content").value.trim();
      const postContent = rawContent + WATERMARK;
      const postTags    = modal.querySelector("#ah-lf-tags").value
                            .split(",").map(s => s.trim().toLowerCase()).filter(Boolean);
      const postCid     = parseInt(modal.querySelector("#ah-lf-cid").value, 10) || cid;

      if (!postTitle)   { setStatus(t("lfErrTitle"),   "err"); return; }
      if (!postContent) { setStatus(t("lfErrContent"), "err"); return; }

      const btn = modal.querySelector("#ah-lf-submit");
      btn.disabled = true; btn.textContent = t("lfPosting");
      setStatus(t("lfConnecting"), "load");
      GM_setValue("bs-lf-cid", postCid);

      postLFTopic(postTitle, postContent, postTags, postCid, (err, result) => {
        btn.disabled = false; btn.textContent = t("lfPost");
        if (err) { setStatus(`&#10060; ${err}`, "err"); return; }

        const topicData = (result && result.response && result.response.topicData) ||
                          (result && result.topicData) ||
                          (result && result.response) || result || {};
        const slug     = topicData.slug || topicData.tid || null;
        const topicUrl = slug ? `${SITE_URL}/topic/${slug}` : SITE_URL;
        const tid      = topicData.tid;

        if (tid && getSetting("autoWatch")) {
          getCSRFToken((csrfErr, csrf) => { if (!csrfErr && csrf) watchTopic(tid, csrf); });
        }

        recordPost();
        applyCooldownToAllButtons();
        setStatus(`${t("lfSuccess")} <a href="${topicUrl}" target="_blank" rel="noopener">${t("lfViewPost")}</a>`, "ok");
        setTimeout(() => modal.remove(), 5000);
      });
    });

    function setStatus(html, type) {
      const el = modal.querySelector("#ah-lf-status");
      el.innerHTML = html; el.className = `ah-lf-st-${type}`;
    }
  }

  // ─── Import Modal ─────────────────────────────────────────────────────────
  function showImportModal(onImport) {
    document.getElementById("ah-import-modal")?.remove();

    const modal = document.createElement("div");
    modal.id = "ah-import-modal";
    modal.innerHTML = `
      <div class="ah-import-backdrop"></div>
      <div class="ah-import-dialog">
        <div class="ah-import-header">
          <div class="ah-import-title">
            <svg width="13" height="13" viewBox="0 0 13 13" fill="none">
              <path d="M6.5 9V3M3.5 6l3 3 3-3M1 11h11" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
            Import Data
          </div>
          <button class="ah-import-close">&#10005;</button>
        </div>
        <div class="ah-import-body">
          <div class="ah-import-drop" id="ah-import-drop">
            <svg width="28" height="28" viewBox="0 0 28 28" fill="none">
              <path d="M14 4v14M7 11l7-7 7 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
              <path d="M4 22h20" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
            </svg>
            <span class="ah-import-drop-label">${t("importDrop")}</span>
            <span class="ah-import-drop-sub">${t("importDropSub")}</span>
            <input type="file" id="ah-import-file" accept=".json,application/json" style="display:none"/>
          </div>
          <div id="ah-import-status"></div>
        </div>
      </div>`;
    document.body.appendChild(modal);
    injectImportCSS();

    const close = () => modal.remove();
    modal.querySelector(".ah-import-backdrop").addEventListener("click", close);
    modal.querySelector(".ah-import-close").addEventListener("click", close);

    const drop    = modal.querySelector("#ah-import-drop");
    const fileInp = modal.querySelector("#ah-import-file");
    const status  = modal.querySelector("#ah-import-status");

    function setStatus(msg, type) {
      status.textContent = msg;
      status.className   = `ah-import-st ah-import-st--${type}`;
    }

    function processFile(file) {
      if (!file || !file.name.endsWith(".json")) {
        setStatus(t("importInvalid"), "err"); return;
      }
      const reader = new FileReader();
      reader.onload = (e) => {
        try {
          const data = JSON.parse(e.target.result);
          if (!data || (typeof data !== "object")) throw new Error("Invalid format");
          setStatus(t("importOk"), "ok");
          setTimeout(() => { modal.remove(); onImport(data); }, 900);
        } catch(err) {
          setStatus(t("importErr"), "err");
        }
      };
      reader.readAsText(file);
    }

    drop.addEventListener("click", () => fileInp.click());
    fileInp.addEventListener("change", () => {
      if (fileInp.files[0]) processFile(fileInp.files[0]);
    });

    drop.addEventListener("dragover", (e) => {
      e.preventDefault();
      drop.classList.add("ah-import-drop--over");
    });
    drop.addEventListener("dragleave", () => drop.classList.remove("ah-import-drop--over"));
    drop.addEventListener("drop", (e) => {
      e.preventDefault();
      drop.classList.remove("ah-import-drop--over");
      const file = e.dataTransfer.files[0];
      processFile(file);
    });
  }

  // ─── Confirm Modal ─────────────────────────────────────────────────────────
  function showConfirmModal({ title, message, proceedLabel, onProceed }) {
    document.getElementById("ah-confirm-modal")?.remove();
    const modal = document.createElement("div");
    modal.id = "ah-confirm-modal";
    modal.innerHTML = `
      <div class="ah-confirm-backdrop"></div>
      <div class="ah-confirm-dialog" role="alertdialog" aria-modal="true">
        <div class="ah-confirm-icon-row">
          <svg width="28" height="28" viewBox="0 0 28 28" fill="none">
            <path d="M14 3L26 24H2L14 3Z" stroke="#ff6680" stroke-width="1.6" stroke-linejoin="round" fill="rgba(255,102,128,.08)"/>
            <path d="M14 11V17" stroke="#ff6680" stroke-width="1.8" stroke-linecap="round"/>
            <circle cx="14" cy="21" r="1.1" fill="#ff6680"/>
          </svg>
          <span class="ah-confirm-title">${esc(title)}</span>
        </div>
        <div class="ah-confirm-body">${esc(message)}</div>
        <div class="ah-confirm-actions">
          <button class="ah-confirm-cancel" id="ah-confirm-cancel">${t("btnCancel")}</button>
          <button class="ah-confirm-proceed" id="ah-confirm-proceed">${esc(proceedLabel || "Proceed")}</button>
        </div>
      </div>`;
    document.body.appendChild(modal);
    injectConfirmCSS();

    const close = () => modal.remove();
    modal.querySelector(".ah-confirm-backdrop").addEventListener("click", close);
    modal.querySelector("#ah-confirm-cancel").addEventListener("click", close);
    modal.querySelector("#ah-confirm-proceed").addEventListener("click", () => {
      close();
      onProceed();
    });
  }

  // ─── Render Results ───────────────────────────────────────────────────────
  function renderResults(data, panel) {
    const posts = data.posts || [], count = data.matchCount || 0;

    if (!count || !posts.length) {
      return `<div class="ah-no-results">${t("noResult")}</div>
        <div class="ah-lf-prompt">
          <p>${t("lfNotFound")}</p>
          <button class="ah-btn-lf" id="ah-lf-open">${t("lfBtn")}</button>
        </div>`;
    }

    const dloc = { en: "en-US", ja: "ja-JP", ru: "ru-RU", "pt-BR": "pt-BR" }[currentLang] || "en-US";

    function buildCard(post, isDL) {
      const tp    = post.topic    || {};
      const cat   = post.category || {};
      const u     = post.user     || {};
      const tid   = tp.tid || "";
      const title = dec(tp.titleRaw || tp.title || t("untitled"));
      const href  = `${SITE_URL}/topic/${tp.slug || tid}`;
      const catN  = dec(cat.name || "");
      const solved = tp.isSolved === 1;
      const pc    = tp.postcount || 0;
      const vc    = tp.viewcount || 0;
      const date  = post.timestampISO ? new Date(post.timestampISO).toLocaleDateString(dloc) : "";
      const tags  = (tp.tags || []).map(g => dec(g.value));
      const user  = dec(u.displayname || u.username || "?");

      const catStyle = getCatStyle(catN);
      const catBadge = catN
        ? `<span class="ah-cat" style="color:${catStyle.color};background:${catStyle.bg};border-color:${catStyle.bd}">${esc(catN)}</span>`
        : "";

      const openStyle   = getCatStyle("open");
      const solvedStyle = getCatStyle("solved");
      const statusBadge = solved
        ? `<span class="ah-badge ah-badge--solved" style="color:${solvedStyle.color};background:${solvedStyle.bg};border-color:${solvedStyle.bd}">${t("solved")}</span>`
        : `<span class="ah-badge ah-badge--open" style="color:${openStyle.color};background:${openStyle.bg};border-color:${openStyle.bd}">${t("unsolved")}</span>`;

      const dlChip = isDL
        ? `<span class="ah-dl-chip">&#8595; DL</span>`
        : "";

      const bumpBtn = tid && !isDL
        ? `<button class="ah-bump-btn" data-tid="${esc(String(tid))}" title="${esc(t("bumpBtn"))}">${esc(t("bumpBtn"))}</button>`
        : "";

      return `<a class="ah-card ${isDL ? "ah-card--dl" : "ah-card--disc"}" data-tid="${esc(String(tid))}" data-slug="${esc(String(tp.slug || tid))}" href="${href}" target="_blank" rel="noopener">
        <div class="ah-card-top">
          ${statusBadge}
          ${catBadge}
          ${dlChip}
          ${bumpBtn ? `<span class="ah-card-bump-wrap">${bumpBtn}</span>` : ""}
        </div>
        <div class="ah-card-title">${esc(title)}</div>
        <div class="ah-card-meta">
          <span class="ah-card-user">${esc(user)}</span>
          <span>${date}</span><span>&#128172; ${pc}</span><span>&#128065; ${vc}</span>
        </div>
        ${tags.length ? `<div class="ah-tags">${tags.map(g => `<span class="ah-tag">${esc(g)}</span>`).join("")}</div>` : ""}
      </a>`;
    }

    let html = `<div class="ah-result-count">${count}${t("hits")}</div>`;
    html += `<div class="ah-section-label ah-section--dl" id="ah-dl-hd" style="display:none">&#8595; ${t("dlFound")}</div>`;
    html += `<div class="ah-list" id="ah-dl-list"></div>`;
    html += `<div class="ah-section-label ah-section--disc" id="ah-disc-hd" style="display:none">&#9711; ${t("secDiscussions")}</div>`;
    html += `<div class="ah-list" id="ah-disc-list">`;
    for (const post of posts) {
      const cat  = post.category || {};
      const isGifts = isGiftsCategory(cat);
      html += buildCard(post, isGifts);
    }
    html += `</div>`;
    html += `<div class="ah-bottom-actions">
      <button class="ah-btn-watch" id="ah-wl-add">${t("addWatch")}</button>
      <button class="ah-btn-lf" id="ah-lf-open">${t("lfBtn")}</button>
    </div>`;

    setTimeout(() => {
      const out      = panel.querySelector("#ah-out"); if (!out) return;
      const discHd   = out.querySelector("#ah-disc-hd");
      const dlHd     = out.querySelector("#ah-dl-hd");
      const dlList   = out.querySelector("#ah-dl-list");
      const discList = out.querySelector("#ah-disc-list");

      if (discList && dlList) {
        Array.from(discList.querySelectorAll(".ah-card--dl")).forEach(card => {
          dlList.appendChild(card);
          dlHd.style.display = "";
        });
        if (discList.children.length) discHd.style.display = "";
      }

      for (const post of posts) {
        const tid  = (post.topic || {}).tid; if (!tid) continue;
        const isGifts = isGiftsCategory(post.category || {});
        if (isGifts) continue;

        checkDL(tid, (found) => {
          if (!found) return;
          const card = discList && discList.querySelector(`[data-tid="${tid}"]`);
          if (card && dlList) {
            const clone = card.cloneNode(true);
            clone.className = "ah-card ah-card--dl";
            if (!clone.querySelector(".ah-dl-chip")) {
              const chip = document.createElement("span");
              chip.className = "ah-dl-chip";
              chip.textContent = "\u2193 DL";
              clone.querySelector(".ah-card-top").appendChild(chip);
            }
            // Remove bump button from dl cards
            const cloneBump = clone.querySelector(".ah-bump-btn");
            if (cloneBump) cloneBump.closest(".ah-card-bump-wrap")?.remove() || cloneBump.remove();
            dlList.appendChild(clone);
            card.remove();
            dlHd.style.display = "";
            if (discList.children.length === 0) discHd.style.display = "none";
          }
        });
      }

      // Wire bump buttons on all initially rendered cards
      out.querySelectorAll(".ah-bump-btn").forEach(btn => wireBumpBtn(btn));
    }, 150);

    return html;
  }

  // ─── Bump button handler ──────────────────────────────────────────────────
  function wireBumpBtn(btn) {
    // Prevent the anchor click from firing when the button is clicked
    btn.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopPropagation();

      if (btn.dataset.bumped === "1") return;

      const tid = btn.dataset.tid;
      if (!tid) return;

      const remaining = cooldownRemaining();
      if (remaining > 0) {
        // Already shown via applyCooldownToAllButtons, but as safety net:
        return;
      }

      const msg = (getSetting("bumpMessage") || "bump").trim() || "bump";

      btn.disabled = true;
      btn.textContent = t("bumpBtnSending");
      btn.classList.add("ah-bump-btn--sending");

      postReply(tid, msg, (err) => {
        if (err) {
          btn.disabled = false;
          btn.classList.remove("ah-bump-btn--sending");
          btn.classList.add("ah-bump-btn--err");
          btn.title = String(err);
          btn.textContent = t("bumpBtnErr");
          console.error("[AssetHunter] Bump failed:", err);
          // reset after 5 seconds
          setTimeout(() => {
            btn.textContent = t("bumpBtn");
            btn.classList.remove("ah-bump-btn--err");
            btn.title = t("bumpBtn");
          }, 5000);
        } else {
          btn.dataset.bumped = "1";
          btn.textContent = t("bumpBtnDone");
          btn.classList.remove("ah-bump-btn--sending");
          btn.classList.add("ah-bump-btn--done");
          recordPost();
          applyCooldownToAllButtons();
        }
      });
    });
  }

  // ─── Main Panel ───────────────────────────────────────────────────────────
  function injectUI() {
    if (document.getElementById("ah-panel")) return;

    const adapter = getAdapter();
    if (!adapter) return;

    const id    = adapter.getId();
    const name  = adapter.getName();
    const query = adapter.buildQuery(id, name);
    if (!query) return;

    const platformLabel = (() => {
      if (HOST === "booth.pm" || HOST.endsWith(".booth.pm")) return "booth.pm";
      if (HOST === "gumroad.com" || HOST.endsWith(".gumroad.com")) return "gumroad";
      if (HOST === "jinxxy.com" || HOST.endsWith(".jinxxy.com")) return "jinxxy";
      if (HOST === "payhip.com" || HOST.endsWith(".payhip.com")) return "payhip";
      if (HOST === "itch.io" || HOST.endsWith(".itch.io")) return "itch.io";
      return HOST;
    })();

    const panel = document.createElement("div");
    panel.id = "ah-panel";
    panel.innerHTML = `
      <div id="ah-header">
        <div id="ah-header-left">
          <svg class="ah-logo-star" width="16" height="16" viewBox="0 0 16 16" fill="none">
            <path d="M8 1L9.5 6H15L10.5 9L12 14L8 11L4 14L5.5 9L1 6H6.5L8 1Z"
              stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/>
          </svg>
          <span id="ah-title">${t("title")}</span>
        </div>
        <div id="ah-header-right">
          <span id="ah-booth-badge">${platformLabel}</span>
          <button id="ah-minimize" title="${t("minimize")}">
            <svg width="10" height="10" viewBox="0 0 10 10" fill="none">
              <path d="M2 5H8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
            </svg>
          </button>
        </div>
      </div>

      <div id="ah-collapsible">
        <div id="ah-tabs">
          <button class="ah-tab ah-tab--active" data-tab="search" id="ah-tab-search">${t("search")}</button>
          <button class="ah-tab" data-tab="watchlist" id="ah-tab-watchlist">${t("watchlist")} <span id="ah-wl-count"></span></button>
          <button class="ah-tab ah-tab--supporters" data-tab="supporters" id="ah-tab-supporters" title="${t('supTitle')}">&#10022;</button>
          <button class="ah-tab ah-tab--icon" data-tab="settings" title="${t("settings")}">
            <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
              <circle cx="6" cy="6" r="1.8" stroke="currentColor" stroke-width="1.2"/>
              <path d="M6 1.5V2.5M6 9.5V10.5M1.5 6H2.5M9.5 6H10.5M2.9 2.9L3.6 3.6M8.4 8.4L9.1 9.1M2.9 9.1L3.6 8.4M8.4 3.6L9.1 2.9"
                stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/>
            </svg>
          </button>
        </div>

        <div id="ah-pane-search">
          <div id="ah-item-info">
            <div id="ah-item-name" title="${esc(name)}">${esc(name) || t("unknown")}</div>
            ${id ? `<div id="ah-item-id">ID ${id}</div>` : ""}
          </div>
          <div id="ah-search-type-row">
            <button id="ah-search-mode-btn" class="ah-search-mode-btn" data-mode="${getSetting("defaultSearchMode") || "name"}">
              ${(getSetting("defaultSearchMode") || "name") === "url" ? t("searchModeToggle_url") : t("searchModeToggle_name")}
            </button>
            <button id="ah-match-mode-btn" class="ah-search-mode-btn" data-match="any">${t("matchModeToggle_any")}</button>
          </div>
          <button id="ah-manual-search-btn" style="display:none">${t("manualSearchBtn")}</button>
          <div id="ah-search-row">
            <input id="ah-input" type="text" value="${esc(query)}" placeholder="${t("placeholder")}" />
            <button id="ah-search-btn">${t("search")}</button>
          </div>
          <div id="ah-out"></div>
        </div>

        <div id="ah-pane-watchlist" style="display:none">
          <div id="ah-wl-body"></div>
        </div>

        <div id="ah-pane-supporters" style="display:none">
          <div id="ah-supporters-body"></div>
        </div>

        <div id="ah-pane-settings" style="display:none">
          <div id="ah-settings-body"></div>
        </div>
      </div>`;
    document.body.appendChild(panel);
    injectCSS();

    // ── Ko-fi donation card ──────────────────────────────────────────────────
    const KOFI_CLOSE_ANYWAY_COUNT_KEY = "ah-kofi-close-anyway-count";
    const KOFI_DONT_ASK_KEY = "ah-kofi-dont-ask";
    const card = document.createElement("div");
    card.id = "ah-kofi-card";
    let renderKofiCardContent = () => {};
    if (GM_getValue(KOFI_DONT_ASK_KEY, "0") === "1") {
      renderKofiCardContent = () => {};
    } else {
      renderKofiCardContent = () => {
        card.innerHTML = `<div id="ah-kofi-inner">
      <span id="ah-kofi-heart">&#9829;</span>
      <div id="ah-kofi-text">
        <span id="ah-kofi-msg">${t("kofiCardMsg")}</span>
        <a href="https://ko-fi.com/xedinho" target="_blank" rel="noopener">&#9749; ko-fi.com/xedinho</a>
      </div>
      <button id="ah-kofi-close">&#10005;</button>
    </div>`;
        card.querySelector("#ah-kofi-close").addEventListener("click", showKofiCloseModal);
      };
      renderKofiCardContent();
      document.body.appendChild(card);
      // Hide immediately if panel starts minimized
      if (getSetting("startMinimized")) card.style.display = "none";

    function positionKofiCard() {
      const pr = panel.getBoundingClientRect();
      card.style.right  = (window.innerWidth  - pr.right)  + "px";
      card.style.bottom = (window.innerHeight - pr.top + 5) + "px";
      card.style.width  = pr.width + "px";
    }

    requestAnimationFrame(() => requestAnimationFrame(positionKofiCard));
    window.addEventListener("resize", positionKofiCard);
    if (typeof ResizeObserver !== "undefined") {
      const kofiObserver = new ResizeObserver(positionKofiCard);
      kofiObserver.observe(panel);
    }

    function showKofiCloseModal() {
      document.getElementById("ah-kofi-confirm-modal")?.remove();
      const closeAnywayCount = parseInt(GM_getValue(KOFI_CLOSE_ANYWAY_COUNT_KEY, "0"), 10) || 0;
      const canShowDontAsk = closeAnywayCount > 2;
      const modal = document.createElement("div");
      modal.id = "ah-kofi-confirm-modal";
      modal.innerHTML = `
        <div class="ah-kofi-confirm-backdrop"></div>
        <div class="ah-kofi-confirm-dialog" role="alertdialog" aria-modal="true">
          <div class="ah-kofi-confirm-title">
            <span class="ah-kofi-confirm-heart">&#9829;</span>
            ${t("kofiModalTitle")}
          </div>
          <div class="ah-kofi-confirm-body">
            ${t("kofiModalBody")}
          </div>
          <a href="https://ko-fi.com/xedinho" target="_blank" rel="noopener" class="ah-kofi-confirm-link">&#9749; ko-fi.com/xedinho</a>
          ${canShowDontAsk ? `<label class="ah-kofi-confirm-optout"><input type="checkbox" id="ah-kofi-dont-ask"> ${t("kofiDontAsk")}</label>` : ""}
          <div class="ah-kofi-confirm-actions">
            <button id="ah-kofi-keep-btn">${t("kofiKeepBtn")}</button>
            <button id="ah-kofi-close-btn">${t("kofiCloseBtn")}</button>
          </div>
        </div>`;
      document.body.appendChild(modal);

      const dismiss = () => modal.remove();
      modal.querySelector(".ah-kofi-confirm-backdrop").addEventListener("click", dismiss);
      modal.querySelector("#ah-kofi-keep-btn").addEventListener("click", dismiss);
      modal.querySelector("#ah-kofi-close-btn").addEventListener("click", () => {
        const nextCount = closeAnywayCount + 1;
        GM_setValue(KOFI_CLOSE_ANYWAY_COUNT_KEY, String(nextCount));
        const dontAskEl = modal.querySelector("#ah-kofi-dont-ask");
        if (dontAskEl && dontAskEl.checked) {
          GM_setValue(KOFI_DONT_ASK_KEY, "1");
        }
        card.remove();
        dismiss();
      });
    }
    }

    const out     = panel.querySelector("#ah-out");
    const inp     = panel.querySelector("#ah-input");
    const wlBody  = panel.querySelector("#ah-wl-body");
    const supBody = panel.querySelector("#ah-supporters-body");
    const setBody = panel.querySelector("#ah-settings-body");
    let lastSearchData = null;

    function updateWlCount() {
      const el = panel.querySelector("#ah-wl-count");
      const n  = wlGet().length;
      el.textContent = n > 0 ? `(${n})` : "";
    }
    updateWlCount();

    // ── Tab switching ──
    const PANES = {
      search:     "#ah-pane-search",
      watchlist:  "#ah-pane-watchlist",
      supporters: "#ah-pane-supporters",
      settings:   "#ah-pane-settings",
    };
    panel.querySelectorAll(".ah-tab").forEach(btn => {
      btn.addEventListener("click", () => {
        panel.querySelectorAll(".ah-tab").forEach(b => b.classList.remove("ah-tab--active"));
        btn.classList.add("ah-tab--active");
        const tab = btn.dataset.tab;
        Object.entries(PANES).forEach(([k, sel]) => {
          panel.querySelector(sel).style.display = k === tab ? "" : "none";
        });
        if (tab === "watchlist")  renderWatchlist();
        if (tab === "supporters") renderSupporters();
        if (tab === "settings")   renderSettings();
      });
    });

    // ── Watchlist ──
    function renderWatchlist() {
      const list = wlGet();
      updateWlCount();
      if (!list.length) { wlBody.innerHTML = `<div class="ah-wl-empty">${t("noWatch")}</div>`; return; }

      const dlItems    = list.filter(x => x.status === "dl");
      const discItems  = list.filter(x => x.status === "found");
      const otherItems = list.filter(x => x.status !== "dl" && x.status !== "found");

      // Items eligible for bump = have a ripperTid and are NOT marked as dl (already downloaded)
      const bumpableItems = list.filter(x => x.ripperTid && x.status !== "dl");

      function itemHTML(item) {
        const hasTid     = item.ripperTid || item.ripperSlug;
        const topicRef   = item.ripperTid || item.ripperSlug;
        const isDLStatus = item.status === "dl";
        const isFoundStatus = item.status === "found";

        const badge =
          isDLStatus      ? `<span class="ah-wl-badge ah-wl-badge--dl">&#8595; DL Found</span>`     :
          isFoundStatus   ? `<span class="ah-wl-badge ah-wl-badge--disc">&#9711; Discussion</span>`  :
          item.status === "none"
                          ? `<span class="ah-wl-badge ah-wl-badge--none">&#10005; Not Found</span>`   :
                            `<span class="ah-wl-badge ah-wl-badge--pending">&#8987; Pending</span>`;

        const checked = item.lastChecked
          ? `<span class="ah-wl-ts" data-ts="${item.lastChecked}">${timeAgo(item.lastChecked)}</span>`
          : "";

        const ripperLink = (isDLStatus || isFoundStatus) && hasTid
          ? `<a href="${SITE_URL}/topic/${esc(String(topicRef))}" target="_blank" class="ah-wl-link ah-wl-link--ripper">${t("openPost")}</a>`
          : "";

        // Show bump button only for items that have a tid and are NOT solved (dl)
        const bumpBtn = hasTid && !isDLStatus
          ? `<button class="ah-bump-btn ah-wl-bump-btn" data-tid="${esc(String(item.ripperTid || topicRef))}" title="${esc(t("bumpBtn"))}">${esc(t("bumpBtn"))}</button>`
          : "";

        return `<div class="ah-wl-item ah-wl-item--${item.status || "pending"}" data-url="${esc(item.url)}">
          <div class="ah-wl-row1">
            ${badge}
            <button class="ah-wl-remove" data-url="${esc(item.url)}">&#10005;</button>
          </div>
          <div class="ah-wl-name">${esc(item.name)}</div>
          <div class="ah-wl-row2">
            <a href="${esc(item.url)}" target="_blank" class="ah-wl-link">${t("openItem")}</a>
            ${ripperLink}
            ${bumpBtn}
            ${checked}
          </div>
        </div>`;
      }

      // Bump All button + recheck + progress bar
      const recheckSVG = `<svg width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M11 6.5A4.5 4.5 0 1 1 9.2 3M11 1.5V4H8.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
      const bumpAllHTML = `<div class="ah-wl-bump-all-row">
          <div class="ah-wl-bump-all-btns">
            ${bumpableItems.length ? `<button class="ah-wl-bump-all-btn" id="ah-wl-bump-all">${t("bumpAllBtn")} (${bumpableItems.length})</button>` : ""}
            <button class="ah-wl-recheck-btn" id="ah-recheck-btn" title="${t("recheck")}">${recheckSVG}</button>
          </div>
          <div class="ah-wl-bump-progress" id="ah-wl-bump-progress" style="display:none">
            <div class="ah-wl-bump-progress-bar-wrap"><div class="ah-wl-bump-progress-bar" id="ah-wl-bump-progress-bar"></div></div>
            <span class="ah-wl-bump-progress-text" id="ah-wl-bump-progress-text"></span>
          </div>
         </div>`;

      let html = bumpAllHTML;
      if (dlItems.length)    html += `<div class="ah-section-label ah-section--dl">&#8595; ${t("dlFound")}</div>` + dlItems.map(itemHTML).join("");
      if (discItems.length)  html += `<div class="ah-section-label ah-section--disc">&#9711; ${t("secDiscussions")}</div>` + discItems.map(itemHTML).join("");
      if (otherItems.length) {
        if (dlItems.length || discItems.length) html += `<div class="ah-section-label ah-section--other">&#9675; ${t("secOther")}</div>`;
        html += otherItems.map(itemHTML).join("");
      }

      wlBody.innerHTML = html;

      wlBody.querySelectorAll(".ah-wl-remove").forEach(btn => {
        btn.addEventListener("click", e => { e.preventDefault(); wlRemove(btn.dataset.url); renderWatchlist(); });
      });

      const recheckBtn = wlBody.querySelector("#ah-recheck-btn");
      if (recheckBtn) recheckBtn.addEventListener("click", runWatchlistCheck);

      // Wire individual bump buttons in watchlist
      wlBody.querySelectorAll(".ah-wl-bump-btn").forEach(btn => wireBumpBtn(btn));

      // Wire Bump All button
      const bumpAllBtn = wlBody.querySelector("#ah-wl-bump-all");
      if (bumpAllBtn) {
        bumpAllBtn.addEventListener("click", () => {
          const items = wlGet().filter(x => x.ripperTid && x.status !== "dl");
          if (!items.length) return;

          const BUMP_DELAY_MS = 11000;
          const totalMs = items.length * BUMP_DELAY_MS;
          const totalSecs = Math.ceil(totalMs / 1000);

          const progressWrap = wlBody.querySelector("#ah-wl-bump-progress");
          const progressBar  = wlBody.querySelector("#ah-wl-bump-progress-bar");
          const progressText = wlBody.querySelector("#ah-wl-bump-progress-text");

          bumpAllBtn.disabled = true;
          progressWrap.style.display = "";

          let bumped = 0;
          let failed = 0;

          function updateProgress() {
            const done = bumped + failed;
            const pct  = Math.round((done / items.length) * 100);
            progressBar.style.width = pct + "%";
            const failedPartRun  = failed ? t("bumpProgressFailed").replace("{n}", failed) : "";
            const secsLeft = Math.ceil(((items.length - done) * BUMP_DELAY_MS) / 1000);
            progressText.textContent = t("bumpProgressRunning")
              .replace("{done}", done).replace("{total}", items.length)
              .replace("{failed}", failedPartRun).replace("{secs}", secsLeft);
          }

          function bumpNext(idx) {
            if (idx >= items.length) {
              const failedPartDone = failed ? t("bumpProgressDoneFailed").replace("{n}", failed) : "";
              progressText.textContent = t("bumpProgressDone")
                .replace("{bumped}", bumped).replace("{failed}", failedPartDone);
              progressBar.style.width = "100%";
              bumpAllBtn.disabled = false;
              bumpAllBtn.textContent = t("bumpAllBtn") + ` (${items.length})`;
              return;
            }

            const item = items[idx];
            const msg  = (getSetting("bumpMessage") || "bump").trim() || "bump";

            // Highlight the item being bumped
            const itemEl = wlBody.querySelector(`.ah-wl-item[data-url="${CSS.escape(item.url)}"]`);
            if (itemEl) itemEl.classList.add("ah-wl-item--bumping");

            // Wait for cooldown before posting
            const wait = Math.max(0, cooldownRemaining());
            setTimeout(() => {
              postReply(String(item.ripperTid), msg, (err) => {
                if (itemEl) itemEl.classList.remove("ah-wl-item--bumping");
                if (err) { failed++; } else { bumped++; recordPost(); applyCooldownToAllButtons(); }
                updateProgress();
                // Wait 11 seconds before next bump regardless
                setTimeout(() => bumpNext(idx + 1), BUMP_DELAY_MS);
              });
            }, wait);
          }

          updateProgress();
          bumpNext(0);
        });
      }

      if (wlBody._tick) clearInterval(wlBody._tick);
      wlBody._tick = setInterval(() => {
        wlBody.querySelectorAll(".ah-wl-ts").forEach(el => {
          const ts = parseInt(el.dataset.ts, 10); if (ts) el.textContent = timeAgo(ts);
        });
      }, 1000);
    }

    // ── Supporters Leaderboard ──
    function renderSupporters() {
      supBody.innerHTML = `<div class="ah-sup-loading"><span class="ah-spinner"></span></div>`;

      fetchDonors((err, donors) => {
        if (err || !donors) {
          supBody.innerHTML = `<div class="ah-sup-empty">${t("supLoadErr")}</div>`;
          return;
        }

        function getRankStyle(pos) {
          if (pos === 0) return { cls: "ah-sup-rank--1", medal: "&#10022;" };
          if (pos === 1) return { cls: "ah-sup-rank--2", medal: "&#10022;" };
          if (pos === 2) return { cls: "ah-sup-rank--3", medal: "&#10023;" };
          if (pos < 6)   return { cls: "ah-sup-rank--mid", medal: "&middot;" };
          return { cls: "ah-sup-rank--low", medal: "" };
        }

        let html = `
          <div class="ah-sup-header">
            <div class="ah-sup-title">${t("supTitle")}</div>
            <div class="ah-sup-subtitle">${t("supSubtitle")}</div>
            <a class="ah-sup-kofi-link" href="https://ko-fi.com/xedinho" target="_blank" rel="noopener">&#9749; ko-fi.com/xedinho</a>
          </div>`;

        if (!donors.length) {
          html += `<div class="ah-sup-empty">${t("supEmpty")}<br><span class="ah-sup-empty-sub">${t("supEmptySub")}</span></div>`;
        } else {
          html += `<div class="ah-sup-list">`;
          donors.forEach((donor, pos) => {
            const { cls, medal } = getRankStyle(pos);
            const rank = pos + 1;
            html += `
              <div class="ah-sup-entry ${cls}">
                <span class="ah-sup-pos">${medal || rank}</span>
                <span class="ah-sup-name">${esc(donor.name)}</span>
                <span class="ah-sup-amount">&#8364;${donor.total % 1 === 0 ? donor.total.toFixed(0) : donor.total.toFixed(2)}</span>
              </div>`;
          });
          html += `</div>`;
        }

        supBody.innerHTML = html;
      });
    }

    // ── Settings validation warn popup ──
    function showSettingsWarn(errors) {
      if (document.getElementById("ah-warn-modal")) return;

      const modal = document.createElement("div");
      modal.id = "ah-warn-modal";

      const itemsHTML = errors.map(function(e) {
        return '<div class="ah-warn-item">' + e + '</div>';
      }).join("");

      modal.innerHTML = [
        '<div class="ah-warn-backdrop"></div>',
        '<div class="ah-warn-dialog" role="alertdialog" aria-modal="true">',
          '<div class="ah-warn-icon-row">',
            '<svg width="28" height="28" viewBox="0 0 28 28" fill="none">',
              '<path d="M14 3L26 24H2L14 3Z" stroke="#ff6680" stroke-width="1.6" stroke-linejoin="round" fill="rgba(255,102,128,.08)"/>',
              '<path d="M14 11V17" stroke="#ff6680" stroke-width="1.8" stroke-linecap="round"/>',
              '<circle cx="14" cy="21" r="1.1" fill="#ff6680"/>',
            '</svg>',
            '<span class="ah-warn-title">' + t("warnModalTitle") + '</span>',
          '</div>',
          '<div class="ah-warn-body">' + itemsHTML + '</div>',
          '<button class="ah-warn-btn" id="ah-warn-ok">' + t("warnModalOk") + '</button>',
        '</div>'
      ].join("");

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

      const close = function() { modal.remove(); };
      modal.querySelector("#ah-warn-ok").addEventListener("click", close);
      modal.querySelector(".ah-warn-backdrop").addEventListener("click", close);
    }

    // ── Settings ──
    function renderSettings() {
      setBody.innerHTML = `
        <div class="ah-set-changelog-btn-wrap">
          <button class="ah-set-changelog-btn" id="ah-set-changelog-open">
            <svg width="11" height="11" viewBox="0 0 11 11" fill="none">
              <path d="M5.5 1L6.5 4H10L7.5 5.8L8.5 9L5.5 7.2L2.5 9L3.5 5.8L1 4H4.5L5.5 1Z"
                stroke="currentColor" stroke-width="1.1" stroke-linejoin="round"/>
            </svg>
            ${t("changelogBtn")}
            <span class="ah-set-changelog-version">v${CURRENT_VERSION}</span>
          </button>
        </div>

        <div class="ah-set-section">${t("langLabel")}</div>

        <div class="ah-set-field">
          <div class="ah-lang-picker" id="ah-lang-picker">
            <button class="ah-lang-picker-btn" id="ah-lang-picker-btn" type="button">
              <span>${esc(LANG_OPTIONS.find(o => o.value === currentLang)?.label || currentLang)}</span>
              <svg width="9" height="9" viewBox="0 0 9 9" fill="none">
                <path d="M1.5 3L4.5 6L7.5 3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
              </svg>
            </button>
            <div class="ah-lang-picker-list" id="ah-lang-picker-list">
              ${LANG_OPTIONS.map(o => `<button class="ah-lang-picker-opt${o.value === currentLang ? " ah-lang-picker-opt--active" : ""}" type="button" data-value="${o.value}">${o.label}</button>`).join("")}
            </div>
          </div>
        </div>

        <div class="ah-set-section">${t("secLfTemplates")}</div>

        <div class="ah-set-field">
          <div class="ah-set-field-hd">
            <label class="ah-set-label">${t("labelTitleTpl")}</label>
            <button class="ah-set-reset" data-key="titleTpl">${t("btnReset")}</button>
          </div>
          <div class="ah-set-hint">${t("hintTitleTpl")}</div>
          <textarea class="ah-set-input ah-set-input--single" id="ah-set-titleTpl" rows="1">${esc(getSetting("titleTpl"))}</textarea>
        </div>

        <div class="ah-set-field">
          <div class="ah-set-field-hd">
            <label class="ah-set-label">${t("labelBodyTpl")}</label>
            <button class="ah-set-reset" data-key="bodyTpl">${t("btnReset")}</button>
          </div>
          <div class="ah-set-hint">${t("hintBodyTpl")}</div>
          <textarea class="ah-set-input" id="ah-set-bodyTpl" rows="6">${esc(getSetting("bodyTpl"))}</textarea>
          <div class="ah-set-wm-box">
            <span class="ah-set-wm-label">${t("wmLabel")}</span>
            <pre class="ah-set-wm-pre">---\n# Posted via Asset Hunter</pre>
          </div>
        </div>

        <div class="ah-set-field">
          <div class="ah-set-field-hd">
            <label class="ah-set-label">${t("labelDefaultTags")}</label>
            <button class="ah-set-reset" data-key="defaultTags">${t("btnReset")}</button>
          </div>
          <div class="ah-set-hint">${t("hintDefaultTags")}</div>
          <textarea class="ah-set-input ah-set-input--single" id="ah-set-defaultTags" rows="1">${esc(getSetting("defaultTags"))}</textarea>
        </div>

        <div class="ah-set-field">
          <div class="ah-set-field-hd">
            <label class="ah-set-label">${t("labelBumpMessage")}</label>
            <button class="ah-set-reset" data-key="bumpMessage">${t("btnReset")}</button>
          </div>
          <div class="ah-set-hint">${t("hintBumpMessage")}</div>
          <textarea class="ah-set-input ah-set-input--single" id="ah-set-bumpMessage" rows="1">${esc(getSetting("bumpMessage"))}</textarea>
        </div>

        <div class="ah-set-section">${t("secBehaviour")}</div>

        <div class="ah-set-toggle-row">
          <div class="ah-set-toggle-info">
            <span class="ah-set-toggle-label">${t("labelAutoSearch")}</span>
            <span class="ah-set-toggle-hint">${t("hintAutoSearch")}</span>
          </div>
          <button class="ah-set-toggle ${getSetting("autoSearch") ? "ah-set-toggle--on" : ""}"
            id="ah-set-autoSearch" aria-pressed="${getSetting("autoSearch")}">
            <span class="ah-set-toggle-knob"></span>
          </button>
        </div>

        <div class="ah-set-toggle-row">
          <div class="ah-set-toggle-info">
            <span class="ah-set-toggle-label">${t("labelAutoWatch")}</span>
            <span class="ah-set-toggle-hint">${t("hintAutoWatch")}</span>
          </div>
          <button class="ah-set-toggle ${getSetting("autoWatch") ? "ah-set-toggle--on" : ""}"
            id="ah-set-autoWatch" aria-pressed="${getSetting("autoWatch")}">
            <span class="ah-set-toggle-knob"></span>
          </button>
        </div>

        <div class="ah-set-toggle-row">
          <div class="ah-set-toggle-info">
            <span class="ah-set-toggle-label">${t("labelStartMinimized")}</span>
            <span class="ah-set-toggle-hint">${t("hintStartMinimized")}</span>
          </div>
          <button class="ah-set-toggle ${getSetting("startMinimized") ? "ah-set-toggle--on" : ""}"
            id="ah-set-startMinimized" aria-pressed="${getSetting("startMinimized")}">
            <span class="ah-set-toggle-knob"></span>
          </button>
        </div>

        <div class="ah-set-field">
          <div class="ah-set-field-hd">
            <label class="ah-set-label">${t("labelDefaultSearchMode")}</label>
          </div>
          <div class="ah-set-hint">${t("hintDefaultSearchMode")}</div>
          <div class="ah-set-search-mode-row">
            <button class="ah-set-mode-opt ${(getSetting("defaultSearchMode") || "name") === "name" ? "ah-set-mode-opt--active" : ""}" id="ah-set-mode-name" data-mode="name">${t("searchModeToggle_name")}</button>
            <button class="ah-set-mode-opt ${(getSetting("defaultSearchMode") || "name") === "url"  ? "ah-set-mode-opt--active" : ""}" id="ah-set-mode-url"  data-mode="url">${t("searchModeToggle_url")}</button>
          </div>
        </div>

        <div class="ah-set-field">
          <div class="ah-set-field-hd">
            <label class="ah-set-label">${t("labelMinimizeHotkey")}</label>
            <button class="ah-set-reset" data-key="minimizeHotkey">${t("btnReset")}</button>
          </div>
          <div class="ah-set-hint">${t("hintMinimizeHotkey")}</div>
          <textarea class="ah-set-input ah-set-input--single" id="ah-set-minimizeHotkey" rows="1" placeholder="${t("hotkeyPlaceholder")}">${esc(getSetting("minimizeHotkey"))}</textarea>
        </div>

        <div class="ah-set-section">${t("secActiveSites")}</div>
        <div class="ah-set-hint" style="margin-bottom:10px">${t("hintActiveSites")}</div>
        <div class="ah-set-sites-grid">
          ${SITE_DEFS.map(s => `
            <label class="ah-set-site-row">
              <input type="checkbox" class="ah-set-site-cb" data-site="${s.key}" ${isSiteEnabled(s.key) ? "checked" : ""}>
              <span class="ah-set-site-name">${s.label}</span>
            </label>
          `).join("")}
        </div>

        <div class="ah-set-section ah-set-section--danger">${t("secDataMgmt")}</div>

        <div class="ah-set-data-actions">
          <button class="ah-set-data-btn ah-set-data-btn--export" id="ah-set-export">
            <svg width="11" height="11" viewBox="0 0 11 11" fill="none">
              <path d="M5.5 1v6M2.5 5l3 3 3-3M1 9h9" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
            ${t("btnExport")}
          </button>
          <button class="ah-set-data-btn ah-set-data-btn--import" id="ah-set-import">
            <svg width="11" height="11" viewBox="0 0 11 11" fill="none">
              <path d="M5.5 10V4M2.5 6l3-3 3 3M1 1h9" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
            ${t("btnImport")}
          </button>
          <button class="ah-set-data-btn ah-set-data-btn--reset" id="ah-set-reset-defaults">
            <svg width="11" height="11" viewBox="0 0 11 11" fill="none">
              <path d="M9 5.5A3.5 3.5 0 1 1 7.2 2.5M9 1v2.5H6.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
            ${t("btnResetDef")}
          </button>
          <button class="ah-set-data-btn ah-set-data-btn--delete" id="ah-set-delete-data">
            <svg width="11" height="11" viewBox="0 0 11 11" fill="none">
              <path d="M1.5 3h8M4 3V2h3v1M2.5 3l.5 6h5l.5-6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
            ${t("btnDeleteData")}
          </button>
        </div>`;

      // Changelog button in settings
      setBody.querySelector("#ah-set-changelog-open").addEventListener("click", () => showChangelogModal(false));

      function autoSaveTitleTpl() {
        const val = setBody.querySelector("#ah-set-titleTpl").value || DEFAULTS.titleTpl;
        setSetting("titleTpl", val);
      }
      function autoSaveBodyTpl() {
        const val = setBody.querySelector("#ah-set-bodyTpl").value || DEFAULTS.bodyTpl;
        setSetting("bodyTpl", val);
      }
      function autoSaveDefaultTags() {
        const val = setBody.querySelector("#ah-set-defaultTags").value || DEFAULTS.defaultTags;
        setSetting("defaultTags", val);
      }
      function autoSaveBumpMessage() {
        const val = setBody.querySelector("#ah-set-bumpMessage").value.trim() || DEFAULTS.bumpMessage;
        setSetting("bumpMessage", val);
      }

      setBody.querySelector("#ah-set-titleTpl").addEventListener("change", autoSaveTitleTpl);
      setBody.querySelector("#ah-set-bodyTpl").addEventListener("change", autoSaveBodyTpl);
      setBody.querySelector("#ah-set-defaultTags").addEventListener("change", autoSaveDefaultTags);
      setBody.querySelector("#ah-set-bumpMessage").addEventListener("change", autoSaveBumpMessage);

      // ── Language custom picker ──
      const langPickerBtn  = setBody.querySelector("#ah-lang-picker-btn");
      const langPickerList = setBody.querySelector("#ah-lang-picker-list");

      langPickerBtn.addEventListener("click", (e) => {
        e.stopPropagation();
        const isOpen = langPickerList.classList.toggle("ah-lang-picker-list--open");
        langPickerBtn.classList.toggle("ah-lang-picker-btn--open", isOpen);
        if (isOpen) {
          setTimeout(() => {
            const onOutside = (ev) => {
              if (!setBody.querySelector("#ah-lang-picker")?.contains(ev.target)) {
                langPickerList.classList.remove("ah-lang-picker-list--open");
                langPickerBtn.classList.remove("ah-lang-picker-btn--open");
                document.removeEventListener("click", onOutside, true);
              }
            };
            document.addEventListener("click", onOutside, true);
          }, 0);
        }
      });

      setBody.querySelectorAll(".ah-lang-picker-opt").forEach(btn => {
        btn.addEventListener("click", () => {
          const chosen = btn.dataset.value;
          langPickerList.classList.remove("ah-lang-picker-list--open");
          langPickerBtn.classList.remove("ah-lang-picker-btn--open");
          if (chosen && STRINGS[chosen]) {
            GM_setValue("ah-cfg-lang", chosen);
            currentLang = chosen;
            const tabSearch = panel.querySelector("#ah-tab-search");
            const tabWl     = panel.querySelector("#ah-tab-watchlist");
            const wlCount   = panel.querySelector("#ah-wl-count");
            if (tabSearch) tabSearch.textContent = t("search");
            if (tabWl)     tabWl.innerHTML = `${t("watchlist")} <span id="ah-wl-count">${wlCount ? wlCount.textContent : ""}</span>`;
            if (document.getElementById("ah-kofi-card")) renderKofiCardContent();
            if (lastSearchData) {
              out.innerHTML = renderResults(lastSearchData, panel);
              wireSearchResults();
            }
            const manualBtn = panel.querySelector("#ah-manual-search-btn");
            if (manualBtn) manualBtn.textContent = t("manualSearchBtn");
            const smBtn = panel.querySelector("#ah-search-mode-btn");
            if (smBtn) smBtn.textContent = smBtn.dataset.mode === "url" ? t("searchModeToggle_url") : t("searchModeToggle_name");
            const mmBtn = panel.querySelector("#ah-match-mode-btn");
            if (mmBtn) mmBtn.textContent = _matchWords === "all" ? t("matchModeToggle_all") : t("matchModeToggle_any");
            if (wlBody.closest("#ah-pane-watchlist").style.display !== "none") {
              renderWatchlist();
            }
            if (supBody.closest("#ah-pane-supporters").style.display !== "none") {
              renderSupporters();
            }
            renderSettings();
          }
        });
      });

      setBody.querySelector("#ah-set-autoSearch").addEventListener("click", function() {
        const on = this.classList.toggle("ah-set-toggle--on");
        this.setAttribute("aria-pressed", on);
        setSetting("autoSearch", on);
        const manualBtn = panel.querySelector("#ah-manual-search-btn");
        const searchRow = panel.querySelector("#ah-search-row");
        if (!manualBtn || !searchRow) return;
        if (on) {
          manualBtn.style.display = "none";
          searchRow.style.display = "";
        } else {
          if (!lastSearchData) {
            manualBtn.style.display = "";
            searchRow.style.display = "none";
          }
        }
      });

      setBody.querySelector("#ah-set-autoWatch").addEventListener("click", function() {
        const on = this.classList.toggle("ah-set-toggle--on");
        this.setAttribute("aria-pressed", on);
        setSetting("autoWatch", on);
      });

      setBody.querySelector("#ah-set-startMinimized").addEventListener("click", function() {
        const on = this.classList.toggle("ah-set-toggle--on");
        this.setAttribute("aria-pressed", on);
        setSetting("startMinimized", on);
      });

      setBody.querySelectorAll(".ah-set-mode-opt").forEach(btn => {
        btn.addEventListener("click", () => {
          setBody.querySelectorAll(".ah-set-mode-opt").forEach(b => b.classList.remove("ah-set-mode-opt--active"));
          btn.classList.add("ah-set-mode-opt--active");
          setSetting("defaultSearchMode", btn.dataset.mode);
          // Update the live toggle button in the search pane too
          const modeBtn = panel.querySelector("#ah-search-mode-btn");
          if (modeBtn) {
            modeBtn.dataset.mode = btn.dataset.mode;
            modeBtn.textContent = btn.dataset.mode === "url" ? t("searchModeToggle_url") : t("searchModeToggle_name");
          }
        });
      });

      function autoSaveMinimizeHotkey() {
        const val = setBody.querySelector("#ah-set-minimizeHotkey").value.trim();
        setSetting("minimizeHotkey", val);
      }
      setBody.querySelector("#ah-set-minimizeHotkey").addEventListener("change", autoSaveMinimizeHotkey);

      setBody.querySelectorAll(".ah-set-site-cb").forEach(cb => {
        cb.addEventListener("change", function() {
          GM_setValue("ah-site-enabled-" + this.dataset.site, this.checked ? "1" : "0");
        });
      });

      setBody.querySelectorAll(".ah-set-reset").forEach(btn => {
        btn.addEventListener("click", () => {
          const key = btn.dataset.key;
          const el  = setBody.querySelector(`#ah-set-${key}`);
          if (el) { el.value = DEFAULTS[key]; setSetting(key, DEFAULTS[key]); }
        });
      });

      setBody.querySelector("#ah-set-export").addEventListener("click", () => {
        const exportData = {
          version: CURRENT_VERSION,
          exported: new Date().toISOString(),
          settings: {
            lang:              currentLang,
            titleTpl:          getSetting("titleTpl"),
            bodyTpl:           getSetting("bodyTpl"),
            defaultTags:       getSetting("defaultTags"),
            autoWatch:         getSetting("autoWatch"),
            autoSearch:        getSetting("autoSearch"),
            bumpMessage:       getSetting("bumpMessage"),
            startMinimized:    getSetting("startMinimized"),
            defaultSearchMode: getSetting("defaultSearchMode"),
            minimizeHotkey:    getSetting("minimizeHotkey"),
          },
          watchlist: wlGet(),
        };
        const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: "application/json" });
        const url  = URL.createObjectURL(blob);
        const a    = document.createElement("a");
        a.href     = url;
        a.download = `asset-hunter-data-${Date.now()}.json`;
        a.click();
        URL.revokeObjectURL(url);
      });

      setBody.querySelector("#ah-set-import").addEventListener("click", () => {
        showImportModal((data) => {
          if (data.settings) {
            const s = data.settings;
            if (s.lang && STRINGS[s.lang]) { GM_setValue("ah-cfg-lang", s.lang); currentLang = s.lang; }
            if (s.titleTpl)       setSetting("titleTpl",       s.titleTpl);
            if (s.bodyTpl)        setSetting("bodyTpl",        s.bodyTpl);
            if (s.defaultTags)    setSetting("defaultTags",    s.defaultTags);
            if (s.autoWatch      !== undefined) setSetting("autoWatch",         s.autoWatch);
            if (s.autoSearch     !== undefined) setSetting("autoSearch",        s.autoSearch);
            if (s.bumpMessage    !== undefined) setSetting("bumpMessage",       s.bumpMessage);
            if (s.startMinimized !== undefined) setSetting("startMinimized",    s.startMinimized);
            if (s.defaultSearchMode)            setSetting("defaultSearchMode", s.defaultSearchMode);
            if (s.minimizeHotkey !== undefined) setSetting("minimizeHotkey",    s.minimizeHotkey);
          }
          if (Array.isArray(data.watchlist)) {
            wlSave(data.watchlist);
            updateWlCount();
          }
          renderSettings();
        });
      });

      setBody.querySelector("#ah-set-reset-defaults").addEventListener("click", () => {
        showConfirmModal({
          title: t("modalResetTitle"),
          message: t("modalResetMsg"),
          proceedLabel: t("modalResetProceed"),
          onProceed: () => {
            ["titleTpl","bodyTpl","defaultTags","autoWatch","autoSearch","bumpMessage",
             "startMinimized","defaultSearchMode","minimizeHotkey"].forEach(k => {
              GM_setValue("ah-cfg-" + k, null);
            });
            GM_setValue("ah-cfg-lang", null);
            currentLang = "en";
            renderSettings();
          },
        });
      });

      setBody.querySelector("#ah-set-delete-data").addEventListener("click", () => {
        showConfirmModal({
          title: t("modalDeleteTitle"),
          message: t("modalDeleteMsg"),
          proceedLabel: t("modalDeleteProceed"),
          onProceed: () => {
            wlSave([]);
            updateWlCount();
            renderSettings();
          },
        });
      });
    }

    // ── Watchlist re-check ──
    function runWatchlistCheck() {
      panel.querySelectorAll(".ah-tab").forEach(b => b.classList.remove("ah-tab--active"));
      panel.querySelector('[data-tab="watchlist"]').classList.add("ah-tab--active");
      Object.entries(PANES).forEach(([k, sel]) => {
        panel.querySelector(sel).style.display = k === "watchlist" ? "" : "none";
      });

      const list = wlGet();
      if (!list.length) { renderWatchlist(); return; }
      list.forEach(item => { item.status = "pending"; });
      wlSave(list); renderWatchlist();

      const CONCURRENCY = 2;
      const ITEM_DELAY_MS = 1200;
      let idx = 0;
      let active = 0;

      function scheduleNext() {
        while (active < CONCURRENCY && idx < list.length) {
          active++;
          const item = list[idx++];
          processItem(item, () => {
            active--;
            scheduleNext();
          });
        }
      }

      function processItem(item, done) {
        setTimeout(() => {
          doSearch(item.id || item.name, (err, data) => {
            const cur   = wlGet();
            const entry = cur.find(x => x.url === item.url);
            if (!entry) { done(); return; }

            if (err || !data || !data.matchCount || !data.posts || !data.posts.length) {
              entry.status = "none"; entry.lastChecked = Date.now();
              wlSave(cur); renderWatchlist(); done(); return;
            }

            const giftsPost = data.posts.find(p => isGiftsCategory(p.category || {}));
            if (giftsPost) {
              const tp = giftsPost.topic || {};
              entry.status      = "dl";
              entry.lastChecked = Date.now();
              entry.ripperTid   = tp.tid || null;
              entry.ripperSlug  = tp.slug || tp.tid || null;
              wlSave(cur); renderWatchlist(); done(); return;
            }

            const topics = data.posts
              .map(p => ({ tid: (p.topic || {}).tid, slug: (p.topic || {}).slug }))
              .filter(x => x.tid);

            let foundDL = false;
            function checkNext(tidIdx) {
              if (tidIdx >= topics.length) {
                if (!foundDL) {
                  entry.status      = topics.length ? "found" : "none";
                  entry.lastChecked = Date.now();
                  if (topics.length) {
                    entry.ripperTid  = topics[0].tid;
                    entry.ripperSlug = topics[0].slug || topics[0].tid;
                  }
                  wlSave(cur); renderWatchlist();
                }
                done(); return;
              }
              checkDL(topics[tidIdx].tid, (confirmed) => {
                if (confirmed && !foundDL) {
                  foundDL           = true;
                  entry.status      = "dl";
                  entry.lastChecked = Date.now();
                  entry.ripperTid   = topics[tidIdx].tid;
                  entry.ripperSlug  = topics[tidIdx].slug || topics[tidIdx].tid;
                  wlSave(cur); renderWatchlist();
                  done();
                } else {
                  checkNext(tidIdx + 1);
                }
              });
            }
            checkNext(0);
          });
        }, (idx - 1) * ITEM_DELAY_MS);
      }

      scheduleNext();
    }

    _recheckFn = runWatchlistCheck;

    // ── Search ──
    function wireSearchResults() {
      const lfBtn = panel.querySelector("#ah-lf-open");
      if (lfBtn) lfBtn.addEventListener("click", () => showLFModal());

      const wlBtn = panel.querySelector("#ah-wl-add");
      if (wlBtn) {
        if (wlGet().find(x => x.url === window.location.href)) {
          wlBtn.textContent = `✓ ${t("inWatch")}`; wlBtn.disabled = true;
        }
        wlBtn.addEventListener("click", () => {
          wlAdd({ name, url: window.location.href, id: query, status: "pending" });
          wlBtn.textContent = `✓ ${t("inWatch")}`; wlBtn.disabled = true;
          updateWlCount();
        });
      }
    }

    function search(q) {
      if (!q) return;
      inp.value     = q;
      out.innerHTML = `<div class="ah-loading"><span class="ah-spinner"></span>${t("searching")}</div>`;
      doSearch(q, (err, data) => {
        lastSearchData = err ? null : data;
        out.innerHTML = err
          ? `<div class="ah-error">&#9888; ${esc(err)}</div>`
          : renderResults(data, panel);
        wireSearchResults();
      });
    }

    panel.querySelector("#ah-search-btn").addEventListener("click", () => search(inp.value.trim()));
    inp.addEventListener("keydown", e => { if (e.key === "Enter") search(inp.value.trim()); });

    const manualBtn = panel.querySelector("#ah-manual-search-btn");
    manualBtn.addEventListener("click", () => {
      manualBtn.style.display = "none";
      panel.querySelector("#ah-search-row").style.display = "";
      search(inp.value.trim());
    });

    // ── Minimize ──
    const collapsible = panel.querySelector("#ah-collapsible");
    const minBtn      = panel.querySelector("#ah-minimize");
    let collapsed     = false;
    minBtn.addEventListener("click", e => {
      e.stopPropagation();
      collapsed = !collapsed;
      collapsible.classList.toggle("ah-collapsed", collapsed);
      minBtn.querySelector("svg").style.transform = collapsed ? "rotate(45deg)" : "";
      minBtn.title = collapsed ? t("expand") : t("minimize");
      const kofiCard = document.getElementById("ah-kofi-card");
      if (kofiCard) kofiCard.style.display = collapsed ? "none" : "";
    });

    // ── Match mode toggle ──
    const matchModeBtn = panel.querySelector("#ah-match-mode-btn");
    if (matchModeBtn) {
      matchModeBtn.addEventListener("click", () => {
        _matchWords = _matchWords === "any" ? "all" : "any";
        matchModeBtn.dataset.match = _matchWords;
        matchModeBtn.textContent = _matchWords === "all" ? t("matchModeToggle_all") : t("matchModeToggle_any");
      });
    }

    // ── Start minimized ──
    if (getSetting("startMinimized")) {
      collapsed = true;
      collapsible.classList.add("ah-collapsed");
      minBtn.querySelector("svg").style.transform = "rotate(45deg)";
      minBtn.title = t("expand");
      // Ko-fi card isn't in DOM yet at this point; it gets hidden after it's appended below
    }

    // ── Search mode toggle ──
    let currentSearchMode = getSetting("defaultSearchMode") || "name";
    const searchModeBtn = panel.querySelector("#ah-search-mode-btn");
    if (searchModeBtn) {
      function applySearchMode(mode) {
        currentSearchMode = mode;
        searchModeBtn.dataset.mode = mode;
        searchModeBtn.textContent = mode === "url" ? t("searchModeToggle_url") : t("searchModeToggle_name");
        inp.value = mode === "url" ? window.location.href : query;
      }
      searchModeBtn.addEventListener("click", () => {
        applySearchMode(currentSearchMode === "url" ? "name" : "url");
      });
    }

    // ── Hotkey listener ──
    function matchesHotkey(e, hotkeyStr) {
      if (!hotkeyStr) return false;
      const parts = hotkeyStr.split("+").map(s => s.trim().toLowerCase());
      const key = parts[parts.length - 1];
      const needCtrl  = parts.includes("ctrl");
      const needAlt   = parts.includes("alt");
      const needShift = parts.includes("shift");
      if (needCtrl  !== e.ctrlKey)  return false;
      if (needAlt   !== e.altKey)   return false;
      if (needShift !== e.shiftKey) return false;
      return e.key.toLowerCase() === key || e.code.toLowerCase() === ("key" + key) || e.code.toLowerCase() === key;
    }
    document.addEventListener("keydown", function hotkeyHandler(e) {
      if (!document.getElementById("ah-panel")) {
        document.removeEventListener("keydown", hotkeyHandler, true);
        return;
      }
      const hotkey = getSetting("minimizeHotkey");
      if (!hotkey) return;
      const tag = (document.activeElement || {}).tagName;
      if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
      if (!matchesHotkey(e, hotkey)) return;
      e.preventDefault();
      minBtn.click();
    }, true);

    if (getSetting("autoSearch")) {
      const initialQuery = currentSearchMode === "url" ? window.location.href : query;
      search(initialQuery);
    } else {
      if (currentSearchMode === "url") inp.value = window.location.href;
      manualBtn.style.display = "";
      panel.querySelector("#ah-search-row").style.display = "none";
    }
  }

  // ─── Panel CSS ────────────────────────────────────────────────────────────
  function injectCSS() {
    if (document.getElementById("ah-css")) return;
    const s = document.createElement("style");
    s.id = "ah-css";
    s.textContent = `
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400&family=Noto+Sans+JP:wght@400;500;700&display=swap');

#ah-panel {
  --ah-bg0: #0c0c0e;
  --ah-bg1: #111115;
  --ah-bg2: #16161b;
  --ah-bg3: #1c1c23;
  --ah-border: rgba(255,255,255,.07);
  --ah-border-h: rgba(255,255,255,.14);
  --ah-txt: #e8e8f0;
  --ah-txt2: #6b6b80;
  --ah-muted: #3a3a48;
  --ah-accent: #c8a8ff;
  --ah-accent-dim: rgba(200,168,255,.12);
  --ah-dl: #72f0a8;
  --ah-dl-bg: rgba(114,240,168,.06);
  --ah-dl-bd: rgba(114,240,168,.18);
  --ah-disc: #9898ff;
  --ah-disc-bg: rgba(152,152,255,.06);
  --ah-disc-bd: rgba(152,152,255,.18);
  --ah-r: 10px;
  --ah-r-sm: 6px;
  --ah-f: 'Space Mono','Noto Sans JP',monospace;
  --ah-gold:   #ffd700;
  --ah-silver: #c0c8d8;
  --ah-bronze: #cd8a4a;
}

#ah-panel {
  position:fixed;bottom:24px;right:22px;z-index:999999;
  width:352px;
  max-height:600px;
  background:var(--ah-bg0);border:1px solid var(--ah-border);border-radius:var(--ah-r);
  box-shadow:0 0 0 1px rgba(255,255,255,.03),0 4px 6px rgba(0,0,0,.4),
             0 20px 60px rgba(0,0,0,.8),inset 0 1px 0 rgba(255,255,255,.04);
  font-family:var(--ah-f);color:var(--ah-txt);
  display:flex;flex-direction:column;overflow:hidden;font-size:11px;
}

/* Header */
#ah-header {
  display:flex;align-items:center;justify-content:space-between;
  padding:11px 14px;background:var(--ah-bg1);
  border-bottom:1px solid var(--ah-border);flex-shrink:0;user-select:none;
}
#ah-header-left { display:flex;align-items:center;gap:7px; }
.ah-logo-star   { color:var(--ah-accent);flex-shrink:0;opacity:.85; }
#ah-title       { font-size:9.5px;font-weight:700;letter-spacing:4px;text-transform:uppercase;font-style:italic; }
#ah-header-right { display:flex;align-items:center;gap:8px; }
#ah-booth-badge {
  font-size:8px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;
  color:#ff1428;padding:2px 7px;border-radius:3px;
  background:rgba(255,20,40,.08);border:1px solid rgba(255,20,40,.2);
}
#ah-minimize {
  background:none;border:none;color:var(--ah-muted);cursor:pointer;
  padding:3px;display:flex;align-items:center;border-radius:4px;
  transition:color .15s,background .15s;
}
#ah-minimize:hover { color:var(--ah-txt);background:var(--ah-bg3); }
#ah-minimize svg  { transition:transform .25s ease; }

/* Collapse */
#ah-collapsible {
  display:flex;flex-direction:column;
  overflow:hidden;flex:1;min-height:0;
  max-height:10000px;
  transition:max-height .3s cubic-bezier(.4,0,.2,1),opacity .25s ease;
  opacity:1;
}
#ah-collapsible.ah-collapsed { max-height:0!important;opacity:0;pointer-events:none; }

/* Tabs */
#ah-tabs {
  display:flex;align-items:center;background:var(--ah-bg1);
  border-bottom:1px solid var(--ah-border);flex-shrink:0;padding:0 2px;
}
.ah-tab {
  flex:1;padding:8px 0;background:none;border:none;border-bottom:2px solid transparent;
  color:var(--ah-muted);font-family:var(--ah-f);font-size:8.5px;font-weight:700;
  letter-spacing:2px;text-transform:uppercase;cursor:pointer;
  transition:color .15s,border-color .15s;margin-bottom:-1px;
  display:flex;align-items:center;justify-content:center;gap:4px;
}
.ah-tab:hover      { color:var(--ah-txt2); }
.ah-tab--active    { color:var(--ah-txt)!important;border-bottom-color:var(--ah-accent)!important; }
.ah-tab--icon      { flex:0 0 34px; }
#ah-wl-count       { font-size:8px;opacity:.6; }
.ah-wl-bump-all-btns { display:flex;gap:6px;align-items:center; }
#ah-recheck-btn {
  background:none;border:1px solid rgba(200,168,255,.2);color:var(--ah-muted);cursor:pointer;
  padding:6px 8px;display:flex;align-items:center;border-radius:var(--ah-r-sm);
  transition:color .15s, border-color .15s;flex-shrink:0;
}
#ah-recheck-btn:hover { color:var(--ah-txt);border-color:rgba(200,168,255,.4); }

.ah-tab--supporters {
  flex:0 0 30px;
  font-size:13px;
  letter-spacing:0;
  text-transform:none;
  color:#8a7020;
  text-shadow: none;
  transition: color .2s, text-shadow .2s;
  animation: ah-sup-tab-pulse 3s ease-in-out infinite;
}
.ah-tab--supporters:hover,
.ah-tab--supporters.ah-tab--active {
  color: var(--ah-gold) !important;
  text-shadow:
    0 0 6px rgba(255,215,0,.9),
    0 0 14px rgba(255,215,0,.6),
    0 0 28px rgba(255,180,0,.4);
  border-bottom-color: var(--ah-gold) !important;
}
@keyframes ah-sup-tab-pulse {
  0%,100% { color:#8a7020; text-shadow:none; }
  50% {
    color:#d4a800;
    text-shadow: 0 0 8px rgba(255,215,0,.7), 0 0 18px rgba(255,180,0,.4);
  }
}

/* Panes */
#ah-pane-search,#ah-pane-watchlist,#ah-pane-supporters,#ah-pane-settings {
  padding:12px 13px;overflow-y:auto;flex:1;min-height:0;
}
#ah-pane-search::-webkit-scrollbar,
#ah-pane-watchlist::-webkit-scrollbar,
#ah-pane-supporters::-webkit-scrollbar,
#ah-pane-settings::-webkit-scrollbar  { width:2px; }
#ah-pane-search::-webkit-scrollbar-thumb,
#ah-pane-watchlist::-webkit-scrollbar-thumb,
#ah-pane-supporters::-webkit-scrollbar-thumb,
#ah-pane-settings::-webkit-scrollbar-thumb { background:var(--ah-bg3);border-radius:2px; }

/* Item info */
#ah-item-info {
  margin-bottom:10px;padding:8px 10px;
  background:var(--ah-bg2);border-radius:var(--ah-r-sm);border:1px solid var(--ah-border);
}
#ah-item-name {
  font-size:11px;font-weight:700;color:var(--ah-txt);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.4;
}
#ah-item-id { font-size:9px;color:var(--ah-muted);margin-top:2px;letter-spacing:1px; }

/* Manual search trigger button */
#ah-manual-search-btn {
  display:flex;align-items:center;justify-content:center;gap:8px;
  width:100%;padding:14px 16px;margin-bottom:12px;
  background:var(--ah-accent-dim);
  border:1px solid rgba(200,168,255,.25);border-radius:var(--ah-r-sm);
  color:var(--ah-accent);font-family:var(--ah-f);
  font-size:10px;font-weight:700;letter-spacing:2px;text-transform:uppercase;
  cursor:pointer;
  transition:background .15s,border-color .15s,box-shadow .15s;
  box-sizing:border-box;
}
#ah-manual-search-btn:hover {
  background:rgba(200,168,255,.2);border-color:rgba(200,168,255,.45);
  box-shadow:0 0 0 1px rgba(200,168,255,.12),0 4px 16px rgba(200,168,255,.08);
}

/* Search mode toggle row */
#ah-search-type-row {
  display:flex;gap:5px;margin-bottom:8px;
}
.ah-search-mode-btn {
  padding:5px 12px;
  background:rgba(200,168,255,.06);
  border:1px solid rgba(200,168,255,.18);
  border-radius:var(--ah-r-sm);
  color:rgba(200,168,255,.55);
  font-family:var(--ah-f);font-size:8px;font-weight:700;
  letter-spacing:1.5px;text-transform:uppercase;
  cursor:pointer;transition:background .15s,border-color .15s,color .15s;
}
.ah-search-mode-btn:hover {
  background:rgba(200,168,255,.12);
  border-color:rgba(200,168,255,.32);
  color:var(--ah-accent);
}
.ah-search-mode-btn[data-mode="url"] {
  background:rgba(114,240,168,.06);
  border-color:rgba(114,240,168,.22);
  color:rgba(114,240,168,.7);
}
.ah-search-mode-btn[data-mode="url"]:hover {
  background:rgba(114,240,168,.12);
  border-color:rgba(114,240,168,.4);
  color:var(--ah-dl);
}

.ah-search-mode-btn[data-match="all"] {
  background:rgba(255,215,0,.06);
  border-color:rgba(255,215,0,.22);
  color:rgba(255,215,0,.7);
}
.ah-search-mode-btn[data-match="all"]:hover {
  background:rgba(255,215,0,.12);
  border-color:rgba(255,215,0,.4);
  color:var(--ah-gold);
}

/* Search row */
#ah-search-row { display:flex;gap:6px;margin-bottom:12px; }
#ah-input {
  flex:1;padding:7px 10px;
  background:#16161b !important;
  border:1px solid var(--ah-border);border-radius:var(--ah-r-sm);
  color:var(--ah-txt) !important;font-family:var(--ah-f);font-size:10.5px;
  outline:none !important;box-shadow:none !important;-webkit-appearance:none;
  transition:border-color .15s;
  -webkit-box-shadow:0 0 0 1000px #16161b inset !important;
  -webkit-text-fill-color:var(--ah-txt) !important;
  caret-color:var(--ah-accent);
}
#ah-input:focus {
  outline:none !important;box-shadow:none !important;
  border-color:rgba(200,168,255,.35);
  background:#16161b !important;
  -webkit-box-shadow:0 0 0 1000px #16161b inset !important;
}
#ah-input::placeholder { color:var(--ah-muted); }
#ah-search-btn {
  padding:7px 12px;background:var(--ah-accent-dim);
  border:1px solid rgba(200,168,255,.22);border-radius:var(--ah-r-sm);
  color:var(--ah-accent);font-family:var(--ah-f);font-size:9px;font-weight:700;
  letter-spacing:1.5px;text-transform:uppercase;cursor:pointer;white-space:nowrap;
  transition:background .15s,border-color .15s;
}
#ah-search-btn:hover { background:rgba(200,168,255,.2);border-color:rgba(200,168,255,.4); }

/* Loading/error */
.ah-loading {
  display:flex;align-items:center;gap:8px;padding:20px 0;justify-content:center;
  color:var(--ah-txt2);font-size:9.5px;letter-spacing:2px;text-transform:uppercase;
}
.ah-spinner {
  width:12px;height:12px;border:1.5px solid var(--ah-bg3);
  border-top-color:var(--ah-accent);border-radius:50%;
  animation:ah-spin .7s linear infinite;flex-shrink:0;
}
@keyframes ah-spin { to { transform:rotate(360deg); } }
.ah-error      { color:#ff6680;font-size:10.5px;padding:12px 0;text-align:center; }
.ah-no-results { color:var(--ah-muted);font-size:10px;padding:20px 0 6px;text-align:center;letter-spacing:1.5px;text-transform:uppercase; }
.ah-result-count { font-size:8.5px;color:var(--ah-muted);letter-spacing:2.5px;text-transform:uppercase;margin-bottom:10px; }

/* Section labels */
.ah-section-label {
  font-size:8px;font-weight:700;letter-spacing:3px;text-transform:uppercase;
  padding:4px 8px;border-radius:4px;margin:10px 0 6px;display:flex;align-items:center;gap:5px;
}
.ah-section--dl    { color:var(--ah-dl);  background:var(--ah-dl-bg);  border:1px solid var(--ah-dl-bd); }
.ah-section--disc  { color:var(--ah-disc);background:var(--ah-disc-bg);border:1px solid var(--ah-disc-bd); }
.ah-section--other { color:var(--ah-muted);background:var(--ah-bg2);border:1px solid var(--ah-border); }

/* Cards */
.ah-list  { display:flex;flex-direction:column;gap:5px; }
.ah-card  {
  display:block;padding:9px 11px;border-radius:var(--ah-r-sm);text-decoration:none;color:inherit;
  border:1px solid var(--ah-border);background:var(--ah-bg2);
  transition:border-color .15s,background .15s;
}
.ah-card:hover        { border-color:var(--ah-border-h);background:var(--ah-bg3); }
.ah-card--dl          { background:var(--ah-dl-bg);border-color:var(--ah-dl-bd); }
.ah-card--dl:hover    { border-color:rgba(114,240,168,.35);background:rgba(114,240,168,.09); }
.ah-card--disc        { background:var(--ah-disc-bg);border-color:var(--ah-disc-bd); }
.ah-card--disc:hover  { border-color:rgba(152,152,255,.35);background:rgba(152,152,255,.09); }
.ah-card-top          { display:flex;align-items:center;gap:5px;margin-bottom:5px;flex-wrap:wrap; }
.ah-card-bump-wrap    { margin-left:auto; }
.ah-badge             { font-size:7.5px;font-weight:700;letter-spacing:1px;text-transform:uppercase;padding:2px 6px;border-radius:3px;border:1px solid transparent; }
.ah-cat               { font-size:7.5px;font-weight:700;padding:2px 6px;border-radius:3px;border:1px solid transparent;max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; }
.ah-dl-chip {
  font-size:7.5px;font-weight:700;letter-spacing:1px;
  color:var(--ah-dl);padding:2px 7px;border-radius:3px;
  background:rgba(114,240,168,.1);border:1px solid rgba(114,240,168,.25);
  margin-left:auto;
  animation:ah-dl-pulse 2.5s ease-in-out infinite;
}
@keyframes ah-dl-pulse { 0%,100%{opacity:1} 50%{opacity:.6} }
.ah-card-title        { font-size:11px;font-weight:700;color:var(--ah-txt);margin-bottom:5px;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden; }
.ah-card--dl   .ah-card-title { color:var(--ah-dl); }
.ah-card--disc .ah-card-title { color:var(--ah-disc); }
.ah-card-meta         { display:flex;gap:8px;font-size:9px;color:var(--ah-muted);flex-wrap:wrap; }
.ah-card-user         { color:var(--ah-txt2); }
.ah-tags              { display:flex;flex-wrap:wrap;gap:3px;margin-top:6px; }
.ah-tag               { font-size:8px;padding:1px 5px;border-radius:3px;background:var(--ah-bg3);color:var(--ah-muted);border:1px solid var(--ah-border); }
.ah-card--dl   .ah-tag { background:rgba(114,240,168,.06);color:rgba(114,240,168,.5);border-color:rgba(114,240,168,.1); }
.ah-card--disc .ah-tag { background:rgba(152,152,255,.06);color:rgba(152,152,255,.5);border-color:rgba(152,152,255,.1); }

/* Bump button row */
.ah-bump-btn {
  display:inline-flex;align-items:center;justify-content:center;
  padding:4px 10px;
  font-family:var(--ah-f);font-size:8px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;
  color:rgba(200,168,255,.7);
  background:rgba(200,168,255,.08);
  border:1px solid rgba(200,168,255,.2);
  border-radius:4px;
  cursor:pointer;
  transition:background .15s,border-color .15s,color .15s,opacity .15s;
  user-select:none;
}
.ah-bump-btn:hover:not(:disabled):not([data-bumped="1"]) {
  background:rgba(200,168,255,.16);
  border-color:rgba(200,168,255,.4);
  color:var(--ah-accent);
}
.ah-bump-btn:disabled { opacity:.5;cursor:default; }
.ah-bump-btn--sending {
  color:var(--ah-muted);
  background:rgba(255,255,255,.04);
  border-color:rgba(255,255,255,.08);
  animation:ah-bump-sending .8s ease-in-out infinite;
}
@keyframes ah-bump-sending { 0%,100%{opacity:.5} 50%{opacity:1} }
.ah-bump-btn--done {
  color:var(--ah-dl);
  background:rgba(114,240,168,.08);
  border-color:rgba(114,240,168,.2);
  cursor:default;
}
.ah-bump-btn--err {
  color:#ff6680;
  background:rgba(255,102,128,.08);
  border-color:rgba(255,102,128,.2);
}

/* Bottom actions */
.ah-bottom-actions { display:flex;gap:6px;margin-top:12px; }
.ah-btn-watch {
  flex:1;padding:8px;background:var(--ah-bg2);border:1px solid var(--ah-border);
  border-radius:var(--ah-r-sm);color:var(--ah-txt2);font-family:var(--ah-f);
  font-size:8.5px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;
  cursor:pointer;transition:all .15s;
}
.ah-btn-watch:hover:not(:disabled) { border-color:var(--ah-border-h);color:var(--ah-txt); }
.ah-btn-watch:disabled { opacity:.4;cursor:default; }
.ah-btn-lf {
  flex:1;padding:8px;background:var(--ah-accent-dim);
  border:1px solid rgba(200,168,255,.2);border-radius:var(--ah-r-sm);
  color:var(--ah-accent);font-family:var(--ah-f);font-size:8.5px;font-weight:700;
  letter-spacing:1.5px;text-transform:uppercase;cursor:pointer;transition:all .15s;
}
.ah-btn-lf:hover { background:rgba(200,168,255,.2);border-color:rgba(200,168,255,.4); }

/* LF prompt */
.ah-lf-prompt  { margin-top:8px;padding:12px;background:var(--ah-bg2);border:1px solid var(--ah-border);border-radius:var(--ah-r-sm);text-align:center; }
.ah-lf-prompt p { font-size:10px;color:var(--ah-txt2);margin:0 0 10px;line-height:1.5; }

/* Watchlist */
.ah-wl-empty { color:var(--ah-muted);font-size:9.5px;letter-spacing:1px;text-align:center;padding:24px 0;text-transform:uppercase; }
.ah-wl-item  { padding:9px 10px;margin-bottom:5px;background:var(--ah-bg2);border:1px solid var(--ah-border);border-radius:var(--ah-r-sm);transition:border-color .15s; }
.ah-wl-item--dl    { background:var(--ah-dl-bg);  border-color:var(--ah-dl-bd); }
.ah-wl-item--found { background:var(--ah-disc-bg);border-color:var(--ah-disc-bd); }
.ah-wl-row1  { display:flex;align-items:center;gap:6px;margin-bottom:4px; }
.ah-wl-badge { font-size:7.5px;font-weight:700;letter-spacing:1px;text-transform:uppercase;padding:2px 7px;border-radius:3px; }
.ah-wl-badge--dl      { color:var(--ah-dl);  background:rgba(114,240,168,.1);border:1px solid rgba(114,240,168,.2); }
.ah-wl-badge--disc    { color:var(--ah-disc);background:rgba(152,152,255,.1);border:1px solid rgba(152,152,255,.2); }
.ah-wl-badge--none    { color:var(--ah-muted);background:var(--ah-bg3);border:1px solid var(--ah-border); }
.ah-wl-badge--pending { color:var(--ah-muted);background:var(--ah-bg3);border:1px solid var(--ah-border); }
.ah-wl-remove {
  background:none;border:none;color:var(--ah-muted);font-size:10px;cursor:pointer;
  padding:1px 4px;border-radius:3px;transition:color .12s;line-height:1;
  margin-left:auto;
}
.ah-wl-remove:hover { color:#ff6680; }
.ah-wl-name  { font-size:10.5px;font-weight:700;color:var(--ah-txt);margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; }
.ah-wl-item.ah-wl-item--dl    .ah-wl-name { color:var(--ah-dl); }
.ah-wl-item.ah-wl-item--found .ah-wl-name { color:var(--ah-disc); }
.ah-wl-row2  { display:flex;align-items:center;justify-content:flex-start;flex-wrap:wrap;gap:6px; }
.ah-wl-ts    { font-size:8.5px;color:var(--ah-muted);margin-left:auto; }
.ah-wl-link  { font-size:9px;color:var(--ah-muted);text-decoration:none;transition:color .12s; }
.ah-wl-link:hover { color:var(--ah-txt); }
.ah-wl-link--ripper { color:var(--ah-dl) !important;opacity:.8; }
.ah-wl-link--ripper:hover { opacity:1 !important; }
.ah-wl-item--found .ah-wl-link--ripper { color:var(--ah-disc) !important; }

/* Watchlist bump all */
.ah-wl-bump-all-row {
  margin-bottom:10px;
  display:flex;flex-direction:column;gap:6px;
}
.ah-wl-bump-all-btn {
  width:100%;padding:8px;
  background:rgba(200,168,255,.08);border:1px solid rgba(200,168,255,.2);
  border-radius:var(--ah-r-sm);color:var(--ah-accent);
  font-family:var(--ah-f);font-size:8.5px;font-weight:700;
  letter-spacing:1.5px;text-transform:uppercase;cursor:pointer;transition:all .15s;
}
.ah-wl-bump-all-btn:hover:not(:disabled) { background:rgba(200,168,255,.15);border-color:rgba(200,168,255,.4); }
.ah-wl-bump-all-btn:disabled { opacity:.5;cursor:default; }
.ah-wl-bump-progress {
  width:100%;
}
.ah-wl-bump-progress-bar-wrap {
  width:100%;height:4px;background:var(--ah-bg3);border-radius:2px;overflow:hidden;margin-bottom:4px;
}
.ah-wl-bump-progress-bar {
  height:4px;background:var(--ah-accent);border-radius:2px;
  width:0%;transition:width .4s ease;
}
.ah-wl-bump-progress-text {
  display:block;font-size:8.5px;color:var(--ah-txt2);letter-spacing:.5px;
}
.ah-wl-item--bumping {
  border-color:rgba(200,168,255,.4) !important;
  background:rgba(200,168,255,.05) !important;
  animation:ah-wl-bumping-pulse 1s ease-in-out infinite;
}
@keyframes ah-wl-bumping-pulse {
  0%,100%{opacity:.7} 50%{opacity:1}
}

/* Supporters pane */
.ah-sup-loading {
  display:flex;align-items:center;justify-content:center;padding:32px 0;
}
.ah-sup-header {
  text-align:center;margin-bottom:16px;padding-bottom:14px;
  border-bottom:1px solid rgba(255,215,0,.1);
}
.ah-sup-title {
  font-size:11px;font-weight:700;letter-spacing:3px;text-transform:uppercase;
  color:var(--ah-gold);
  text-shadow:0 0 10px rgba(255,215,0,.5),0 0 24px rgba(255,180,0,.3);
  margin-bottom:5px;
}
.ah-sup-subtitle {
  font-size:8.5px;color:var(--ah-txt2);letter-spacing:.5px;line-height:1.5;
  margin-bottom:8px;
}
.ah-sup-kofi-link {
  display:inline-block;font-size:9px;font-weight:700;letter-spacing:1px;
  color:#ff5e5b;text-decoration:none;
  transition:color .15s,text-shadow .15s;
}
.ah-sup-kofi-link:hover {
  color:#ff8f8d;
  text-shadow:0 0 8px rgba(255,94,91,.5);
}
.ah-sup-empty {
  text-align:center;padding:28px 12px;
  font-size:10px;color:rgba(255,255,255,.12);
  line-height:1.8;letter-spacing:.3px;
}
.ah-sup-empty-sub {
  display:block;font-size:9px;color:rgba(255,255,255,.08);margin-top:4px;
}
.ah-sup-list { display:flex;flex-direction:column;gap:3px; }
.ah-sup-entry {
  display:flex;align-items:center;gap:0;
  padding:6px 10px;border-radius:var(--ah-r-sm);
  border:1px solid transparent;
  transition:background .15s,border-color .15s;
  position:relative;overflow:hidden;
}
.ah-sup-pos { flex:0 0 22px;text-align:center;font-size:11px;line-height:1; }
.ah-sup-name { flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding: 0 10px; }
.ah-sup-amount { flex-shrink:0;font-size:10px;font-weight:700;letter-spacing:.5px; }
.ah-sup-rank--1 {
  background: linear-gradient(90deg, rgba(255,215,0,.1) 0%, rgba(255,180,0,.04) 100%);
  border-color: rgba(255,215,0,.25);
}
.ah-sup-rank--1 .ah-sup-pos { font-size:14px;color:var(--ah-gold);text-shadow:0 0 6px rgba(255,215,0,1),0 0 14px rgba(255,200,0,.8),0 0 28px rgba(255,160,0,.5);animation:ah-gold-pulse 2.2s ease-in-out infinite; }
.ah-sup-rank--1 .ah-sup-name { font-size:13px;font-weight:700;color:var(--ah-gold);text-shadow:0 0 8px rgba(255,215,0,.7),0 0 20px rgba(255,180,0,.4);letter-spacing:.5px; }
.ah-sup-rank--1 .ah-sup-amount { font-size:12px;color:var(--ah-gold);text-shadow:0 0 8px rgba(255,215,0,.6); }
@keyframes ah-gold-pulse {
  0%,100% { text-shadow:0 0 6px rgba(255,215,0,1),0 0 14px rgba(255,200,0,.8),0 0 28px rgba(255,160,0,.5); }
  50%      { text-shadow:0 0 10px rgba(255,230,0,1),0 0 24px rgba(255,210,0,1),0 0 40px rgba(255,170,0,.7); }
}
.ah-sup-rank--2 { background:linear-gradient(90deg,rgba(192,200,216,.08) 0%,rgba(192,200,216,.02) 100%);border-color:rgba(192,200,216,.18); }
.ah-sup-rank--2 .ah-sup-pos { font-size:13px;color:var(--ah-silver);text-shadow:0 0 6px rgba(200,210,230,.6),0 0 14px rgba(180,190,210,.3); }
.ah-sup-rank--2 .ah-sup-name { font-size:12px;font-weight:700;color:var(--ah-silver);text-shadow:0 0 6px rgba(200,210,230,.4);letter-spacing:.3px; }
.ah-sup-rank--2 .ah-sup-amount { font-size:11px;color:var(--ah-silver);text-shadow:0 0 5px rgba(200,210,230,.4); }
.ah-sup-rank--3 { background:linear-gradient(90deg,rgba(205,138,74,.08) 0%,rgba(205,138,74,.02) 100%);border-color:rgba(205,138,74,.18); }
.ah-sup-rank--3 .ah-sup-pos { font-size:12px;color:var(--ah-bronze);text-shadow:0 0 5px rgba(205,138,74,.6),0 0 12px rgba(180,110,50,.3); }
.ah-sup-rank--3 .ah-sup-name { font-size:11px;font-weight:700;color:var(--ah-bronze);text-shadow:0 0 5px rgba(205,138,74,.4); }
.ah-sup-rank--3 .ah-sup-amount { font-size:10.5px;color:var(--ah-bronze);text-shadow:0 0 4px rgba(205,138,74,.4); }
.ah-sup-rank--mid { background:transparent;border-color:rgba(152,152,255,.08); }
.ah-sup-rank--mid .ah-sup-pos { font-size:10px;color:rgba(152,152,255,.45); }
.ah-sup-rank--mid .ah-sup-name { font-size:10.5px;font-weight:400;color:rgba(232,232,240,.55); }
.ah-sup-rank--mid .ah-sup-amount { font-size:10px;color:rgba(232,232,240,.4); }
.ah-sup-rank--low { background:transparent;border-color:transparent; }
.ah-sup-rank--low .ah-sup-pos { font-size:9px;color:var(--ah-muted); }
.ah-sup-rank--low .ah-sup-name { font-size:10px;font-weight:400;color:var(--ah-muted); }
.ah-sup-rank--low .ah-sup-amount { font-size:9.5px;color:rgba(107,107,128,.6); }

/* Settings pane */
.ah-set-section {
  font-size:8px;font-weight:700;letter-spacing:3px;text-transform:uppercase;
  color:var(--ah-muted);margin-bottom:12px;padding-bottom:6px;
  border-bottom:1px solid var(--ah-border);
}
.ah-set-section--danger {
  color:rgba(255,102,128,.5);border-color:rgba(255,102,128,.15);margin-top:8px;
}
.ah-set-field       { margin-bottom:14px; }
.ah-set-field-hd    { display:flex;align-items:center;justify-content:space-between;margin-bottom:3px; }
.ah-set-label       { font-size:9px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:var(--ah-txt2); }
.ah-set-hint        { font-size:8.5px;color:var(--ah-muted);margin-bottom:5px;line-height:1.5; }
.ah-set-hint code   { font-family:var(--ah-f);font-size:8.5px;color:var(--ah-accent);background:var(--ah-accent-dim);padding:1px 4px;border-radius:3px; }
.ah-set-input {
  display:block;width:100%;box-sizing:border-box;
  padding:8px 10px;background:#16161b !important;
  border:1px solid rgba(255,255,255,.07);border-radius:var(--ah-r-sm);
  color:var(--ah-txt) !important;font-family:var(--ah-f);font-size:10.5px;line-height:1.6;
  outline:none !important;box-shadow:none !important;-webkit-appearance:none;appearance:none;
  transition:border-color .15s;resize:vertical;
  -webkit-box-shadow:0 0 0 1000px #16161b inset !important;
  -webkit-text-fill-color:var(--ah-txt) !important;caret-color:var(--ah-accent);
}
textarea.ah-set-input.ah-set-input--single {
  min-height:unset;height:36px;resize:none;overflow:hidden;white-space:nowrap;
}
textarea.ah-set-input:not(.ah-set-input--single) { min-height:100px;resize:vertical; }
.ah-set-input:focus {
  outline:none !important;box-shadow:none !important;
  border-color:rgba(200,168,255,.4);background:#16161b !important;
  -webkit-box-shadow:0 0 0 1000px #16161b inset !important;
}
.ah-set-input:-webkit-autofill,.ah-set-input:-webkit-autofill:focus {
  outline:none !important;
  -webkit-box-shadow:0 0 0 1000px #16161b inset !important;
  -webkit-text-fill-color:var(--ah-txt) !important;
}

/* Custom language picker (replaces native select) */
.ah-lang-picker { position:relative; }
.ah-lang-picker-btn {
  display:flex;align-items:center;justify-content:space-between;
  width:100%;box-sizing:border-box;
  padding:8px 10px;
  background:#16161b;border:1px solid rgba(255,255,255,.07);border-radius:var(--ah-r-sm);
  color:var(--ah-txt);
  font-family:system-ui,-apple-system,'Noto Sans JP','Noto Sans','DejaVu Sans',sans-serif;
  font-size:10.5px;cursor:pointer;text-align:left;transition:border-color .15s;
}
.ah-lang-picker-btn:hover,.ah-lang-picker-btn--open {
  border-color:rgba(200,168,255,.4);
}
.ah-lang-picker-btn svg { flex-shrink:0;color:var(--ah-muted);transition:transform .2s; }
.ah-lang-picker-btn--open svg { transform:rotate(180deg); }
.ah-lang-picker-list {
  display:none;position:absolute;left:0;right:0;top:calc(100% + 3px);z-index:9999;
  background:#111115;border:1px solid rgba(200,168,255,.18);border-radius:var(--ah-r-sm);
  box-shadow:0 4px 16px rgba(0,0,0,.55),0 8px 28px rgba(0,0,0,.4);overflow:hidden;
}
.ah-lang-picker-list--open { display:block; }
.ah-lang-picker-opt {
  display:block;width:100%;padding:8px 12px;
  background:none;border:none;border-bottom:1px solid rgba(255,255,255,.04);
  color:var(--ah-txt2);text-align:left;cursor:pointer;
  font-family:system-ui,-apple-system,'Noto Sans JP','Noto Sans','DejaVu Sans',sans-serif;
  font-size:10.5px;transition:background .12s,color .12s;
}
.ah-lang-picker-opt:last-child { border-bottom:none; }
.ah-lang-picker-opt:hover { background:rgba(255,255,255,.06);color:var(--ah-txt); }
.ah-lang-picker-opt--active { color:var(--ah-accent);background:var(--ah-accent-dim); }
.ah-set-reset {
  background:none;border:1px solid var(--ah-border);border-radius:4px;
  color:var(--ah-muted);font-family:var(--ah-f);font-size:8px;letter-spacing:.5px;
  padding:2px 7px;cursor:pointer;transition:all .14s;white-space:nowrap;
}
.ah-set-reset:hover { color:var(--ah-txt);border-color:var(--ah-border-h); }
.ah-set-wm-box  { margin-top:6px;padding:7px 10px;background:rgba(255,255,255,.02);border:1px solid rgba(255,255,255,.04);border-radius:var(--ah-r-sm); }
.ah-set-wm-label { display:block;font-size:7.5px;letter-spacing:1.5px;text-transform:uppercase;color:var(--ah-muted);margin-bottom:4px; }
.ah-set-wm-pre  { margin:0;font-family:var(--ah-f);font-size:9.5px;color:rgba(255,255,255,.18);white-space:pre-wrap;line-height:1.6; }
.ah-set-toggle-row {
  display:flex;align-items:flex-start;justify-content:space-between;gap:14px;
  padding:10px 12px;margin-bottom:10px;
  background:var(--ah-bg2);border:1px solid var(--ah-border);border-radius:var(--ah-r-sm);
}
.ah-set-toggle-info  { display:flex;flex-direction:column;gap:3px; }
.ah-set-toggle-label { font-size:10px;font-weight:700;color:var(--ah-txt); }
.ah-set-toggle-hint  { font-size:8.5px;color:var(--ah-muted);line-height:1.5;max-width:220px; }
.ah-set-toggle {
  flex-shrink:0;width:34px;height:18px;border-radius:9px;
  background:var(--ah-bg3);border:1px solid var(--ah-border);
  cursor:pointer;position:relative;transition:background .2s,border-color .2s;padding:0;
}
.ah-set-toggle--on   { background:rgba(200,168,255,.28);border-color:rgba(200,168,255,.45); }
.ah-set-toggle-knob  {
  position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;
  background:var(--ah-muted);transition:transform .2s,background .2s;pointer-events:none;
}
.ah-set-toggle--on .ah-set-toggle-knob { transform:translateX(16px);background:var(--ah-accent); }

/* Search mode option buttons in settings */
.ah-set-search-mode-row { display:flex;gap:6px; }
.ah-set-mode-opt {
  flex:1;padding:7px 0;border-radius:var(--ah-r-sm);
  font-family:var(--ah-f);font-size:8.5px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;
  cursor:pointer;transition:all .15s;
  background:var(--ah-bg3);border:1px solid var(--ah-border);color:var(--ah-muted);
}
.ah-set-mode-opt:hover { border-color:var(--ah-border-h);color:var(--ah-txt2); }
.ah-set-mode-opt--active {
  background:rgba(200,168,255,.1);border-color:rgba(200,168,255,.3);color:var(--ah-accent);
}

/* Site enable grid in settings */
.ah-set-sites-grid {
  display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px;
}
.ah-set-site-row {
  display:flex;align-items:center;gap:7px;
  padding:6px 10px;border-radius:var(--ah-r-sm);
  background:var(--ah-bg2);border:1px solid var(--ah-border);
  cursor:pointer;transition:border-color .15s;user-select:none;
}
.ah-set-site-row:hover { border-color:var(--ah-border-h); }
.ah-set-site-row input[type="checkbox"] {
  accent-color:var(--ah-accent);width:12px;height:12px;cursor:pointer;margin:0;flex-shrink:0;
}
.ah-set-site-name { font-size:9px;font-weight:700;letter-spacing:1px;text-transform:uppercase;color:var(--ah-txt2); }
.ah-set-data-actions { display:flex;gap:6px;flex-wrap:wrap; }
.ah-set-data-btn {
  flex:1;min-width:80px;padding:8px 6px;border-radius:var(--ah-r-sm);
  font-family:var(--ah-f);font-size:8px;font-weight:700;letter-spacing:1px;
  text-transform:uppercase;cursor:pointer;transition:all .15s;
  display:flex;align-items:center;justify-content:center;gap:5px;
}
.ah-set-data-btn--export { background:rgba(96,200,255,.08);border:1px solid rgba(96,200,255,.2);color:#60c8ff; }
.ah-set-data-btn--export:hover { background:rgba(96,200,255,.15);border-color:rgba(96,200,255,.4); }
.ah-set-data-btn--import { background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);color:rgba(255,255,255,.35); }
.ah-set-data-btn--import:hover { background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.2);color:rgba(255,255,255,.6); }
.ah-set-data-btn--reset  { background:rgba(255,179,71,.08);border:1px solid rgba(255,179,71,.2);color:#ffb347; }
.ah-set-data-btn--reset:hover { background:rgba(255,179,71,.15);border-color:rgba(255,179,71,.4); }
.ah-set-data-btn--delete { background:rgba(255,86,128,.08);border:1px solid rgba(255,86,128,.2);color:#ff5680; }
.ah-set-data-btn--delete:hover { background:rgba(255,86,128,.15);border-color:rgba(255,86,128,.4); }

/* Changelog button in settings */
.ah-set-changelog-btn-wrap {
  margin-bottom:14px;
}
.ah-set-changelog-btn {
  display:flex;align-items:center;justify-content:center;gap:7px;
  width:100%;padding:9px 12px;
  background:rgba(200,168,255,.06);
  border:1px solid rgba(200,168,255,.18);
  border-radius:var(--ah-r-sm);
  color:rgba(200,168,255,.6);
  font-family:var(--ah-f);font-size:8.5px;font-weight:700;
  letter-spacing:2px;text-transform:uppercase;
  cursor:pointer;transition:all .15s;
  box-sizing:border-box;
}
.ah-set-changelog-btn:hover {
  background:rgba(200,168,255,.12);
  border-color:rgba(200,168,255,.32);
  color:var(--ah-accent);
}
.ah-set-changelog-version {
  font-size:7.5px;letter-spacing:1px;
  color:rgba(200,168,255,.35);
  background:rgba(200,168,255,.08);
  padding:1px 5px;border-radius:3px;
  border:1px solid rgba(200,168,255,.12);
}
.ah-set-changelog-btn:hover .ah-set-changelog-version {
  color:rgba(200,168,255,.6);
  border-color:rgba(200,168,255,.2);
}

/* Ko-fi card */
#ah-kofi-card {
  position:fixed;z-index:9999998;
  font-family:'Space Mono','Noto Sans JP',monospace;
  box-sizing:border-box;
}
#ah-kofi-inner {
  display:flex;align-items:center;gap:9px;
  padding:9px 12px;
  background:#0c0c0e;
  border:1px solid rgba(255,255,255,.08);
  border-left:3px solid #ff5e5b;
  border-radius:10px;
  box-shadow:0 0 0 1px rgba(255,255,255,.02),0 4px 6px rgba(0,0,0,.4),0 20px 60px rgba(0,0,0,.8),inset 0 1px 0 rgba(255,255,255,.04);
  font-size:10px;color:#9898a8;
}
#ah-kofi-heart { color:#ff5e5b;font-size:12px;line-height:1;animation:ah-kofi-pulse 1.35s ease-in-out infinite; }
#ah-kofi-text { display:flex;flex-direction:column;gap:2px;min-width:0; }
#ah-kofi-msg { color:#9898a8;font-size:9px;line-height:1.3; }
#ah-kofi-inner a { color:#ff5e5b;text-decoration:none;font-weight:700;font-size:10px;line-height:1.2; }
#ah-kofi-inner a:hover { text-decoration:underline; }
#ah-kofi-close {
  background:none;border:none;color:#3a3a48;cursor:pointer;
  font-size:10px;padding:2px 4px;line-height:1;margin-left:auto;
  font-family:'Space Mono',monospace;transition:color .15s;
}
#ah-kofi-close:hover { color:#9898a8; }
@keyframes ah-kofi-pulse {
  0%,100% { transform:scale(1); opacity:.9; }
  50% { transform:scale(1.16); opacity:1; }
}
#ah-kofi-confirm-modal {
  position:fixed;inset:0;z-index:99999999;
  display:flex;align-items:center;justify-content:center;pointer-events:all;
  font-family:'Space Mono','Noto Sans JP',monospace;
}
.ah-kofi-confirm-backdrop { position:absolute;inset:0;background:rgba(0,0,0,.7);backdrop-filter:blur(5px); }
.ah-kofi-confirm-dialog {
  position:relative;z-index:1;
  width:430px;max-width:92vw;
  background:#0c0c0e;
  border:1px solid rgba(255,255,255,.08);
  border-left:3px solid #ff5e5b;
  border-radius:10px;
  box-shadow:0 0 0 1px rgba(255,255,255,.02),0 8px 16px rgba(0,0,0,.5),0 32px 80px rgba(0,0,0,.9),inset 0 1px 0 rgba(255,255,255,.04);
  padding:16px;
  animation:ah-kofi-confirm-in .18s cubic-bezier(.34,1.4,.64,1) both;
}
@keyframes ah-kofi-confirm-in { from{opacity:0;transform:scale(.9) translateY(8px)} to{opacity:1;transform:scale(1) translateY(0)} }
.ah-kofi-confirm-title { display:flex;align-items:center;gap:8px;color:#ffb7b6;font-size:10px;font-weight:700;letter-spacing:1px;text-transform:uppercase;margin-bottom:10px; }
.ah-kofi-confirm-heart { color:#ff5e5b;font-size:12px;line-height:1;animation:ah-kofi-pulse 1.35s ease-in-out infinite; }
.ah-kofi-confirm-body { color:#9898a8;font-size:10px;line-height:1.6;background:#111115;border:1px solid rgba(255,255,255,.06);border-radius:8px;padding:10px 11px;margin-bottom:10px; }
.ah-kofi-confirm-link { display:inline-block;color:#ff5e5b;text-decoration:none;font-weight:700;font-size:10px;margin-bottom:12px; }
.ah-kofi-confirm-link:hover { text-decoration:underline; }
.ah-kofi-confirm-optout { display:flex;align-items:center;gap:7px;color:#9898a8;font-size:9px;line-height:1.2;margin-bottom:11px;user-select:none; }
.ah-kofi-confirm-optout input { accent-color:#ff5e5b;width:12px;height:12px;cursor:pointer; }
.ah-kofi-confirm-actions { display:flex;gap:8px; }
#ah-kofi-keep-btn,#ah-kofi-close-btn {
  flex:1;padding:9px 10px;border-radius:6px;cursor:pointer;
  font-family:'Space Mono','Noto Sans JP',monospace;
  font-size:8.5px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;
  transition:all .15s;
}
#ah-kofi-keep-btn { background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);color:#9898a8; }
#ah-kofi-keep-btn:hover { background:rgba(255,255,255,.08);color:#d0d0e4; }
#ah-kofi-close-btn { background:rgba(255,94,91,.12);border:1px solid rgba(255,94,91,.35);color:#ff8f8d; }
#ah-kofi-close-btn:hover { background:rgba(255,94,91,.2);border-color:rgba(255,94,91,.55); }
`;
    document.head.appendChild(s);
  }

  // ─── Modal CSS ────────────────────────────────────────────────────────────
  function injectModalCSS() {
    if (document.getElementById("ah-lf-css")) return;
    const s = document.createElement("style");
    s.id = "ah-lf-css";
    s.textContent = `
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Noto+Sans+JP:wght@400;700&display=swap');
#ah-lf-modal {
  position:fixed;top:0;left:0;width:100%;height:100%;z-index:9999999;
  display:flex;align-items:center;justify-content:center;
}
.ah-lf-backdrop  { position:absolute;inset:0;background:rgba(0,0,0,.72);backdrop-filter:blur(6px); }
.ah-lf-dialog {
  position:relative;z-index:1;width:480px;max-width:95vw;max-height:90vh;overflow-y:auto;
  background:#0c0c0e;border:1px solid rgba(255,255,255,.08);border-radius:12px;
  box-shadow:0 0 0 1px rgba(255,255,255,.02),0 8px 16px rgba(0,0,0,.5),0 32px 80px rgba(0,0,0,.9);
  font-family:'Space Mono','Noto Sans JP',monospace;color:#c0c0d0;font-size:11px;
}
.ah-lf-dialog::-webkit-scrollbar       { width:2px; }
.ah-lf-dialog::-webkit-scrollbar-thumb { background:rgba(255,255,255,.08); }
.ah-lf-dialog-header {
  display:flex;align-items:center;justify-content:space-between;
  padding:13px 16px;background:#111115;border-bottom:1px solid rgba(255,255,255,.07);
  position:sticky;top:0;z-index:2;
}
.ah-lf-dialog-title { display:flex;align-items:center;gap:7px;font-size:9.5px;font-weight:700;letter-spacing:3.5px;text-transform:uppercase;color:#c8a8ff; }
.ah-lf-close { background:none;border:none;color:rgba(255,255,255,.25);font-size:15px;cursor:pointer;padding:2px 6px;border-radius:4px;transition:color .15s;line-height:1; }
.ah-lf-close:hover { color:rgba(255,255,255,.75); }
.ah-lf-dialog-body { padding:16px 18px; }
.ah-lf-field       { margin-bottom:11px; }
.ah-lf-row-2       { display:flex;gap:12px;margin-bottom:11px; }
.ah-lf-row-2 .ah-lf-field { flex:1;margin-bottom:0; }
.ah-lf-label { display:block;font-size:8px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:rgba(255,255,255,.25);margin-bottom:5px; }
.ah-lf-hint { font-size:7.5px;text-transform:none;letter-spacing:.5px;opacity:.6; }
.ah-lf-dialog-body input,
.ah-lf-dialog-body textarea,
.ah-lf-dialog-body select {
  width:100%;box-sizing:border-box;padding:8px 10px;
  background:#16161b !important;border:1px solid rgba(255,255,255,.07);border-radius:6px;
  color:#d0d0e4 !important;font-family:'Space Mono',monospace;font-size:10.5px;
  outline:none !important;box-shadow:none !important;-webkit-appearance:none;
  transition:border-color .15s;resize:vertical;
  -webkit-box-shadow:0 0 0 1000px #16161b inset !important;
  -webkit-text-fill-color:#d0d0e4 !important;caret-color:#c8a8ff;
}
.ah-lf-dialog-body select { cursor:pointer;resize:none; }
.ah-lf-dialog-body select option { background:#111115;color:#d0d0e4; }
.ah-lf-dialog-body input:focus,
.ah-lf-dialog-body textarea:focus,
.ah-lf-dialog-body select:focus {
  outline:none !important;box-shadow:none !important;
  border-color:rgba(200,168,255,.35);background:#16161b !important;
  -webkit-box-shadow:0 0 0 1000px #16161b inset !important;
}
.ah-lf-dialog-body input:-webkit-autofill,
.ah-lf-dialog-body input:-webkit-autofill:focus {
  -webkit-box-shadow:0 0 0 1000px #16161b inset !important;
  -webkit-text-fill-color:#d0d0e4 !important;
}
.ah-lf-dialog-body input::placeholder,
.ah-lf-dialog-body textarea::placeholder { color:rgba(255,255,255,.12); }
.ah-lf-notice { display:flex;align-items:flex-start;gap:7px;padding:9px 11px;background:rgba(255,255,255,.02);border:1px solid rgba(255,255,255,.05);border-radius:6px;margin:12px 0;font-size:9.5px;color:rgba(255,255,255,.3);line-height:1.5; }
.ah-lf-notice svg { flex-shrink:0;margin-top:1px;color:rgba(255,255,255,.2); }
.ah-lf-notice a { color:#c8a8ff;text-decoration:none; }
.ah-lf-notice a:hover { text-decoration:underline; }
.ah-lf-actions { display:flex;gap:8px; }
#ah-lf-submit { flex:1;padding:10px 16px;background:rgba(200,168,255,.1);border:1px solid rgba(200,168,255,.25);border-radius:6px;color:#c8a8ff;font-family:'Space Mono',monospace;font-size:9px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;cursor:pointer;transition:all .15s; }
#ah-lf-submit:hover:not(:disabled) { background:rgba(200,168,255,.18);border-color:rgba(200,168,255,.5); }
#ah-lf-submit:disabled { opacity:.4;cursor:not-allowed; }
#ah-lf-status { font-size:10px;letter-spacing:.3px; }
#ah-lf-status:not(:empty) { margin-top:10px; }
#ah-lf-status a { text-decoration:none; }
#ah-lf-status a:hover { text-decoration:underline; }
.ah-lf-st-load { color:rgba(255,255,255,.3); }
.ah-lf-st-err  { color:#ff7090; }
.ah-lf-st-ok   { color:#72f0a8; }
.ah-lf-st-ok a { color:#72f0a8; }
`;
    document.head.appendChild(s);
  }

  // ─── Changelog CSS ────────────────────────────────────────────────────────
  function injectChangelogCSS() {
    if (document.getElementById("ah-cl-css")) return;
    const s = document.createElement("style");
    s.id = "ah-cl-css";
    s.textContent = `
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400&family=Noto+Sans+JP:wght@400;700&display=swap');

#ah-changelog-modal {
  position:fixed;inset:0;z-index:99999999;
  display:flex;align-items:center;justify-content:center;pointer-events:all;
  font-family:'Space Mono','Noto Sans JP',monospace;
}
.ah-cl-backdrop {
  position:absolute;inset:0;
  background:rgba(0,0,0,.78);
  backdrop-filter:blur(8px);
  cursor:default;
}
.ah-cl-dialog {
  position:relative;z-index:1;
  width:480px;max-width:94vw;
  max-height:85vh;
  display:flex;flex-direction:column;
  background:#0c0c0e;
  border:1px solid rgba(200,168,255,.14);
  border-radius:12px;
  box-shadow:
    0 0 0 1px rgba(200,168,255,.04),
    0 8px 16px rgba(0,0,0,.5),
    0 32px 80px rgba(0,0,0,.95),
    inset 0 1px 0 rgba(200,168,255,.06);
  animation:ah-cl-in .22s cubic-bezier(.34,1.36,.64,1) both;
  overflow:hidden;
}
@keyframes ah-cl-in {
  from { opacity:0; transform:scale(.9) translateY(12px); }
  to   { opacity:1; transform:scale(1) translateY(0); }
}

/* Header */
.ah-cl-header {
  display:flex;align-items:center;justify-content:space-between;
  padding:14px 18px;
  background:#111115;
  border-bottom:1px solid rgba(200,168,255,.1);
  flex-shrink:0;
}
.ah-cl-header-left {
  display:flex;align-items:center;gap:10px;
}
.ah-cl-logo {
  color:#c8a8ff;opacity:.8;flex-shrink:0;
}
.ah-cl-title-block {
  display:flex;flex-direction:column;gap:2px;
}
.ah-cl-title {
  font-size:10.5px;font-weight:700;letter-spacing:3px;text-transform:uppercase;
  font-style:italic;color:#c8a8ff;
  text-shadow:0 0 18px rgba(200,168,255,.35);
}
.ah-cl-subtitle {
  font-size:8px;letter-spacing:2px;text-transform:uppercase;
  color:rgba(200,168,255,.4);
}
.ah-cl-close {
  background:none;border:none;color:rgba(255,255,255,.2);
  font-size:15px;cursor:pointer;padding:3px 7px;border-radius:5px;
  transition:color .15s,background .15s;line-height:1;
}
.ah-cl-close:hover { color:rgba(255,255,255,.7);background:rgba(255,255,255,.05); }

/* Body */
.ah-cl-body {
  flex:1;overflow-y:auto;
  padding:18px 20px;
  display:flex;flex-direction:column;gap:0;
}
.ah-cl-body::-webkit-scrollbar { width:2px; }
.ah-cl-body::-webkit-scrollbar-thumb { background:rgba(200,168,255,.15);border-radius:2px; }

/* Version block */
.ah-cl-version-block {
  padding:14px 0;
  border-bottom:1px solid rgba(255,255,255,.05);
}
.ah-cl-version-block:last-child { border-bottom:none;padding-bottom:4px; }
.ah-cl-version-block--latest { padding-top:0; }

.ah-cl-version-tag {
  display:flex;align-items:center;gap:8px;
  font-size:9px;font-weight:700;letter-spacing:3px;text-transform:uppercase;
  color:rgba(255,255,255,.2);
  margin-bottom:12px;
}
.ah-cl-version-star {
  color:rgba(200,168,255,.5);
  font-size:11px;
}
.ah-cl-version-block--latest .ah-cl-version-tag {
  color:rgba(200,168,255,.6);
}
.ah-cl-version-block--latest .ah-cl-version-star {
  color:#c8a8ff;
  font-size:13px;
  text-shadow:0 0 10px rgba(200,168,255,.7),0 0 22px rgba(200,168,255,.35);
  animation:ah-cl-star-pulse 2.4s ease-in-out infinite;
}
@keyframes ah-cl-star-pulse {
  0%,100% { text-shadow:0 0 10px rgba(200,168,255,.7),0 0 22px rgba(200,168,255,.35); }
  50%      { text-shadow:0 0 16px rgba(200,168,255,1),0 0 36px rgba(200,168,255,.6); }
}
.ah-cl-version-new {
  font-size:7px;font-weight:700;letter-spacing:2px;
  color:#c8a8ff;
  background:rgba(200,168,255,.12);
  border:1px solid rgba(200,168,255,.25);
  padding:1px 6px;border-radius:3px;
  text-shadow:0 0 8px rgba(200,168,255,.5);
}

/* Section labels */
.ah-cl-section-label {
  display:flex;align-items:center;gap:6px;
  font-size:8px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;
  margin:10px 0 6px;padding:4px 8px;
  border-radius:4px;
}
.ah-cl-section-label--fix {
  color:#60c8ff;
  background:rgba(96,200,255,.06);
  border:1px solid rgba(96,200,255,.15);
}
.ah-cl-section-label--add {
  color:#72f0a8;
  background:rgba(114,240,168,.06);
  border:1px solid rgba(114,240,168,.15);
}
.ah-cl-bullet { font-size:9px;opacity:.7; }

/* List items */
.ah-cl-list {
  list-style:none;margin:0;padding:0;
  display:flex;flex-direction:column;gap:5px;
}
.ah-cl-item {
  display:flex;align-items:flex-start;gap:8px;
  font-size:10px;line-height:1.6;
  padding:7px 10px;border-radius:5px;
  color:rgba(232,232,240,.65);
}
.ah-cl-item--fix {
  background:rgba(96,200,255,.04);
  border:1px solid rgba(96,200,255,.1);
}
.ah-cl-item--add {
  background:rgba(114,240,168,.04);
  border:1px solid rgba(114,240,168,.1);
}
.ah-cl-dot {
  flex-shrink:0;margin-top:3px;font-size:6px;opacity:.5;
}
.ah-cl-item--fix .ah-cl-dot { color:#60c8ff; }
.ah-cl-item--add .ah-cl-dot { color:#72f0a8; }
.ah-cl-section-label--rem { color:#ff6b6b; }
.ah-cl-item--rem {
  background:rgba(255,107,107,.04);
  border:1px solid rgba(255,107,107,.1);
}
.ah-cl-item--rem .ah-cl-dot { color:#ff6b6b; opacity:1; }

/* Info section */
.ah-cl-section-label--info {
  color:#ffd700;
  background:rgba(255,215,0,.06);
  border:1px solid rgba(255,215,0,.15);
}
.ah-cl-item--info {
  background:rgba(255,215,0,.03);
  border:1px solid rgba(255,215,0,.1);
  color:rgba(232,232,240,.65);
}
.ah-cl-item--info .ah-cl-dot { color:#ffd700; opacity:.7; }

/* Tip card */
.ah-cl-tip-card {
  margin:12px 0 4px;
  padding:10px 13px;
  border-radius:5px;
  background:rgba(255,179,71,.04);
  border:1px solid rgba(255,179,71,.12);
  border-left:3px solid rgba(255,179,71,.45);
}
.ah-cl-tip-label {
  font-size:7.5px;font-weight:700;letter-spacing:2px;text-transform:uppercase;
  color:rgba(255,179,71,.65);margin-bottom:6px;
}
.ah-cl-tip-text {
  font-size:9.5px;line-height:1.65;color:rgba(232,232,240,.6);
}

/* Ko-fi banner in changelog */
.ah-cl-kofi-banner {
  display:flex;align-items:center;gap:10px;
  padding:10px 13px;margin-bottom:16px;
  background:#0c0c0e;
  border:1px solid rgba(255,255,255,.08);
  border-left:3px solid #ff5e5b;
  border-radius:8px;
  box-shadow:0 0 0 1px rgba(255,255,255,.02),0 4px 6px rgba(0,0,0,.3),inset 0 1px 0 rgba(255,255,255,.03);
  text-decoration:none;
  transition:background .15s,border-color .15s,box-shadow .15s;
}
.ah-cl-kofi-banner:hover {
  background:#111115;
  border-color:rgba(255,94,91,.35);
  box-shadow:0 0 0 1px rgba(255,94,91,.06),0 4px 12px rgba(0,0,0,.4);
}
.ah-cl-kofi-icon {
  font-size:18px;line-height:1;flex-shrink:0;
  animation:ah-kofi-pulse 1.35s ease-in-out infinite;
}
.ah-cl-kofi-text {
  display:flex;flex-direction:column;gap:2px;min-width:0;flex:1;
}
.ah-cl-kofi-title {
  font-size:10px;font-weight:700;letter-spacing:.5px;
  color:#ffb7b6;
}
.ah-cl-kofi-sub {
  font-size:8.5px;color:#9898a8;letter-spacing:.3px;line-height:1.4;
}
.ah-cl-kofi-arrow {
  font-size:14px;color:#ff5e5b;flex-shrink:0;opacity:.7;
  transition:opacity .15s,transform .15s;
}
.ah-cl-kofi-banner:hover .ah-cl-kofi-arrow {
  opacity:1;transform:translate(2px,-2px);
}

/* Footer */
.ah-cl-footer {
  padding:12px 20px 14px;
  background:#111115;
  border-top:1px solid rgba(200,168,255,.08);
  flex-shrink:0;
}
.ah-cl-ok-btn {
  display:block;width:100%;
  padding:10px 0;
  background:rgba(200,168,255,.1);
  border:1px solid rgba(200,168,255,.25);
  border-radius:7px;
  color:#c8a8ff;
  font-family:'Space Mono','Noto Sans JP',monospace;
  font-size:9px;font-weight:700;letter-spacing:2px;text-transform:uppercase;
  cursor:pointer;
  transition:background .15s,border-color .15s,box-shadow .15s;
  text-shadow:0 0 10px rgba(200,168,255,.3);
}
.ah-cl-ok-btn:hover {
  background:rgba(200,168,255,.18);
  border-color:rgba(200,168,255,.45);
  box-shadow:0 0 0 1px rgba(200,168,255,.08),0 4px 16px rgba(200,168,255,.1);
}
`;
    document.head.appendChild(s);
  }

  // ─── Warn popup CSS ───────────────────────────────────────────────────────
  function injectWarnCSS() {
    if (document.getElementById("ah-warn-css")) return;
    const s = document.createElement("style");
    s.id = "ah-warn-css";
    s.textContent = `
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
#ah-warn-modal {
  position:fixed;inset:0;z-index:99999999;
  display:flex;align-items:center;justify-content:center;pointer-events:all;
}
.ah-warn-backdrop { position:absolute;inset:0;background:rgba(0,0,0,.65);backdrop-filter:blur(3px);cursor:default; }
.ah-warn-dialog {
  position:relative;z-index:1;
  width:fit-content;
  min-width:280px;max-width:min(440px,92vw);
  background:#0f0a0a;border:1px solid rgba(255,102,128,.25);border-radius:10px;
  box-shadow:0 0 0 1px rgba(255,102,128,.08),0 8px 24px rgba(0,0,0,.6),0 24px 64px rgba(0,0,0,.9),inset 0 1px 0 rgba(255,102,128,.08);
  font-family:'Space Mono',monospace;padding:22px 22px 18px;
  animation:ah-warn-in .18s cubic-bezier(.34,1.4,.64,1) both;
}
@keyframes ah-warn-in { from{opacity:0;transform:scale(.88) translateY(8px)} to{opacity:1;transform:scale(1) translateY(0)} }
.ah-warn-icon-row { display:flex;align-items:center;gap:10px;margin-bottom:14px; }
.ah-warn-title { font-size:11px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;color:#ff8898; }
.ah-warn-body { display:flex;flex-direction:column;gap:9px;margin-bottom:18px; }
.ah-warn-item { font-size:10.5px;color:rgba(255,255,255,.55);line-height:1.6;padding:9px 11px;background:rgba(255,102,128,.05);border:1px solid rgba(255,102,128,.12);border-radius:6px; }
.ah-warn-item strong { color:#ff8898;font-weight:700; }
.ah-warn-item code { font-family:'Space Mono',monospace;font-size:9.5px;color:#c8a8ff;background:rgba(200,168,255,.12);padding:1px 5px;border-radius:3px; }
.ah-warn-btn { display:block;width:100%;padding:10px 0;background:rgba(255,102,128,.1);border:1px solid rgba(255,102,128,.3);border-radius:6px;color:#ff8898;font-family:'Space Mono',monospace;font-size:9px;font-weight:700;letter-spacing:2px;text-transform:uppercase;cursor:pointer;transition:background .15s,border-color .15s; }
.ah-warn-btn:hover { background:rgba(255,102,128,.18);border-color:rgba(255,102,128,.55); }
`;
    document.head.appendChild(s);
  }

  // ─── Import popup CSS ─────────────────────────────────────────────────────
  function injectImportCSS() {
    if (document.getElementById("ah-import-css")) return;
    const s = document.createElement("style");
    s.id = "ah-import-css";
    s.textContent = `
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
#ah-import-modal {
  position:fixed;inset:0;z-index:99999999;
  display:flex;align-items:center;justify-content:center;pointer-events:all;
}
.ah-import-backdrop { position:absolute;inset:0;background:rgba(0,0,0,.72);backdrop-filter:blur(6px);cursor:default; }
.ah-import-dialog {
  position:relative;z-index:1;width:360px;max-width:92vw;
  background:#0c0c0e;border:1px solid rgba(255,255,255,.08);border-radius:12px;
  box-shadow:0 0 0 1px rgba(255,255,255,.02),0 8px 16px rgba(0,0,0,.5),0 32px 80px rgba(0,0,0,.9);
  font-family:'Space Mono',monospace;color:#c0c0d0;font-size:11px;
  animation:ah-import-in .18s cubic-bezier(.34,1.4,.64,1) both;
}
@keyframes ah-import-in { from{opacity:0;transform:scale(.9) translateY(8px)} to{opacity:1;transform:scale(1) translateY(0)} }
.ah-import-header { display:flex;align-items:center;justify-content:space-between;padding:13px 16px;background:#111115;border-bottom:1px solid rgba(255,255,255,.07);border-radius:12px 12px 0 0; }
.ah-import-title { display:flex;align-items:center;gap:7px;font-size:9.5px;font-weight:700;letter-spacing:3.5px;text-transform:uppercase;color:#c8a8ff; }
.ah-import-close { background:none;border:none;color:rgba(255,255,255,.25);font-size:15px;cursor:pointer;padding:2px 6px;border-radius:4px;transition:color .15s;line-height:1; }
.ah-import-close:hover { color:rgba(255,255,255,.75); }
.ah-import-body { padding:18px; }
.ah-import-drop { display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:32px 20px;border:1.5px dashed rgba(255,255,255,.1);border-radius:8px;background:rgba(255,255,255,.02);cursor:pointer;transition:border-color .15s,background .15s;color:rgba(255,255,255,.25); }
.ah-import-drop:hover,.ah-import-drop--over { border-color:rgba(200,168,255,.4);background:rgba(200,168,255,.04);color:rgba(200,168,255,.6); }
.ah-import-drop svg { opacity:.5;transition:opacity .15s; }
.ah-import-drop:hover svg,.ah-import-drop--over svg { opacity:1; }
.ah-import-drop-label { font-size:10.5px;font-weight:700;letter-spacing:1px; }
.ah-import-drop-sub   { font-size:8.5px;letter-spacing:.5px;opacity:.5; }
.ah-import-st { display:block;margin-top:12px;font-size:9.5px;text-align:center;letter-spacing:.5px;min-height:16px; }
.ah-import-st--err { color:#ff7090; }
.ah-import-st--ok  { color:#72f0a8; }
`;
    document.head.appendChild(s);
  }

  // ─── Bump Topic popup CSS ─────────────────────────────────────────────────
  function injectBumpTopicCSS() {
    if (document.getElementById("ah-bt-css")) return;
    const s = document.createElement("style");
    s.id = "ah-bt-css";
    s.textContent = `
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
#ah-bumptopic-modal {
  position:fixed;inset:0;z-index:99999999;
  display:flex;align-items:center;justify-content:center;pointer-events:all;
  font-family:'Space Mono','Noto Sans JP',monospace;
}
.ah-bt-backdrop {
  position:absolute;inset:0;background:rgba(0,0,0,.72);backdrop-filter:blur(7px);cursor:default;
}
.ah-bt-dialog {
  position:relative;z-index:1;
  width:420px;max-width:92vw;
  background:#0c0c0e;
  border:1px solid rgba(200,168,255,.18);
  border-radius:12px;
  box-shadow:0 0 0 1px rgba(200,168,255,.05),0 8px 20px rgba(0,0,0,.5),0 32px 80px rgba(0,0,0,.95),inset 0 1px 0 rgba(200,168,255,.07);
  padding:20px 22px 18px;
  animation:ah-bt-in .2s cubic-bezier(.34,1.36,.64,1) both;
}
@keyframes ah-bt-in { from{opacity:0;transform:scale(.9) translateY(10px)} to{opacity:1;transform:scale(1) translateY(0)} }
.ah-bt-icon-row {
  display:flex;align-items:center;gap:9px;margin-bottom:12px;
}
.ah-bt-title {
  font-size:10.5px;font-weight:700;letter-spacing:2px;text-transform:uppercase;
  color:#c8a8ff;text-shadow:0 0 14px rgba(200,168,255,.35);
}
.ah-bt-body {
  font-size:10.5px;color:rgba(232,232,240,.85);line-height:1.65;
  margin-bottom:10px;
}
.ah-bt-sub {
  font-size:9px;color:rgba(200,168,255,.4);line-height:1.6;
  padding:8px 10px;background:rgba(200,168,255,.04);
  border:1px solid rgba(200,168,255,.1);border-radius:6px;
  margin-bottom:16px;
}
.ah-bt-actions { display:flex;gap:8px; }
.ah-bt-no {
  flex:1;padding:9px 0;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);
  border-radius:6px;color:rgba(255,255,255,.35);
  font-family:'Space Mono',monospace;font-size:8.5px;font-weight:700;
  letter-spacing:1.5px;text-transform:uppercase;cursor:pointer;transition:all .15s;
}
.ah-bt-no:hover:not(:disabled) { background:rgba(255,255,255,.08);color:rgba(255,255,255,.65); }
.ah-bt-no:disabled { opacity:.4;cursor:default; }
.ah-bt-yes {
  flex:1;padding:9px 0;background:rgba(200,168,255,.12);border:1px solid rgba(200,168,255,.3);
  border-radius:6px;color:#c8a8ff;
  font-family:'Space Mono',monospace;font-size:8.5px;font-weight:700;
  letter-spacing:1.5px;text-transform:uppercase;cursor:pointer;transition:all .15s;
}
.ah-bt-yes:hover:not(:disabled) { background:rgba(200,168,255,.22);border-color:rgba(200,168,255,.55); }
.ah-bt-yes:disabled { opacity:.6;cursor:default; }
.ah-bt-status { min-height:16px;margin-top:10px;font-size:9.5px;text-align:center;letter-spacing:.3px; }
.ah-bt-status--ok  { color:#72f0a8; }
.ah-bt-status--err { color:#ff6680; }
`;
    document.head.appendChild(s);
  }

  // ─── Ko-fi random popup CSS ───────────────────────────────────────────────
  function injectKofiRandomCSS() {
    if (document.getElementById("ah-kr-css")) return;
    const s = document.createElement("style");
    s.id = "ah-kr-css";
    s.textContent = `
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
#ah-kofi-random-modal {
  position:fixed;inset:0;z-index:99999999;
  display:flex;align-items:center;justify-content:center;pointer-events:all;
  font-family:'Space Mono','Noto Sans JP',monospace;
}
.ah-kr-backdrop {
  position:absolute;inset:0;background:rgba(0,0,0,.72);backdrop-filter:blur(7px);cursor:default;
}
.ah-kr-dialog {
  position:relative;z-index:1;
  width:420px;max-width:92vw;
  background:#0c0c0e;
  border:1px solid rgba(255,255,255,.08);
  border-left:3px solid #ff5e5b;
  border-radius:12px;
  box-shadow:0 0 0 1px rgba(255,255,255,.02),0 8px 20px rgba(0,0,0,.5),0 32px 80px rgba(0,0,0,.95),inset 0 1px 0 rgba(255,255,255,.04);
  padding:18px 20px 16px;
  animation:ah-kr-in .2s cubic-bezier(.34,1.36,.64,1) both;
}
@keyframes ah-kr-in { from{opacity:0;transform:scale(.9) translateY(10px)} to{opacity:1;transform:scale(1) translateY(0)} }
.ah-kr-header {
  display:flex;align-items:center;gap:8px;margin-bottom:12px;
}
.ah-kr-heart {
  color:#ff5e5b;font-size:14px;line-height:1;
  animation:ah-kofi-pulse 1.35s ease-in-out infinite;
  flex-shrink:0;
}
.ah-kr-title {
  flex:1;font-size:10px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;
  color:#ffb7b6;
}
.ah-kr-x {
  background:none;border:none;color:rgba(255,255,255,.2);font-size:14px;
  cursor:pointer;padding:2px 5px;border-radius:4px;line-height:1;
  transition:color .15s;font-family:'Space Mono',monospace;
}
.ah-kr-x:hover { color:rgba(255,255,255,.7); }
.ah-kr-body {
  font-size:10px;color:#9898a8;line-height:1.65;
  background:#111115;border:1px solid rgba(255,255,255,.06);
  border-radius:8px;padding:10px 12px;margin-bottom:14px;
}
.ah-kr-actions { display:flex;gap:8px;align-items:center; }
.ah-kr-dismiss {
  padding:9px 14px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);
  border-radius:6px;color:rgba(255,255,255,.35);
  font-family:'Space Mono',monospace;font-size:8.5px;font-weight:700;
  letter-spacing:1.5px;text-transform:uppercase;cursor:pointer;transition:all .15s;white-space:nowrap;
}
.ah-kr-dismiss:hover { background:rgba(255,255,255,.08);color:rgba(255,255,255,.6); }
.ah-kr-kofi-btn {
  flex:1;display:flex;align-items:center;justify-content:center;
  padding:10px 12px;
  background:rgba(255,94,91,.12);border:1px solid rgba(255,94,91,.35);
  border-radius:6px;color:#ff8f8d;
  font-family:'Space Mono',monospace;font-size:9px;font-weight:700;
  letter-spacing:1.5px;text-transform:uppercase;
  text-decoration:none;cursor:pointer;transition:all .15s;
}
.ah-kr-kofi-btn:hover { background:rgba(255,94,91,.22);border-color:rgba(255,94,91,.6);color:#ffb7b6; }
`;
    document.head.appendChild(s);
  }

  // ─── Confirm popup CSS ────────────────────────────────────────────────────
  function injectConfirmCSS() {
    if (document.getElementById("ah-confirm-css")) return;
    const s = document.createElement("style");
    s.id = "ah-confirm-css";
    s.textContent = `
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
#ah-confirm-modal {
  position:fixed;inset:0;z-index:99999999;
  display:flex;align-items:center;justify-content:center;pointer-events:all;
}
.ah-confirm-backdrop { position:absolute;inset:0;background:rgba(0,0,0,.65);backdrop-filter:blur(3px);cursor:default; }
.ah-confirm-dialog {
  position:relative;z-index:1;
  min-width:280px;max-width:min(400px,92vw);
  background:#0f0a0a;border:1px solid rgba(255,102,128,.25);border-radius:10px;
  box-shadow:0 0 0 1px rgba(255,102,128,.08),0 8px 24px rgba(0,0,0,.6),0 24px 64px rgba(0,0,0,.9);
  font-family:'Space Mono',monospace;padding:22px 22px 18px;
  animation:ah-confirm-in .18s cubic-bezier(.34,1.4,.64,1) both;
}
@keyframes ah-confirm-in { from{opacity:0;transform:scale(.88) translateY(8px)} to{opacity:1;transform:scale(1) translateY(0)} }
.ah-confirm-icon-row { display:flex;align-items:center;gap:10px;margin-bottom:14px; }
.ah-confirm-title { font-size:11px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;color:#ff8898; }
.ah-confirm-body { font-size:10.5px;color:rgba(255,255,255,.5);line-height:1.6;padding:10px 12px;background:rgba(255,102,128,.04);border:1px solid rgba(255,102,128,.1);border-radius:6px;margin-bottom:18px; }
.ah-confirm-actions { display:flex;gap:8px; }
.ah-confirm-cancel { flex:1;padding:9px 0;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);border-radius:6px;color:rgba(255,255,255,.4);font-family:'Space Mono',monospace;font-size:9px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;cursor:pointer;transition:all .15s; }
.ah-confirm-cancel:hover { background:rgba(255,255,255,.08);color:rgba(255,255,255,.7); }
.ah-confirm-proceed { flex:1;padding:9px 0;background:rgba(255,102,128,.12);border:1px solid rgba(255,102,128,.3);border-radius:6px;color:#ff8898;font-family:'Space Mono',monospace;font-size:9px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;cursor:pointer;transition:all .15s; }
.ah-confirm-proceed:hover { background:rgba(255,102,128,.22);border-color:rgba(255,102,128,.6); }
`;
    document.head.appendChild(s);
  }

  // ─── Boot ─────────────────────────────────────────────────────────────────
  function boot() {
    const adapter = getAdapter();
    if (!adapter) return;
    if (!adapter.isItemPage()) return;
    if (!isSiteEnabled()) return;
    injectUI();
    const isNewVersion = GM_getValue("ah-changelog-seen", "") !== CURRENT_VERSION;
    maybeShowChangelog();
    if (!isNewVersion) maybeShowKofiRandomPopup();
  }

  setTimeout(boot, 1200);

  let _lastHref = location.href;
  const _observer = new MutationObserver(() => {
    if (location.href !== _lastHref) {
      _lastHref = location.href;
      document.getElementById("ah-panel")?.remove();
      document.getElementById("ah-kofi-card")?.remove();
      _recheckFn = null;
      setTimeout(boot, 1500);
    }
  });
  _observer.observe(document.body, { childList: true, subtree: true });

})();