function findNodeForAddress(address) {
  if (!address) return null;
  // Look for links first (most reliable)
  const links = document.querySelectorAll('a');
  for (const link of links) {
    if (link.href.includes(address)) return link;
  }
  // Then text nodes
  const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
  let node;
  while ((node = walker.nextNode())) {
    if (node.nodeValue.includes(address)) return node.parentElement;
  }
  return null;
}

function getPadreMint() {
  const skipSelector = '[class*="sidebar"], [id*="sidebar"], [class*="recent"], [class*="trending"]';
  const url = window.location.href;
  const match = url.match(/(?:\/solana\/|\/lp\/|\/trade\/solana\/|\/terminal\?.*address=)([1-9A-HJ-NP-Za-km-z]{32,44})/);
  if (match) {
    const address = match[1].trim();
    return { address, node: findNodeForAddress(address) };
  }

  const isElementVisible = (el) => {
    if (!el || !(el instanceof Element)) return false;
    if (el.closest('[aria-hidden="true"], [hidden], [style*="display: none"]')) return false;
    const style = window.getComputedStyle(el);
    if (!style || style.display === 'none' || style.visibility === 'hidden') return false;
    const rect = el.getBoundingClientRect();
    return rect.width > 0 && rect.height > 0;
  };
  const isValidScope = (el) => !!(el && !el.closest(skipSelector) && isElementVisible(el));
  const scoreNode = (el) => {
    if (!el || !(el instanceof Element)) return 0;
    const rect = el.getBoundingClientRect();
    let score = 0;
    if (rect.width > 0 && rect.height > 0) score += 10;
    if (rect.left >= (window.innerWidth * 0.50)) score += 22; // Padre active token panel is usually on the right.
    if (rect.left >= (window.innerWidth * 0.35)) score += 8;
    if (rect.top >= 40 && rect.top <= (window.innerHeight - 30)) score += 6;
    if (rect.width <= 720) score += 3;
    return score;
  };
  const candidates = new Map();
  const pushCandidate = (address, node, source, bonus = 0) => {
    const a = String(address || '').trim();
    if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(a)) return;
    const n = node || findNodeForAddress(a) || null;
    if (n && !isValidScope(n)) return;
    const score = (n ? scoreNode(n) : 0) + (Number(bonus) || 0);
    const existing = candidates.get(a);
    if (!existing || score > existing.score) {
      candidates.set(a, { address: a, node: n, score, source });
    }
  };

  // Strategy A: direct "CA: <address>" from visible text nodes.
  const inlineCA = /\bCA\s*:?\s*([1-9A-HJ-NP-Za-km-z]{32,44})\b/i;
  const inlineWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
  let inlineNode = null;
  while ((inlineNode = inlineWalker.nextNode())) {
    const raw = String(inlineNode.nodeValue || '').trim();
    if (!raw || !/CA\s*:/i.test(raw)) continue;
    const parent = inlineNode.parentElement;
    if (!isValidScope(parent)) continue;
    const directMatch = raw.match(inlineCA);
    if (directMatch?.[1]) {
      pushCandidate(directMatch[1].trim(), parent, 'inline-ca-direct', 10);
    }
    let scope = parent;
    for (let i = 0; i < 6 && scope; i++) {
      if (!isValidScope(scope)) {
        scope = scope.parentElement;
        continue;
      }
      const scopeText = String(scope.innerText || scope.textContent || '');
      const scopedMatch = scopeText.match(inlineCA);
      if (scopedMatch?.[1]) {
        pushCandidate(scopedMatch[1].trim(), scope, 'inline-ca-scoped', 7);
      }
      scope = scope.parentElement;
    }
  }

  // Strategy B: "CA:" label and nearest ancestor block with a mint-like address.
  const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
  let node;
  while ((node = walker.nextNode())) {
    const text = String(node.nodeValue || '').trim();
    if (text !== 'CA:' && text !== 'CA') continue;
    let container = node.parentElement;
    if (!isValidScope(container)) continue;
    for (let i = 0; i < 6 && container; i++) {
      if (!isValidScope(container)) {
        container = container.parentElement;
        continue;
      }
      const blockText = String(container.innerText || container.textContent || '');
      const labelledCA = blockText.match(/\bCA\s*:?\s*([1-9A-HJ-NP-Za-km-z]{32,44})\b/i);
      if (labelledCA?.[1]) {
        pushCandidate(labelledCA[1].trim(), container, 'ca-label-nearby-labelled', 14);
      } else {
        const labelledDA = blockText.match(/\b(?:DA|DEV)\s*:?\s*([1-9A-HJ-NP-Za-km-z]{32,44})\b/i);
        const daAddr = String(labelledDA?.[1] || '').trim();
        const html = String(container.innerHTML || '');
        const matches = html.match(/([1-9A-HJ-NP-Za-km-z]{32,44})/g) || [];
        matches.forEach((addr) => {
          const normalized = String(addr || '').trim();
          const bonus = (daAddr && normalized === daAddr) ? -8 : 5;
          pushCandidate(normalized, container, 'ca-label-nearby', bonus);
        });
      }
      container = container.parentElement;
    }
  }

  // Strategy C: explorer links fallback.
  const links = document.querySelectorAll('a');
  for (const link of links) {
    const href = link.href || '';
    const m = href.match(/(?:solscan\.io|solana\.fm|explorer\.solana\.com)\/token\/([1-9A-HJ-NP-Za-km-z]{32,44})/);
    if (!m?.[1]) continue;
    if (location.href.includes('padre.gg') && link.closest(skipSelector)) continue;
    if (!isElementVisible(link)) continue;
    pushCandidate(m[1], link, 'explorer-link', 1);
  }

  // Strategy D: copy/address attributes often contain full CA even when visible text is truncated.
  const attrSelectors = [
    '[data-clipboard-text]',
    '[data-copy]',
    '[data-address]',
    '[data-addr]',
    '[data-mint]',
    '[data-token]',
    '[title]',
    '[aria-label]'
  ];
  const addressLike = /([1-9A-HJ-NP-Za-km-z]{32,44})/g;
  const elements = document.querySelectorAll(attrSelectors.join(','));
  for (const el of elements) {
    if (!isElementVisible(el)) continue;
    const rect = el.getBoundingClientRect();
    // Padre active token panel is usually on the right side.
    if (rect.left < (window.innerWidth * 0.40)) continue;
    const attrsToCheck = [
      el.getAttribute('data-clipboard-text'),
      el.getAttribute('data-copy'),
      el.getAttribute('data-address'),
      el.getAttribute('data-addr'),
      el.getAttribute('data-mint'),
      el.getAttribute('data-token'),
      el.getAttribute('title'),
      el.getAttribute('aria-label'),
      el.getAttribute('href')
    ];
    const localText = String((el.textContent || '') + ' ' + (el.closest('div,section,article')?.textContent || '')).toUpperCase();
    const hasCALabelNear = /\bCA\b/.test(localText);
    const hasDALabelNear = /\bDA\b|\bDEV\b/.test(localText);
    for (const raw of attrsToCheck) {
      if (!raw) continue;
      const str = String(raw);
      const matches = str.match(addressLike) || [];
      for (const addr of matches) {
        const bonus = (hasCALabelNear ? 18 : 6) + (hasDALabelNear ? -5 : 0);
        pushCandidate(addr, el, 'attr-address', bonus);
      }
    }
  }

  if (candidates.size > 0) {
    const best = Array.from(candidates.values()).sort((a, b) => b.score - a.score)[0];
    return { address: best.address, node: best.node || findNodeForAddress(best.address) };
  }
  return null;
}

function getPadreDA(caNode) {
  const currentMint = String(getMintFromURL()?.address || '').trim().toLowerCase();
  const isValidAddr = (v) => /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(String(v || '').trim());
  const isRejectedAddr = (v) => {
    const addr = String(v || '').trim().toLowerCase();
    if (!isValidAddr(addr)) return true;
    if (currentMint && addr === currentMint) return true;
    return false;
  };
  const isElementVisible = (el) => {
    if (!el || !(el instanceof Element)) return false;
    if (el.closest('[aria-hidden="true"], [hidden], [style*="display: none"]')) return false;
    const style = window.getComputedStyle(el);
    if (!style || style.display === 'none' || style.visibility === 'hidden') return false;
    const rect = el.getBoundingClientRect();
    return rect.width > 0 && rect.height > 0;
  };
  const extractDaFromText = (raw) => {
    const text = String(raw || '')
      .replace(/[\u00A0\u2007\u202F]/g, ' ')
      .replace(/[\u200B-\u200D\uFEFF]/g, '')
      .replace(/\s+/g, ' ');
    if (!text) return null;
    const m1 = text.match(/\b(?:DA|DEPLOYER)\s*(?:\:|：)?\s*([1-9A-HJ-NP-Za-km-z]{32,44})\b/i);
    if (m1?.[1] && !isRejectedAddr(m1[1])) return m1[1];
    const m2 = text.match(/\b(?:DA|DEPLOYER)\s+([1-9A-HJ-NP-Za-km-z]{32,44})\b/i);
    if (m2?.[1] && !isRejectedAddr(m2[1])) return m2[1];
    return null;
  };
  const extractAddressFromAnyText = (raw) => {
    if (!raw) return null;
    const m = String(raw).match(/([1-9A-HJ-NP-Za-km-z]{32,44})/);
    if (!m?.[1]) return null;
    return isRejectedAddr(m[1]) ? null : m[1];
  };
  const extractAddressFromAttrs = (el, { allowGeneric = true } = {}) => {
    if (!el || !(el instanceof Element)) return null;
    const values = [
      el.getAttribute('data-clipboard-text'),
      el.getAttribute('data-copy'),
      el.getAttribute('data-address'),
      el.getAttribute('data-addr'),
      el.getAttribute('data-wallet'),
      el.getAttribute('data-owner'),
      el.getAttribute('title'),
      el.getAttribute('aria-label'),
      el.getAttribute('href'),
      el.textContent
    ];
    for (const v of values) {
      const addr = extractDaFromText(v);
      if (addr) return addr;
    }
    if (!allowGeneric) return null;
    for (const v of values) {
      const addr = extractAddressFromAnyText(v);
      if (addr) return addr;
    }
    return null;
  };
  const scoreNodeForDA = (el) => {
    if (!el || !(el instanceof Element)) return 0;
    const rect = el.getBoundingClientRect();
    let score = 0;
    if (rect.left >= (window.innerWidth * 0.55)) score += 18;
    if (rect.left >= (window.innerWidth * 0.72)) score += 10;
    if (rect.width > 0 && rect.height > 0) score += 4;
    const txt = String(el.innerText || el.textContent || '');
    if (/\bToken Data\s*&\s*Security\b/i.test(txt)) score += 20;
    if (/\bDA\s*:?\b/i.test(txt)) score += 10;
    return score;
  };
  const candidates = [];
  const addCandidate = (addr, node, bonus = 0) => {
    if (!addr || isRejectedAddr(addr)) return;
    const score = scoreNodeForDA(node) + (Number(bonus) || 0);
    candidates.push({ addr, score });
  };
  const pickBestCandidate = () => {
    if (!candidates.length) return null;
    candidates.sort((a, b) => b.score - a.score);
    return candidates[0]?.addr || null;
  };
  const findAddressInScope = (startNode) => {
    let scope = startNode;
    for (let i = 0; i < 6 && scope; i++) {
      if (!isElementVisible(scope)) {
        scope = scope.parentElement;
        continue;
      }
      const scopeText = String(scope.innerText || scope.textContent || '');
      const byLabel = extractDaFromText(scopeText);
      if (byLabel) addCandidate(byLabel, scope, 30);
      if (/\b(?:DA|DEPLOYER)\b/i.test(scopeText)) {
        const direct = extractAddressFromAttrs(scope, { allowGeneric: false });
        if (direct) addCandidate(direct, scope, 24);
        const attrNodes = scope.querySelectorAll('a, button, span, div, [data-clipboard-text], [data-copy], [data-address], [data-addr], [data-wallet], [data-owner], [title], [aria-label]');
        for (const node of attrNodes) {
          const strictAddr = extractAddressFromAttrs(node, { allowGeneric: false });
          if (strictAddr) addCandidate(strictAddr, node, 18);
        }
        // Only allow generic extraction when this scope does not contain both CA and DA labels.
        const hasCA = /\bCA\s*:?\b/i.test(scopeText);
        if (!hasCA) {
          for (const node of attrNodes) {
            const addr = extractAddressFromAttrs(node, { allowGeneric: true });
            if (addr) addCandidate(addr, node, 4);
          }
        }
      }
      scope = scope.parentElement;
    }
    return pickBestCandidate();
  };

  // 0. High-priority: explicit DA labels on right-side security panel
  const labelWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
  let lblNode;
  while ((lblNode = labelWalker.nextNode())) {
    const label = String(lblNode.nodeValue || '').trim().toUpperCase();
    if (!(label === 'DA:' || label === 'DA' || label === 'DEPLOYER:' || label === 'DEPLOYER')) continue;
    const parent = lblNode.parentElement;
    if (!parent || !isElementVisible(parent)) continue;
    const rect = parent.getBoundingClientRect();
    if (rect.left < (window.innerWidth * 0.45)) continue;
    const byScope = findAddressInScope(parent);
    if (byScope) addCandidate(byScope, parent, 36);
  }
  const earlyBest = pickBestCandidate();
  if (earlyBest) return earlyBest;

  // 1. Try to find DA near the CA node
  if (caNode) {
    let container = caNode;
    for (let i = 0; i < 10 && container; i++) {
      const quickMatch = findAddressInScope(container);
      if (quickMatch) addCandidate(quickMatch, container, 16);
      const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null, false);
      let tNode;
      while ((tNode = walker.nextNode())) {
        const text = String(tNode.nodeValue || '').trim();
        if (text === 'DA:' || text === 'DA' || text.toUpperCase() === 'DEPLOYER:' || text.toUpperCase() === 'DEPLOYER') {
          const byScope = findAddressInScope(tNode.parentElement);
          if (byScope) addCandidate(byScope, tNode.parentElement, 24);
        }
      }
      container = container.parentElement;
    }
  }

  // 2. Global fallback around DA labels
  const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
  let node;
  while ((node = walker.nextNode())) {
    const text = String(node.nodeValue || '').trim();
    if (text === 'DA:' || text === 'DA' || text.toUpperCase() === 'DEPLOYER:' || text.toUpperCase() === 'DEPLOYER') {
      const container = node.parentElement;
      const invalid = container?.closest ? container.closest('[class*="sidebar"], [id*="sidebar"], [class*="recent"], [class*="trending"]') : null;
      if (invalid) continue;
      const byScope = findAddressInScope(container);
      if (byScope) addCandidate(byScope, container, 12);
    }
  }

  // 3. Attribute fallback for truncated rows like "DA: 4abc...X9zQ"
  const attrNodes = document.querySelectorAll('[data-clipboard-text], [data-copy], [data-address], [data-addr], [data-wallet], [data-owner], [title], [aria-label], a[href*="/account/"], a[href*="/address/"]');
  for (const nodeEl of attrNodes) {
    if (!nodeEl || !(nodeEl instanceof Element)) continue;
    const scope = nodeEl.closest('div,section,article,li,tr') || nodeEl;
    if (!isElementVisible(scope)) continue;
    const scopeText = String(scope?.innerText || scope?.textContent || '');
    if (!/\b(?:DA|DEPLOYER)\b/i.test(scopeText)) continue;
    const strict = extractAddressFromAttrs(nodeEl, { allowGeneric: false });
    if (strict) addCandidate(strict, nodeEl, 8);
    const hasCA = /\bCA\s*:?\b/i.test(scopeText);
    if (!hasCA) {
      const addr = extractAddressFromAttrs(nodeEl, { allowGeneric: true });
      if (addr) addCandidate(addr, nodeEl, 2);
    }
  }
  return pickBestCandidate();
}

function getPadreDevStats(targetDeployerAddress = null) {
  const METHOD_CONFIDENCE = {
    'TABLE': 100,
    'CELL-DATAFIELD': 95,
    'CELL-HEADERMAP': 90,
    'BADGE-DATAFIELD': 88,
    'BADGE-HEADERMAP': 84,
    'ROW-STRICT-DA': 82,
    'ROW-STRICT': 76,
    'DEV-LINE': 45,
    'DEV-BLOCK': 40,
    'DEV-BLOCK-LOOSE': 28,
    'DEV-SEGMENT': 24
  };

  const parseVal = (txt) => {
    return parsePadreMoneyValue(txt);
  };

  const buildResult = (bought, sold, method) => {
    // A "Dump" must have significant sold amount (> $20)
    // and must be practically all of the buy (> 90%) or more than the buy
    const hasSold = sold > 15;
    const isDump = hasSold && ((sold >= (bought * 0.90)) || (sold > bought));
    const confidence = Number(METHOD_CONFIDENCE[method]) || 10;

    // Safety check: if sold is effectively zero or dust, it's NEVER a dump
    if (sold < 5) return { bought, sold, hasSold: false, isDump: false, method, confidence };

    console.log(`PRC [${method}]: DEV stats -> Bought: $${bought.toFixed(2)}, Sold: $${sold.toFixed(2)}, hasSold=${hasSold}, isDump=${isDump}`);
    return { bought, sold, hasSold, isDump, method, confidence };
  };

  const extractDollarValues = (txt) => {
    if (!txt) return [];
    const vals = [];
    // Only capture values that are NOT prefixed by + or - (which indicates P/L)
    // Also avoid values preceded by "1)" which is a rank/item index in some views.
    const re = /(?<![+-])(?:\$\s*)?([\d,.]+)\s*([MKk]?)(?!\s*MC)/g;
    let m;
    while ((m = re.exec(txt)) !== null) {
      if (m[0].includes('+') || m[0].includes('-')) continue; // Redundant safety
      let val = parseFloat(String(m[1] || '').replace(/,/g, ''));
      if (!Number.isFinite(val)) continue;
      const suf = String(m[2] || '').toUpperCase();
      if (suf === 'K') val *= 1000;
      if (suf === 'M') val *= 1000000;
      vals.push(val);
    }
    return vals;
  };
  const extractMoneyValuesLoose = (txt) => {
    if (!txt) return [];
    const vals = [];
    const re = /\$?\s*([\d,.]+)\s*([MKk]?)/g;
    let m;
    while ((m = re.exec(txt)) !== null) {
      const raw = String(m[1] || '').replace(/,/g, '');
      if (!raw) continue;
      let val = parseFloat(raw);
      if (!Number.isFinite(val)) continue;
      const suf = String(m[2] || '').toUpperCase();
      if (suf === 'K') val *= 1000;
      if (suf === 'M') val *= 1000000;
      vals.push(val);
    }
    return vals;
  };

  const parseAmountToken = (raw) => {
    if (!raw) return 0;
    const m = String(raw).trim().match(/([\d,.]+)\s*([MKk]?)/);
    if (!m) return 0;
    let val = parseFloat(String(m[1] || '').replace(/,/g, ''));
    if (!Number.isFinite(val)) return 0;
    const suf = String(m[2] || '').toUpperCase();
    if (suf === 'K') val *= 1000;
    if (suf === 'M') val *= 1000000;
    return val;
  };

  const parseLabeledAmount = (txt, label) => {
    if (!txt || !label) return null;
    const src = String(txt);
    // First, explicit numeric value after label (supports "$78.2", "2.3K", "0", etc.)
    const amountMatch = src.match(new RegExp(`\\b${label}\\b[^\\d$-]{0,18}\\$?\\s*([\\d,.]+\\s*[MKk]?)`, 'i'));
    if (amountMatch && amountMatch[1]) {
      const value = parseAmountToken(amountMatch[1]);
      if (Number.isFinite(value)) return value;
    }
    // If label exists but amount is omitted/zero-ish in UI text, treat as 0 instead of guessing.
    const zeroishMatch = src.match(new RegExp(`\\b${label}\\b[^\\d]{0,18}0(?:[.,]0+)?\\b`, 'i'));
    if (zeroishMatch) return 0;
    if (new RegExp(`\\b${label}\\b`, 'i').test(src)) return 0;
    return null;
  };

  const parseDevFromTextBlock = (txt, method) => {
    if (!txt) return null;
    // Strict parsing by labels to avoid mixing Bought/Sold with Avg MC values.
    const boughtMatch = txt.match(/bought[^$\d]*\$?\s*([\d,.]+\s*[MKk]?)/i);
    const soldMatch = txt.match(/sold[^$\d]*\$?\s*([\d,.]+\s*[MKk]?)/i);
    const bought = parseAmountToken(boughtMatch?.[1] || '');
    const sold = parseAmountToken(soldMatch?.[1] || '');
    if (bought <= 0 && sold <= 0) return null;
    return buildResult(bought, sold, method);
  };

  const pickBetterDevStats = (current, candidate) => {
    if (!candidate) return current;
    if (!current) return candidate;
    const cSold = Number(current.sold) || 0;
    const nSold = Number(candidate.sold) || 0;
    const currentHasSold = !!current.hasSold;
    const candidateHasSold = !!candidate.hasSold;
    const currentStrongSold = currentHasSold && cSold > 15;
    const candidateStrongSold = candidateHasSold && nSold > 15;

    // Prefer explicit/meaningful sold evidence even if confidence is lower.
    // This avoids false negatives where a high-confidence parser read the wrong "sold" column as 0.
    if (candidateStrongSold && !currentStrongSold) return candidate;
    if (currentStrongSold && !candidateStrongSold) return current;
    if (candidateHasSold && !currentHasSold && nSold > 0.000001) return candidate;
    if (currentHasSold && !candidateHasSold && cSold > 0.000001) return current;

    const currentConf = Number(current.confidence) || 0;
    const candidateConf = Number(candidate.confidence) || 0;
    if (candidateConf > currentConf) return candidate;
    if (candidateConf < currentConf) return current;
    if (candidateHasSold === currentHasSold) {
      if (nSold > cSold) return candidate;
      if (nSold === cSold) {
        const cBought = Number(current.bought) || 0;
        const nBought = Number(candidate.bought) || 0;
        if (nBought > cBought) return candidate;
      }
    }
    return current;
  };
  const normalizeAddr = (v) => String(v || '').trim().toLowerCase();
  const deployerFull = normalizeAddr(targetDeployerAddress);
  const deployerShort3 = deployerFull && deployerFull.length > 10
    ? `${deployerFull.slice(0, 5)}...${deployerFull.slice(-4)}`
    : '';
  const deployerCompact = deployerFull && deployerFull.length > 10
    ? `${deployerFull.slice(0, 5)}${deployerFull.slice(-4)}`
    : '';
  const rowMatchesTargetDeployer = (txt) => {
    if (!deployerFull) return false;
    const t = normalizeAddr(txt);
    if (!t) return false;
    if (t.includes(deployerFull)) return true;
    if (deployerShort3 && t.includes(deployerShort3)) return true;
    if (deployerCompact && t.replace(/[^a-z0-9]/g, '').includes(deployerCompact)) return true;
    return false;
  };
  const requireTargetMatch = !!deployerFull;
  const isElementVisible = (el) => {
    if (!el || !(el instanceof Element)) return false;
    if (el.closest('[aria-hidden="true"], [hidden], [style*="display: none"]')) return false;
    const style = window.getComputedStyle(el);
    if (!style || style.display === 'none' || style.visibility === 'hidden') return false;
    const rect = el.getBoundingClientRect();
    return rect.width > 0 && rect.height > 0;
  };
  let bestDevStats = null;

  try {
    // === STRATEGY 1: Standard HTML <table> elements ===
    const tables = document.querySelectorAll('table');
    if (tables.length > 0) {
      console.log(`PRC: Found ${tables.length} HTML tables`);
      for (const table of tables) {
        if (!isElementVisible(table)) continue;
        let headers = Array.from(table.querySelectorAll('th')).map(th => th.innerText.toUpperCase().trim());
        if (!headers.length) {
          const firstRow = table.querySelector('tr');
          headers = firstRow
            ? Array.from(firstRow.querySelectorAll('td, th')).map((c) => (c.innerText || '').toUpperCase().trim())
            : [];
        }
        let bIdx = headers.findIndex(h => h.includes('BOUGHT'));
        let sIdx = headers.findIndex(h => h.includes('SOLD'));
        // Accuracy first: never guess hardcoded indices, they can map to Avg MC.
        if (bIdx < 0 || sIdx < 0) {
          // Fallback: search for P/L or Balance to guess columns if BOUGHT/SOLD headers are slightly different
          const plIdx = headers.findIndex(h => h.includes('P/L') || h.includes('PNL'));
          const balIdx = headers.findIndex(h => h.includes('BALANCE'));
          if (balIdx >= 0 && plIdx > balIdx) {
            bIdx = balIdx + 1;
            sIdx = balIdx + 2;
          }
        }
        if (bIdx < 0 || sIdx < 0) continue;
        const rows = table.querySelectorAll('tr');
        for (const row of rows) {
          if (!isElementVisible(row)) continue;
          const rowText = String(row.innerText || row.textContent || '').trim();
          if (!rowText) continue;
          const isTargetRow = rowMatchesTargetDeployer(rowText);
          const isDevRow = /\bDEV\b/i.test(rowText) || !!row.querySelector('[class*="dev"], [title*="Dev"], [class*="badge"]');
          const isRank1 = /^\s*1\s/i.test(rowText); // Many Padre views show DEV at rank 1
          if (!isDevRow && !isTargetRow && !isRank1) continue;
          const cells = row.querySelectorAll('td');
          if (cells.length > Math.max(bIdx, sIdx)) {
            const bought = parseVal(cells[bIdx]?.innerText || "");
            const sold = parseVal(cells[sIdx]?.innerText || "");
            if (bought > 0 || sold > 0) {
              bestDevStats = pickBetterDevStats(bestDevStats, buildResult(bought, sold, isTargetRow ? "ROW-STRICT-DA" : "TABLE"));
            }
          }
        }
      }
    }

    // === STRATEGY 2A: MUI DataGrid cell-based approach ===
    console.log("PRC: Trying MUI DataGrid cell-based scan...");

    // First, find the grid container (MUI uses data-field, so we skip building a fragile index map)
    const grids = document.querySelectorAll('[role="grid"], [class*="MuiDataGrid"], [class*="datagrid"]');
    for (const grid of grids) {
      if (!isElementVisible(grid)) continue;
      
      // Find the DEV row within this grid
      const rows = grid.querySelectorAll('[role="row"]');
      for (const row of rows) {
        if (!isElementVisible(row)) continue;
        const rowText = String(row.textContent || '').trim();
        const isTargetRow = rowMatchesTargetDeployer(rowText);
        const isDevRow = /\bDEV\b/i.test(rowText);
        if (!isDevRow && !isTargetRow) continue;
        // Make sure this is a data row (contains a DEV badge), not a header row
        if (row.querySelector('[role="columnheader"]')) continue;

        const cells = row.querySelectorAll('[role="gridcell"], [role="cell"]');
        
        // Try data-field based lookup first
        let boughtCell = null, soldCell = null;
        for (const cell of cells) {
          const field = (cell.getAttribute('data-field') || '').toLowerCase();
          if (field.includes('bought') || field.includes('buy')) boughtCell = cell;
          if (field.includes('sold') || field.includes('sell')) soldCell = cell;
        }

        if (boughtCell && soldCell) {
          const bought = parseVal(boughtCell.textContent);
          const sold = parseVal(soldCell.textContent);
          bestDevStats = pickBetterDevStats(bestDevStats, buildResult(bought, sold, isTargetRow ? "ROW-STRICT-DA" : "CELL-DATAFIELD"));
          continue;
        }
      }
    }

    // === STRATEGY 2B: DEV badge walk-up with CELL extraction (not text splitting) ===
    console.log("PRC: Trying DEV badge walk-up with cell extraction...");
    const allEls = document.body.querySelectorAll('*');
    for (const el of allEls) {
      const text = el.textContent.trim().toUpperCase();
      // Allow extra chars for emoji symbols.
      if (!text.includes('DEV') || text.length > 15) continue;
      if (el.children.length > 3 || el.innerHTML.length > 200) continue;

      // Walk up to find the row container
      let rowEl = el.closest('[role="row"], tr');
      if (!rowEl) {
        let candidate = el.parentElement;
        for (let i = 0; i < 12 && candidate; i++) {
          const candidateText = candidate.innerText || '';
          const dollarCount = (candidateText.match(/\$/g) || []).length;
          if (dollarCount >= 2 && dollarCount <= 20) {
            const childDivs = candidate.querySelectorAll(':scope > *');
            if (childDivs.length >= 3) { rowEl = candidate; break; }
          }
          candidate = candidate.parentElement;
        }
      }

      if (!rowEl) continue;
      if (!isElementVisible(rowEl)) continue;
      const rowText = String(rowEl.innerText || rowEl.textContent || '').trim();
      const isTargetRow = rowMatchesTargetDeployer(rowText);
      const isDevRow = /\bDEV\b/i.test(rowText);
      if (!isDevRow && !isTargetRow) continue;

      // Try to get cells within this row
      const cells = rowEl.querySelectorAll('[role="gridcell"], [role="cell"]');
      if (cells.length >= 4) {
        // Try data-field based lookup
        let boughtCell = null, soldCell = null;
        for (const cell of cells) {
          const field = (cell.getAttribute('data-field') || '').toLowerCase();
          if (field.includes('bought') || field.includes('buy')) boughtCell = cell;
          if (field.includes('sold') || field.includes('sell')) soldCell = cell;
        }
        if (boughtCell && soldCell) {
          const bought = parseVal(boughtCell.textContent);
          const sold = parseVal(soldCell.textContent);
          bestDevStats = pickBetterDevStats(bestDevStats, buildResult(bought, sold, isTargetRow ? "ROW-STRICT-DA" : "BADGE-DATAFIELD"));
          continue;
        }

      }
    }

    if (bestDevStats) return bestDevStats;

    // Strategies 3, 4, 5 (raw string parsing) were removed due to extreme fragility.
    // getPadreDevStatsQuickFallback handles string fallback much more robustly.

  } catch (e) {
    console.warn("PRC: getPadreDevStats error:", e);
  }
  return null;
}

function parsePadreMoneyValue(raw) {
  const text = String(raw || '')
    .replace(/[\u00A0\u2007\u202F]/g, ' ')
    .trim();
  if (!text || text === '-' || text === '--') return 0;

  const parseToken = (numRaw, suffixRaw) => {
    const base = parseFloat(String(numRaw || '').replace(/,/g, ''));
    if (!Number.isFinite(base)) return null;
    const suffix = String(suffixRaw || '').toUpperCase();
    if (suffix === 'K') return base * 1000;
    if (suffix === 'M') return base * 1000000;
    return base;
  };

  const parseFirstNumericToken = (src) => {
    const re = /\$?\s*([\d,.]+)\s*([MKk]?)/g;
    let m;
    while ((m = re.exec(String(src || ''))) !== null) {
      const value = parseToken(m[1], m[2]);
      if (value !== null) return value;
    }
    return null;
  };

  // Padre cells usually have the primary metric on the first line and
  // secondary metrics (e.g. Avg MC) below. Always trust first line first.
  const lines = text.split(/\r?\n/).map((l) => String(l || '').trim()).filter(Boolean);
  if (lines.length > 1) {
    const first = lines[0];
    if (!first || first === '-' || first === '--') return 0;
    const firstLineValue = parseFirstNumericToken(first);
    return firstLineValue !== null ? firstLineValue : 0;
  }

  const firstValue = parseFirstNumericToken(text);
  return firstValue !== null ? firstValue : 0;
}

function getPadreDevSoldStrict(targetDeployerAddress = null) {
  try {
    const skipSelector = '[class*="sidebar"], [id*="sidebar"], [class*="recent"], [class*="trending"]';
    const normalizeText = (v) => String(v || '').toLowerCase().replace(/\s+/g, ' ').trim();
    const normalizeAddr = (v) => String(v || '').trim().toLowerCase();
    const deployerFull = normalizeAddr(targetDeployerAddress);
    const deployerShort = deployerFull && deployerFull.length > 10
      ? `${deployerFull.slice(0, 5)}...${deployerFull.slice(-4)}`
      : '';
    const deployerCompact = deployerFull && deployerFull.length > 10
      ? `${deployerFull.slice(0, 5)}${deployerFull.slice(-4)}`
      : '';
    const rowMatchesTargetDeployer = (txt) => {
      if (!deployerFull) return false;
      const t = normalizeAddr(txt);
      if (!t) return false;
      if (t.includes(deployerFull)) return true;
      if (deployerShort && t.includes(deployerShort)) return true;
      if (deployerCompact && t.replace(/[^a-z0-9]/g, '').includes(deployerCompact)) return true;
      return false;
    };
    const isElementVisible = (el) => {
      if (!el || !(el instanceof Element)) return false;
      if (el.closest('[aria-hidden="true"], [hidden], [style*="display: none"]')) return false;
      const style = window.getComputedStyle(el);
      if (!style || style.display === 'none' || style.visibility === 'hidden') return false;
      const rect = el.getBoundingClientRect();
      return rect.width > 0 && rect.height > 0;
    };
    const getContextScore = (node, headers = []) => {
      const normalizedHeaders = headers.map((h) => normalizeText(h));
      const hasAddress = normalizedHeaders.some((h) => h.includes('address') || h.includes('holder'));
      const hasRank = normalizedHeaders.some((h) => h === 'rank' || h.startsWith('rank '));
      const hasBalance = normalizedHeaders.some((h) => h.includes('balance'));
      const hasBought = normalizedHeaders.some((h) => h.includes('bought') || h.includes('buy'));
      const hasSold = normalizedHeaders.some((h) => h.includes('sold') || h.includes('sell'));

      let score = 0;
      if (hasBought && hasSold) score += 160;
      if (hasAddress) score += 120;
      if (hasBalance) score += 100;
      if (hasRank) score += 70;

      const scope = node?.closest?.('section, article, main, div') || node;
      const scopeText = normalizeText(scope?.innerText || scope?.textContent || '');
      if (scopeText.includes('holders')) score += 90;
      if (scopeText.includes('top traders')) score -= 80;
      if (scopeText.includes('dev tokens')) score -= 80;

      const rect = node?.getBoundingClientRect?.();
      if (rect && Number.isFinite(rect.left) && rect.left < (window.innerWidth * 0.80)) score += 25;
      return score;
    };
    const isLikelyHoldersHeader = (headers = []) => {
      const normalized = headers.map((h) => normalizeText(h));
      const hasBought = normalized.some((h) => h.includes('bought') || h.includes('buy'));
      const hasSold = normalized.some((h) => h.includes('sold') || h.includes('sell'));
      const hasAddressLike = normalized.some((h) => h.includes('address') || h.includes('holder') || h.includes('rank') || h.includes('balance'));
      return hasBought && hasSold && hasAddressLike;
    };
    const candidates = [];
    const addCandidate = ({ sold, source, isDevRow, isTargetRow, quality = 0, context = 0 }) => {
      const soldNum = Number(sold);
      if (!Number.isFinite(soldNum) || soldNum < 0) return;
      candidates.push({
        sold: soldNum,
        source: String(source || 'unknown'),
        isDevRow: !!isDevRow,
        isTargetRow: !!isTargetRow,
        quality: Number(quality) || 0,
        context: Number(context) || 0
      });
    };
    // Strategy 1: Standard HTML <table> elements
    const tables = document.querySelectorAll('table');
    for (const table of tables) {
      if (table.closest(skipSelector)) continue;
      if (!isElementVisible(table)) continue;
      let headers = Array.from(table.querySelectorAll('th')).map(th => th.innerText.toUpperCase().trim());
      if (!headers.length) {
        const firstRow = table.querySelector('tr');
        headers = firstRow ? Array.from(firstRow.querySelectorAll('td, th')).map((c) => (c.innerText || '').toUpperCase().trim()) : [];
      }
      const sIdx = headers.findIndex(h => h.includes('SOLD') || h.includes('SELL'));
      if (sIdx < 0) continue;
      const contextScore = getContextScore(table, headers);
      if (contextScore < 100) continue;

      const rows = table.querySelectorAll('tr');
      for (const row of rows) {
        if (!isElementVisible(row)) continue;
        const rowText = String(row.innerText || '').trim();
        const isTargetRow = rowMatchesTargetDeployer(rowText);
        const isDevRow = /\bDEV\b/i.test(rowText) || /^\s*1\s/i.test(rowText);
        if (!isDevRow && !isTargetRow) continue;
        const cells = row.querySelectorAll('td');
        if (cells.length > sIdx) {
          addCandidate({
            sold: parsePadreMoneyValue(cells[sIdx]?.innerText || ""),
            source: isTargetRow ? 'table-target' : 'table-dev',
            isDevRow,
            isTargetRow,
            quality: 92,
            context: contextScore
          });
        }
      }
    }

    // Strategy 2: datagrid-like layouts relying strictly on data-field
    const grids = document.querySelectorAll('[role="grid"], [class*="MuiDataGrid"], [class*="datagrid"]');
    for (const grid of grids) {
      if (grid.closest(skipSelector)) continue;
      if (!isElementVisible(grid)) continue;
      
      // Still compute context score from headers if available to prioritize real tables
      const headers = grid.querySelectorAll('[role="columnheader"]');
      const headerTexts = Array.from(headers).map((h) => String(h.textContent || ''));
      const contextScore = getContextScore(grid, headerTexts);

      const rows = grid.querySelectorAll('[role="row"]');
      for (const row of rows) {
        if (!isElementVisible(row)) continue;
        if (row.querySelector('[role="columnheader"]')) continue; // skip header rows
        const rowText = String(row.textContent || '').trim();
        const isTargetRow = rowMatchesTargetDeployer(rowText);
        const isDevRow = /\bDEV\b/i.test(rowText);
        if (!isDevRow && !isTargetRow) continue;

        const cells = row.querySelectorAll('[role="gridcell"], [role="cell"]');
        if (!cells.length) continue;

        const soldCellByField = Array.from(cells).find((c) => {
          const field = String(c.getAttribute('data-field') || '').toLowerCase();
          return field.includes('sold') || field.includes('sell');
        });
        
        if (!soldCellByField) continue;

        const soldRaw = soldCellByField.textContent || '';
        if (!soldRaw) continue;

        const soldValue = parsePadreMoneyValue(soldRaw);
        addCandidate({
          sold: soldValue,
          source: isTargetRow ? 'grid-field-target' : 'grid-field-dev',
          isDevRow,
          isTargetRow,
          quality: 88,
          context: contextScore
        });
      }
    }

    if (candidates.length) {
      const devCandidates = candidates.filter((c) => c.isDevRow);
      const targetCandidates = candidates.filter((c) => c.isTargetRow);
      // If we know the target deployer and have matching rows, prefer those rows first.
      // This avoids picking unrelated DEV rows and creating false "sold" positives.
      const pool = (deployerFull && targetCandidates.length)
        ? targetCandidates
        : (devCandidates.length ? devCandidates : targetCandidates);
      pool.sort((a, b) => {
        const aScore = (a.context * 3) + a.quality + (a.isDevRow ? 20 : 0);
        const bScore = (b.context * 3) + b.quality + (b.isDevRow ? 20 : 0);
        if (bScore !== aScore) return bScore - aScore;
        // On score ties, do not bias toward positive sold values.
        // Prefer explicit target row, then explicit DEV row.
        if (a.isTargetRow !== b.isTargetRow) return (b.isTargetRow ? 1 : 0) - (a.isTargetRow ? 1 : 0);
        if (a.isDevRow !== b.isDevRow) return (b.isDevRow ? 1 : 0) - (a.isDevRow ? 1 : 0);
        if (b.sold !== a.sold) return b.sold - a.sold;
        return 0;
      });
      const best = pool[0];
      return { found: true, sold: Number(best.sold) || 0, source: String(best.source || 'unknown') };
    }
  } catch (err) {
    console.warn('PRC: getPadreDevSoldStrict failed:', err?.message || err);
  }
  return { found: false, sold: 0, source: 'none' };
}

function getPadreDevStatsQuickFallback(targetDeployerAddress = null) {
  try {
    const text = String(document.body?.innerText || '');
    if (!text) return null;
    const normalizeAddr = (v) => String(v || '').trim().toLowerCase();
    const deployerFull = normalizeAddr(targetDeployerAddress);
    const deployerShort = deployerFull && deployerFull.length > 10
      ? `${deployerFull.slice(0, 5)}...${deployerFull.slice(-4)}`
      : '';
    const deployerCompact = deployerFull && deployerFull.length > 10
      ? `${deployerFull.slice(0, 5)}${deployerFull.slice(-4)}`
      : '';
    const blockMatchesTargetDeployer = (txt) => {
      if (!deployerFull) return true;
      const t = normalizeAddr(txt);
      if (!t) return false;
      if (t.includes(deployerFull)) return true;
      if (deployerShort && t.includes(deployerShort)) return true;
      if (deployerCompact && t.replace(/[^a-z0-9]/g, '').includes(deployerCompact)) return true;
      // Flattened/virtualized DEV rows often omit DA text even when they are the
      // real holder row. Accept those only if they look like transaction rows.
      if (/\bdev\b/.test(t) && /\b\d+\s*(txn|txns|tx)\b/.test(t)) return true;
      return false;
    };

    const parseMoneyStrict = (txt) => {
      const vals = [];
      // Catch values with $, optionally with + or - prefix
      const re = /([+-]?\$)\s*([\d,.]+)\s*([MKk]?)/g;
      let m;
      while ((m = re.exec(String(txt || ''))) !== null) {
        let v = parseFloat(String(m[2] || '').replace(/,/g, ''));
        if (!Number.isFinite(v)) continue;
        const isNeg = String(m[1] || '').includes('-');
        const suf = String(m[3] || '').toUpperCase();
        if (suf === 'K') v *= 1000;
        if (suf === 'M') v *= 1000000;
        if (isNeg) v *= -1;
        vals.push({ val: v, index: m.index, full: m[0] });
      }
      return vals;
    };

    const lines = text.split(/\r?\n/);
    let best = null;
    for (let i = 0; i < lines.length; i++) {
      const line = String(lines[i] || '').trim();
      if (!/\bDEV\b/i.test(line)) continue;
      const block = lines.slice(Math.max(0, i - 1), Math.min(lines.length, i + 10)).join(' ');
      if (!blockMatchesTargetDeployer(block)) continue;
      
      // We found the DEV row. Now extract columns.
      // Standard Padre row: Rank Address DEV Balance Bought[$] Sold[$] PnL[$]
      const moneyItems = parseMoneyStrict(line);
      if (moneyItems.length < 2) continue; // Need at least two money values

      let bought = moneyItems[0].val;
      let sold = 0;
      let pnl = 0;

      if (moneyItems.length >= 3) {
        // We have at least 3 values. Standard is Bought, Sold, PnL.
        sold = moneyItems[1].val;
        pnl = moneyItems[2].val;
      } else {
        // Only 2 values. Standard is Bought, PnL. Sold might be in between without $.
        const boughtItem = moneyItems[0];
        const pnlItem = moneyItems[1];
        pnl = pnlItem.val;
        
        const midStart = boughtItem.index + boughtItem.full.length;
        const midEnd = pnlItem.index;
        if (midEnd > midStart) {
          const midText = line.substring(midStart, midEnd).trim();
          // Use parsePadreMoneyValue for the middle text to catch suffixes correctly
          sold = parsePadreMoneyValue(midText);
        }
      }

      if (bought <= 0 && (sold === 0 || sold === null)) continue;

      const hasTxnMarker = /\b\d+\s*(txn|txns|tx)\b/i.test(block);
      const quality = (hasTxnMarker ? 50 : 0) + 40;
      const candidate = {
        bought,
        sold,
        hasSold: sold > 15,
        isDump: sold > 15 && (sold >= (bought * 0.90) || sold > bought),
        method: 'DEV-QUICK-FALLBACK-ROBUST',
        confidence: 65 + quality
      };

      if (!best || candidate.sold > best.sold) {
        best = candidate;
      }
    }
    return best;
  } catch (err) {
    console.warn('PRC: getPadreDevStatsQuickFallback failed:', err?.message || err);
    return null;
  }
}

function getPadreBestDevStats(targetDeployerAddress = null) {
  const primary = getPadreDevStats(targetDeployerAddress);
  const quickTarget = getPadreDevStatsQuickFallback(targetDeployerAddress);
  const hasTarget = !!String(targetDeployerAddress || '').trim();
  // Important: when a target deployer is known, never mix loose/global DEV stats
  // because they can come from other rows/tables and create false sold positives.
  const quickLoose = hasTarget ? null : getPadreDevStatsQuickFallback(null);
  const candidates = [primary, quickTarget, quickLoose].filter(Boolean);
  if (!candidates.length) return null;

  const scoreCandidate = (candidate) => {
    const bought = Number(candidate?.bought) || 0;
    const sold = Number(candidate?.sold) || 0;
    const confidence = Number(candidate?.confidence) || 0;
    const ratio = bought > 0 ? (sold / bought) : (sold > 0 ? 9 : 0);
    const hasStrongSold = !!(
      candidate?.hasSold ||
      sold > 15 ||
      candidate?.isDump ||
      (bought > 0 && (sold >= (bought * 0.50) || sold > bought))
    );
    const method = String(candidate?.method || '').toUpperCase();
    let score = confidence;
    if (hasStrongSold) score += 140;
    score += Math.min(120, sold / 5);
    if (ratio >= 1) score += 45;
    else if (ratio >= 0.20) score += 20;
    if (method.includes('QUICK')) score += 12;
    if (sold <= 0.000001 && bought > 0) score -= 30;
    return score;
  };

  let best = candidates[0];
  let bestScore = scoreCandidate(best);
  for (let i = 1; i < candidates.length; i += 1) {
    const current = candidates[i];
    const sc = scoreCandidate(current);
    if (sc > bestScore) {
      best = current;
      bestScore = sc;
    }
  }
  return best;
}

function getPadreDevHoldingPct() {
  try {
    const normalize = (v) => String(v || '')
      .replace(/[\u00A0\u2007\u202F]/g, ' ')
      .replace(/\s+/g, ' ')
      .trim();

    const parsePct = (txt) => {
      const m = String(txt || '').match(/([0-9]+(?:[.,][0-9]+)?)\s*%/);
      if (!m?.[1]) return null;
      const val = Number(m[1].replace(',', '.'));
      if (!Number.isFinite(val) || val < 0 || val > 100) return null;
      return val;
    };
    const isVisible = (el) => {
      if (!el || !(el instanceof Element)) return false;
      if (el.closest('[aria-hidden="true"], [hidden], [style*="display: none"]')) return false;
      const style = window.getComputedStyle(el);
      if (!style || style.display === 'none' || style.visibility === 'hidden') return false;
      const rect = el.getBoundingClientRect();
      return rect.width > 0 && rect.height > 0;
    };
    const scoreNode = (el) => {
      if (!el || !(el instanceof Element)) return 0;
      const rect = el.getBoundingClientRect();
      let score = 0;
      if (rect.left >= (window.innerWidth * 0.55)) score += 20;
      if (rect.left >= (window.innerWidth * 0.70)) score += 10;
      if (rect.width > 0 && rect.height > 0) score += 5;
      const txt = normalize(el.textContent || '');
      if (/token\s*data\s*&\s*security/i.test(txt)) score += 10;
      if (/dev\s*holding/i.test(txt)) score += 20;
      return score;
    };

    const candidates = [];
    const addCandidate = (value, node, bonus = 0) => {
      if (!Number.isFinite(Number(value))) return;
      const v = Number(value);
      if (v < 0 || v > 100) return;
      candidates.push({ value: v, score: scoreNode(node) + (Number(bonus) || 0) });
    };

    // Strategy 1: find explicit "Dev holding" labels and read nearest percentage.
    const labels = Array.from(document.querySelectorAll('div, span, p, li, button, a')).filter((el) => {
      if (!isVisible(el)) return false;
      const txt = normalize(el.textContent || '');
      return /\bdev\s*holding\b/i.test(txt);
    });
    for (const labelEl of labels) {
      const labelText = normalize(labelEl.textContent || '');
      const direct = parsePct(labelText);
      if (direct !== null) addCandidate(direct, labelEl, 40);

      let scope = labelEl;
      for (let i = 0; i < 4 && scope; i += 1) {
        const scopeText = normalize(scope.textContent || '');
        const m = scopeText.match(/dev\s*holding[\s\W]{0,120}?([0-9]+(?:[.,][0-9]+)?)\s*%/i);
        if (m?.[1]) {
          addCandidate(Number(m[1].replace(',', '.')), scope, 35 - (i * 3));
        }
        const pctInScope = parsePct(scopeText);
        if (pctInScope !== null) {
          addCandidate(pctInScope, scope, 10 - (i * 2));
        }
        scope = scope.parentElement;
      }

      const parent = labelEl.parentElement;
      if (parent) {
        const siblings = parent.querySelectorAll(':scope > *');
        for (const sib of siblings) {
          if (!isVisible(sib)) continue;
          const pct = parsePct(normalize(sib.textContent || ''));
          if (pct !== null) addCandidate(pct, sib, 24);
        }
      }
    }

    // Strategy 2: fallback from full text chunk around Token Data & Security.
    const bodyText = normalize(document.body?.innerText || '');
    if (bodyText) {
      const secIdx = bodyText.toLowerCase().indexOf('token data & security');
      if (secIdx >= 0) {
        const chunk = bodyText.slice(secIdx, Math.min(bodyText.length, secIdx + 4200));
        const m = chunk.match(/dev\s*holding[\s\W]{0,180}?([0-9]+(?:[.,][0-9]+)?)\s*%/i);
        if (m?.[1]) addCandidate(Number(m[1].replace(',', '.')), document.body, 8);
      }
      const global = bodyText.match(/dev\s*holding[\s\W]{0,180}?([0-9]+(?:[.,][0-9]+)?)\s*%/i);
      if (global?.[1]) addCandidate(Number(global[1].replace(',', '.')), document.body, 2);
    }

    if (!candidates.length) return null;
    candidates.sort((a, b) => b.score - a.score);
    return candidates[0].value;
  } catch (_) { }
  return null;
}

function getPadreDevMintedCount() {
  try {
    const text = String(document.body?.innerText || '');
    const normalized = text
      .replace(/[\u00A0\u2007\u202F]/g, ' ')
      .replace(/[\u200B-\u200D\uFEFF]/g, '');
    const re = /dev[\s\W]*tokens?[\s\W]*\(\s*([\d.,]+)\s*\)/gi;
    let match;
    let best = null;
    while ((match = re.exec(normalized)) !== null) {
      const raw = String(match[1] || '').replace(/[^\d]/g, '');
      const value = Number(raw);
      if (Number.isFinite(value)) {
        if (best === null || value > best) best = value;
      }
    }
    if (Number.isFinite(best)) return best;

    // Extra fallback: normalize whitespace variants and parse from full text blob.
    const normalizedText = normalized;
    const mNorm = normalizedText.match(/dev\s*tokens?\s*\(\s*([\d.,]+)\s*\)/i);
    if (mNorm && mNorm[1]) {
      const value = Number(String(mNorm[1]).replace(/[^\d]/g, ''));
      if (Number.isFinite(value) && value > 0) return value;
    }

    // Fallback: scan likely tab/header nodes where "Dev Tokens" appears.
    const nodes = document.querySelectorAll('button, a, [role="tab"], div, span');
    for (const node of nodes) {
      const t = String(node?.textContent || '')
        .replace(/[\u00A0\u2007\u202F]/g, ' ')
        .replace(/[\u200B-\u200D\uFEFF]/g, '')
        .trim();
      if (!/dev[\s\W]*tokens?/i.test(t)) continue;
      const m = t.match(/dev[\s\W]*tokens?[\s\W]*\(?\s*([\d.,]+)\s*\)?/i);
      if (!m) continue;
      const raw = String(m[1] || '').replace(/[^\d]/g, '');
      const value = Number(raw);
      if (Number.isFinite(value)) {
        if (best === null || value > best) best = value;
      }
    }
    if (Number.isFinite(best)) return best;
  } catch (_) { }
  return null;
}

function getLockedSupplyPctFromUI() {
  // Disabled: user requested no locked-supply detection.
  return null;
  try {
    const host = (location.hostname || '').toLowerCase();
    if (!host.includes('trade.padre.gg') && !host.includes('dexscreener.com')) return null;

    const normalize = (s) => String(s || '')
      .replace(/[\u00A0\u2007\u202F]/g, ' ')
      .replace(/[\u200B-\u200D\uFEFF]/g, '')
      .replace(/\s+/g, ' ')
      .trim();
    const parsePct = (txt) => {
      const m = String(txt || '').match(/([0-9]+(?:\.[0-9]+)?)\s*%/);
      if (!m?.[1]) return null;
      const value = Number(m[1]);
      if (!Number.isFinite(value) || value < 0 || value > 100) return null;
      return value;
    };
    const isVisible = (el) => {
      if (!el || !(el instanceof Element)) return false;
      if (el.closest('[aria-hidden="true"], [hidden], [style*="display: none"]')) return false;
      const style = window.getComputedStyle(el);
      if (!style || style.display === 'none' || style.visibility === 'hidden') return false;
      const rect = el.getBoundingClientRect();
      return rect.width > 0 && rect.height > 0;
    };
    const isBlueish = (el) => {
      if (!el || !(el instanceof Element)) return false;
      const s = window.getComputedStyle(el);
      if (!s) return false;
      const color = String(s.color || '');
      const bg = String(s.backgroundColor || '');
      const isBlueRgb = (rgb) => {
        const m = rgb.match(/rgba?\(([^)]+)\)/i);
        if (!m?.[1]) return false;
        const parts = m[1].split(',').map((p) => Number(String(p).trim()));
        if (parts.length < 3) return false;
        const [r, g, b] = parts;
        return Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b) && b >= 120 && b > r && b > g;
      };
      return isBlueRgb(color) || isBlueRgb(bg);
    };
    const hasLockMarker = (el) => {
      if (!el || !(el instanceof Element)) return false;
      const txt = normalize(el.textContent || '');
      if (/🔒|🔐/.test(txt)) return true;
      if (/\b(lock|locked|locking|supply\s*lock(?:ed)?)\b/i.test(txt)) return true;
      const attrs = [
        el.getAttribute('aria-label'),
        el.getAttribute('title'),
        el.getAttribute('class'),
        el.getAttribute('data-icon')
      ].filter(Boolean).join(' ');
      if (/\block\b/i.test(attrs)) return true;
      return !!el.querySelector(
        '[aria-label*="lock" i], [title*="lock" i], [class*="lock" i], [data-icon*="lock" i], svg'
      );
    };

    const candidates = [];
    const addCandidate = (value, el, bonus = 0) => {
      const pct = Number(value);
      if (!Number.isFinite(pct) || pct < 0 || pct > 100) return;
      const rect = (el && el.getBoundingClientRect) ? el.getBoundingClientRect() : null;
      let score = Number(bonus) || 0;
      if (rect) {
        if (rect.top < 220) score += 8;
        if (rect.left > (window.innerWidth * 0.55)) score += 8;
      }
      candidates.push({ value: pct, score });
    };

    const explicitNodes = Array.from(document.querySelectorAll('div, span, p, li, button, a')).filter((el) => {
      if (!isVisible(el)) return false;
      const t = normalize(el.textContent || '');
      return /\b(locked\s*supply|supply\s*locked|supply\s*lock)\b/i.test(t);
    });
    for (const el of explicitNodes) {
      const direct = parsePct(normalize(el.textContent || ''));
      if (direct !== null) addCandidate(direct, el, 60);
      let scope = el;
      for (let i = 0; i < 3 && scope; i += 1) {
        const t = normalize(scope.textContent || '');
        const m = t.match(/(?:locked\s*supply|supply\s*locked|supply\s*lock)[\s\S]{0,120}?([0-9]+(?:\.[0-9]+)?)\s*%/i);
        if (m?.[1]) addCandidate(Number(m[1]), scope, 48 - (i * 6));
        scope = scope.parentElement;
      }
    }

    const pctNodes = Array.from(document.querySelectorAll('div, span, button, a')).filter((el) => {
      if (!isVisible(el)) return false;
      const t = normalize(el.textContent || '');
      return /^[0-9]+(?:\.[0-9]+)?%$/.test(t);
    });
    for (const el of pctNodes) {
      const pct = parsePct(normalize(el.textContent || ''));
      if (pct === null) continue;
      const parent = el.parentElement;
      const grand = parent?.parentElement || null;
      const lockNearby = hasLockMarker(el) || hasLockMarker(parent) || hasLockMarker(grand);
      const blueNearby = isBlueish(el) || isBlueish(parent || el) || isBlueish(grand || el);
      if (lockNearby) addCandidate(pct, el, 42);
      if (lockNearby && blueNearby) addCandidate(pct, el, 24);
    }

    const bodyText = normalize(document.body?.innerText || '');
    if (bodyText) {
      const global = bodyText.match(/(?:locked\s*supply|supply\s*locked|supply\s*lock)[\s\S]{0,140}?([0-9]+(?:\.[0-9]+)?)\s*%/i);
      if (global?.[1]) addCandidate(Number(global[1]), document.body, 10);
    }

    if (!candidates.length) return null;
    candidates.sort((a, b) => b.score - a.score);
    return candidates[0].value;
  } catch (_) { }
  return null;
}


function getMintFromURL() {
  const url = window.location.href;
  const host = (location.hostname || '').toLowerCase();
  const directAddress = getAddressFromUrl(url);
  if (directAddress) return { address: directAddress, node: null };

  if (url.includes('padre.gg')) {
    const res = getPadreMint();
    if (res && res.address) return res;
  }

  if (host.includes('dexscreener.com')) {
    // DexScreener URLs are often lowercased; capture leniently and let background resolve.
    const dexLoosePathMatch = url.match(/\/(?:solana|token)\/([A-Za-z0-9]{32,64})/);
    if (dexLoosePathMatch) return { address: dexLoosePathMatch[1].trim(), node: null };

    const dexPathMatch = url.match(/\/(?:solana|token)\/([1-9A-HJ-NP-Za-km-z]{32,44})/);
    if (dexPathMatch) return { address: dexPathMatch[1].trim(), node: null };

    const dexScopedUrl = `${location.pathname}${location.search}${location.hash}`;
    if (dexScopedUrl.includes('/solana/')) {
      const dexGenericMatch = dexScopedUrl.match(/([1-9A-HJ-NP-Za-km-z]{32,44})/);
      if (dexGenericMatch) return { address: dexGenericMatch[1].trim(), node: null };
    }

    const dexAnchor = document.querySelector('a[href*="/solana/"], a[href*="/token/"]');
    if (dexAnchor?.href) {
      const linkedAddress = getAddressFromUrl(dexAnchor.href);
      if (linkedAddress) return { address: linkedAddress, node: dexAnchor };
    }
  }

  // DEXScreener: dexscreener.com/solana/[address]
  // ...
  const match = url.match(/(?:\/solana\/|\/lp\/|\/board\/|\/coin\/|\/trade\/solana\/|\/terminal\?.*address=)([1-9A-HJ-NP-Za-km-z]{32,44})/);
  if (match) return { address: match[1].trim(), node: null };

  // Try searching for labels or pump suffixes in DOM
  const bodyText = document.body.innerText;
  const caMatches = bodyText.match(/(?:CA|Mint|Contract):\s*([1-9A-HJ-NP-Za-km-z]{32,44})/i);
  if (caMatches) return { address: caMatches[1].trim(), node: null };

  console.log("PRC: No high-confidence mint address found");
  return null;
}

function isPadreTokenContext() {
  const host = (location.hostname || '').toLowerCase();
  if (!host.includes('trade.padre.gg')) return false;
  const mint = getPadreMint();
  return !!(mint && mint.address);
}

function isPadreHostContext() {
  const host = (location.hostname || '').toLowerCase();
  return host.includes('trade.padre.gg');
}

function getPadreTokenAddressFromUrl() {
  if (!isPadreHostContext()) return null;
  return getAddressFromUrl(location.href);
}

function shortAddr(addr) {
  if (!addr || addr.length < 10) return addr || "Unknown";
  return addr.slice(0, 6) + "..." + addr.slice(-4);
}

function getAddressFromUrl(url) {
  if (!url) return null;
  try {
    const parsed = new URL(url, window.location.origin);
    const queryAddress = parsed.searchParams.get('address');
    if (queryAddress && /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(queryAddress)) {
      return queryAddress.trim();
    }
  } catch (e) {
    // Ignore malformed URLs and fall back to regex.
  }

  // DexScreener URLs can be lowercased; parse leniently here and let background canonicalize.
  if (String(url).toLowerCase().includes('dexscreener.com')) {
    const dexLooseMatch = String(url).match(/\/(?:solana|token)\/([A-Za-z0-9]{32,64})/);
    if (dexLooseMatch) return dexLooseMatch[1].trim();
  }

  const pathMatch = String(url).match(/(?:\/solana\/|\/token\/|\/lp\/|\/board\/|\/coin\/|\/trade\/solana\/)([1-9A-HJ-NP-Za-km-z]{32,44})/);
  if (pathMatch) return pathMatch[1].trim();
  const raw = String(url);
  const pumpPathMatch = raw.match(/\/([1-9A-HJ-NP-Za-km-z]{32,44})\.pump(?:[/?#]|$)/);
  return pumpPathMatch ? pumpPathMatch[1].trim() : null;
}

function getClientFingerprint() {
  return {
    userAgent: navigator.userAgent || '',
    platform: navigator.platform || '',
    language: navigator.language || '',
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || '',
    screen: `${window.screen?.width || 0}x${window.screen?.height || 0}`,
    cores: navigator.hardwareConcurrency || 0
  };
}

let prcLoaderProgressValue = 0;
let prcLoaderProgressDone = false;

function resetLoaderProgress() {
  prcLoaderProgressValue = 0;
  prcLoaderProgressDone = false;
}

function setLoaderProgress(pct = 0, label = "", options = {}) {
  const fill = document.getElementById('prc-loader-progress-fill');
  const text = document.getElementById('prc-loader-label');
  const clamped = Math.max(0, Math.min(100, Number(pct) || 0));
  const force = options && options.force === true;
  const next = force ? clamped : Math.max(prcLoaderProgressValue, clamped);
  if (prcLoaderProgressDone && !force && next < 100) return;
  if (fill) fill.style.width = `${next}%`;
  if (text && label && (force || next >= prcLoaderProgressValue)) text.textContent = label;
  prcLoaderProgressValue = next;
  if (next >= 100) prcLoaderProgressDone = true;
}

function completeLoaderThen(callback, delayMs = 220) {
  setLoaderProgress(100, "Done", { force: true });
  setTimeout(() => {
    if (typeof callback === 'function') callback();
  }, delayMs);
}

function tryClickHoldersTab() {
  // Safety: never manipulate Padre terminal navigation/tabs.
  return;
}

// Helper to check if the extension context is still valid (not reloaded)
function isContextValid() {
  try {
    return !!(chrome && chrome.runtime && chrome.runtime.id);
  } catch (e) {
    return false;
  }
}

function safeGetURL(path) {
  if (!isContextValid()) return "";
  try {
    return chrome.runtime.getURL(path);
  } catch (e) {
    return "";
  }
}

const PRC_ASSET_MANIFEST_PATH = 'assets-manifest.json';
const PRC_ASSET_BUNDLE_PATH = 'assets.pack';
const PRC_ASSET_KEY_PARTS = ['Trench', 'Watcher', 'Hermeto', 'BlackBook', 'Assets', 'v1'];
let prcAssetManifest = null;
let prcAssetBundleBytes = null;
let prcAssetKeyPromise = null;
const prcAssetUrlCache = new Map();

function normalizeAssetName(path) {
  const raw = String(path || '').trim();
  if (!raw) return '';
  return raw.replace(/\\/g, '/').replace(/^\/+/, '').replace(/^assets\//, '');
}

function hexToUint8Array(hex) {
  const clean = String(hex || '').trim();
  if (!clean || clean.length % 2 !== 0) return new Uint8Array(0);
  const out = new Uint8Array(clean.length / 2);
  for (let i = 0; i < clean.length; i += 2) {
    out[i / 2] = parseInt(clean.slice(i, i + 2), 16);
  }
  return out;
}

async function getAssetCryptoKey() {
  if (prcAssetKeyPromise) return prcAssetKeyPromise;
  prcAssetKeyPromise = (async () => {
    const phrase = PRC_ASSET_KEY_PARTS.join('|');
    const enc = new TextEncoder().encode(phrase);
    const digest = await crypto.subtle.digest('SHA-256', enc);
    return crypto.subtle.importKey('raw', digest, { name: 'AES-GCM' }, false, ['decrypt']);
  })().catch((err) => {
    prcAssetKeyPromise = null;
    throw err;
  });
  return prcAssetKeyPromise;
}

async function ensurePackedAssetsLoaded() {
  if (prcAssetManifest && prcAssetBundleBytes) return true;
  const manifestUrl = safeGetURL(PRC_ASSET_MANIFEST_PATH);
  const bundleUrl = safeGetURL(PRC_ASSET_BUNDLE_PATH);
  if (!manifestUrl || !bundleUrl) return false;

  const [manifestResp, bundleResp] = await Promise.all([
    fetch(manifestUrl),
    fetch(bundleUrl)
  ]);
  if (!manifestResp.ok || !bundleResp.ok) return false;

  prcAssetManifest = await manifestResp.json();
  prcAssetBundleBytes = new Uint8Array(await bundleResp.arrayBuffer());
  return true;
}

async function getAssetURL(assetPath) {
  const name = normalizeAssetName(assetPath);
  if (!name) return '';
  if (prcAssetUrlCache.has(name)) return prcAssetUrlCache.get(name);

  try {
    const loaded = await ensurePackedAssetsLoaded();
    const record = loaded ? prcAssetManifest?.files?.[name] : null;
    if (record && prcAssetBundleBytes) {
      const offset = Number(record.offset) || 0;
      const length = Number(record.length) || 0;
      const mime = String(record.mime || 'image/png');
      if (length > 0 && offset >= 0 && offset + length <= prcAssetBundleBytes.length) {
        const cipherBytes = prcAssetBundleBytes.slice(offset, offset + length);
        const iv = hexToUint8Array(record.ivHex);
        const key = await getAssetCryptoKey();
        const plainBuffer = await crypto.subtle.decrypt(
          { name: 'AES-GCM', iv, tagLength: 128 },
          key,
          cipherBytes
        );
        const blobUrl = URL.createObjectURL(new Blob([plainBuffer], { type: mime }));
        prcAssetUrlCache.set(name, blobUrl);
        return blobUrl;
      }
    }
  } catch (err) {
    console.warn(`PRC: Failed to decrypt asset "${name}"`, err);
  }

  return safeGetURL(`assets/${name}`);
}

function safeSendMessage(msg, cb) {
  if (!isContextValid()) return;
  try {
    if (cb) chrome.runtime.sendMessage(msg, cb);
    else chrome.runtime.sendMessage(msg);
  } catch (e) {
    console.warn("PRC: sendMessage failed (context invalidated)");
  }
}

function safeStorage() {
  if (!isContextValid() || !chrome || !chrome.storage) return null;
  return chrome.storage.local;
}

function storageGetAsync(keys) {
  return new Promise((resolve) => {
    const storage = safeStorage();
    if (!storage) {
      resolve({});
      return;
    }
    storage.get(keys, (result) => resolve(result || {}));
  });
}

function storageSetAsync(payload) {
  return new Promise((resolve) => {
    const storage = safeStorage();
    if (!storage) {
      resolve(false);
      return;
    }
    storage.set(payload || {}, () => resolve(true));
  });
}

function requestMessageAsync(message) {
  return new Promise((resolve) => {
    safeSendMessage(message, (response) => resolve(response || null));
  });
}

async function getOrCreateUserId() {
  const result = await storageGetAsync([PRC_USER_ID_KEY]);
  if (result?.[PRC_USER_ID_KEY]) return result[PRC_USER_ID_KEY];
  const generated = (crypto && crypto.randomUUID)
    ? crypto.randomUUID()
    : `${Date.now()}-${Math.random().toString(36).slice(2, 12)}`;
  await storageSetAsync({ [PRC_USER_ID_KEY]: generated });
  return generated;
}

function getOrCreateUniqueUserIdLocal() {
  try {
    const current = String(localStorage.getItem(PRC_UNIQUE_USER_ID_LOCAL_KEY) || '').trim();
    if (current) return current.slice(0, 64);
    const generated = (crypto && crypto.randomUUID)
      ? crypto.randomUUID()
      : `u-${Date.now()}-${Math.random().toString(36).slice(2, 12)}`;
    localStorage.setItem(PRC_UNIQUE_USER_ID_LOCAL_KEY, generated);
    return generated.slice(0, 64);
  } catch (_) {
    const fallback = `u-${Date.now()}-${Math.random().toString(36).slice(2, 12)}`;
    return fallback.slice(0, 64);
  }
}

function simpleHash32(text) {
  const input = String(text || '');
  let hash = 2166136261;
  for (let i = 0; i < input.length; i++) {
    hash ^= input.charCodeAt(i);
    hash = Math.imul(hash, 16777619);
  }
  return (hash >>> 0).toString(16).padStart(8, '0');
}

function normalizeCloudRecoveryCodeInput(input) {
  const compact = String(input || '').toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 16);
  if (!compact) return '';
  const parts = compact.match(/.{1,4}/g);
  return Array.isArray(parts) ? parts.join('-') : compact;
}

function normalizeSealActivityPayload(raw) {
  const base = (raw && typeof raw === 'object') ? raw : {};
  const proposalsByMint = (base.proposalsByMint && typeof base.proposalsByMint === 'object') ? base.proposalsByMint : {};
  const grantsByMint = (base.grantsByMint && typeof base.grantsByMint === 'object') ? base.grantsByMint : {};
  return { proposalsByMint, grantsByMint };
}

function readCloudLocalCooldowns() {
  try {
    const raw = localStorage.getItem(PRC_CLOUD_LOCAL_COOLDOWN_STORAGE_KEY);
    const parsed = raw ? JSON.parse(raw) : {};
    return (parsed && typeof parsed === 'object') ? parsed : {};
  } catch (_) {
    return {};
  }
}

async function getCloudProfileMeta() {
  const result = await storageGetAsync([PRC_CLOUD_PROFILE_META_KEY, PRC_CLOUD_ONBOARDING_KEY]);
  const raw = (result?.[PRC_CLOUD_PROFILE_META_KEY] && typeof result[PRC_CLOUD_PROFILE_META_KEY] === 'object')
    ? result[PRC_CLOUD_PROFILE_META_KEY]
    : {};
  const onboardingDone = result?.[PRC_CLOUD_ONBOARDING_KEY] === true;
  return {
    schemaVersion: PRC_CLOUD_SCHEMA_VERSION,
    uniqueUserId: String(raw.uniqueUserId || '').trim().slice(0, 64) || null,
    recoveryCode: String(raw.recoveryCode || '').trim().slice(0, 32) || null,
    lastLocalHash: String(raw.lastLocalHash || '').trim() || null,
    lastLocalUpdatedAt: Math.max(0, Number(raw.lastLocalUpdatedAt) || 0),
    statsVersion: Math.max(0, Number(raw.statsVersion) || 0),
    statsUpdatedAt: Math.max(0, Number(raw.statsUpdatedAt) || 0),
    onboardingDone
  };
}

async function setCloudProfileMeta(patch) {
  const current = await getCloudProfileMeta();
  const next = {
    ...current,
    ...(patch && typeof patch === 'object' ? patch : {}),
    schemaVersion: PRC_CLOUD_SCHEMA_VERSION
  };
  await storageSetAsync({
    [PRC_CLOUD_PROFILE_META_KEY]: next,
    [PRC_CLOUD_ONBOARDING_KEY]: next.onboardingDone === true
  });
  return next;
}

async function markCloudStatsLocalUpdated() {
  const meta = await getCloudProfileMeta();
  await setCloudProfileMeta({
    ...meta,
    lastLocalUpdatedAt: Date.now()
  });
}

async function collectCloudStatsPayload() {
  const result = await storageGetAsync([
    PRC_FLEX_HISTORY_KEY,
    PRC_BADGE_STATE_KEY,
    PRC_SEAL_ACTIVITY_KEY,
    PRC_FLEX_HANDLE_KEY,
    PRC_HAT_BONUS_KEY,
    PRC_HAT_CLICK_COUNT_KEY
  ]);
  const flexHistory = Array.isArray(result?.[PRC_FLEX_HISTORY_KEY]) ? result[PRC_FLEX_HISTORY_KEY].slice(-500) : [];
  const badgeState = (result?.[PRC_BADGE_STATE_KEY] && typeof result[PRC_BADGE_STATE_KEY] === 'object') ? result[PRC_BADGE_STATE_KEY] : null;
  const sealActivity = normalizeSealActivityPayload(result?.[PRC_SEAL_ACTIVITY_KEY]);
  const flexHandle = String(result?.[PRC_FLEX_HANDLE_KEY] || '').trim().replace(/^@+/, '').slice(0, 32);
  const hatBonusPoints = Math.max(0, Number(result?.[PRC_HAT_BONUS_KEY]) || 0);
  const hatClickCount = Math.max(0, Number(result?.[PRC_HAT_CLICK_COUNT_KEY]) || 0);
  const localCooldowns = readCloudLocalCooldowns();
  return {
    schemaVersion: PRC_CLOUD_SCHEMA_VERSION,
    collectedAt: Date.now(),
    uniqueUserId: getOrCreateUniqueUserIdLocal(),
    stats: {
      flexHistory,
      badgeState,
      sealActivity,
      flexHandle,
      hatBonusPoints,
      hatClickCount,
      localCooldowns
    }
  };
}

async function applyCloudStatsPayload(payload) {
  if (!payload || typeof payload !== 'object') return false;
  const stats = (payload.stats && typeof payload.stats === 'object') ? payload.stats : {};
  const update = {};
  if (Array.isArray(stats.flexHistory)) update[PRC_FLEX_HISTORY_KEY] = stats.flexHistory.slice(-500);
  if (stats.badgeState && typeof stats.badgeState === 'object') update[PRC_BADGE_STATE_KEY] = stats.badgeState;
  if (stats.sealActivity && typeof stats.sealActivity === 'object') {
    update[PRC_SEAL_ACTIVITY_KEY] = normalizeSealActivityPayload(stats.sealActivity);
  }
  if (Object.prototype.hasOwnProperty.call(stats, 'flexHandle')) {
    update[PRC_FLEX_HANDLE_KEY] = String(stats.flexHandle || '').trim().replace(/^@+/, '').slice(0, 32);
  }
  if (Object.prototype.hasOwnProperty.call(stats, 'hatBonusPoints')) {
    update[PRC_HAT_BONUS_KEY] = Math.max(0, Number(stats.hatBonusPoints) || 0);
  }
  if (Object.prototype.hasOwnProperty.call(stats, 'hatClickCount')) {
    update[PRC_HAT_CLICK_COUNT_KEY] = Math.max(0, Number(stats.hatClickCount) || 0);
  }
  if (Object.keys(update).length > 0) {
    await storageSetAsync(update);
  }
  if (stats.localCooldowns && typeof stats.localCooldowns === 'object') {
    try {
      localStorage.setItem(PRC_CLOUD_LOCAL_COOLDOWN_STORAGE_KEY, JSON.stringify(stats.localCooldowns));
    } catch (_) { }
  }
  return true;
}

async function updateRecoveryCodeFieldUI(overrideMeta = null) {
  const input = document.getElementById('prc-recovery-code');
  const meta = overrideMeta || await getCloudProfileMeta();
  if (input) {
    input.value = meta?.recoveryCode || '';
    input.placeholder = meta?.recoveryCode ? '' : 'Generating...';
  }
}

function bindRecoveryCodeFieldActions() {
  const input = document.getElementById('prc-recovery-code');
  const toggle = document.getElementById('prc-recovery-toggle');
  const copyBtn = document.getElementById('prc-recovery-copy');
  if (!input || !toggle || !copyBtn) return;
  if (toggle.dataset.bound !== '1') {
    toggle.onclick = () => {
      const visible = input.type === 'text';
      input.type = visible ? 'password' : 'text';
      toggle.textContent = visible ? 'Show' : 'Hide';
      toggle.title = visible ? 'Show code' : 'Hide code';
    };
    toggle.dataset.bound = '1';
  }
  if (copyBtn.dataset.bound !== '1') {
    copyBtn.onclick = async () => {
      const code = String(input.value || '').trim();
      if (!code) return;
      try {
        await navigator.clipboard.writeText(code);
        const old = copyBtn.textContent;
        copyBtn.textContent = 'Copied';
        setTimeout(() => { copyBtn.textContent = old; }, 1200);
      } catch (_) { }
    };
    copyBtn.dataset.bound = '1';
  }
}

async function syncCloudStatsProfile(options = {}) {
  if (prcCloudSyncInFlight) return prcCloudSyncInFlight;
  prcCloudSyncInFlight = (async () => {
    const forceSync = options.force === true;
    const uniqueUserId = getOrCreateUniqueUserIdLocal();
    const userId = await getOrCreateUserId();
    const payload = await collectCloudStatsPayload();
    const payloadStr = JSON.stringify(payload);
    const payloadHash = `${payloadStr.length}:${simpleHash32(payloadStr)}`;
    const meta = await getCloudProfileMeta();
    const localChanged = meta.lastLocalHash !== payloadHash;
    const localUpdatedAt = forceSync
      ? Math.max(Date.now(), meta.lastLocalUpdatedAt || 0)
      : (localChanged ? Date.now() : Math.max(0, Number(meta.lastLocalUpdatedAt) || 0));

    const response = await requestMessageAsync({
      action: 'syncCloudStats',
      userId,
      uniqueUserId,
      clientFingerprint: getClientFingerprint(),
      statsPayload: payload,
      statsUpdatedAt: localUpdatedAt
    });

    if (!response || response.success !== true) {
      return { success: false, message: response?.message || 'Cloud sync failed' };
    }

    const remoteUniqueUserId = String(response.uniqueUserId || '').trim().slice(0, 64);
    if (remoteUniqueUserId && remoteUniqueUserId !== uniqueUserId) {
      try { localStorage.setItem(PRC_UNIQUE_USER_ID_LOCAL_KEY, remoteUniqueUserId); } catch (_) { }
    }

    let finalHash = payloadHash;
    let finalLocalUpdatedAt = localUpdatedAt;
    const shouldPull = !!(response.statsPayload && typeof response.statsPayload === 'object')
      && (Number(response.statsUpdatedAt) || 0) >= localUpdatedAt
      && response.decision !== 'pushed_local';

    if (shouldPull) {
      const applied = await applyCloudStatsPayload(response.statsPayload);
      if (applied) {
        const pulledPayload = await collectCloudStatsPayload();
        const pulledStr = JSON.stringify(pulledPayload);
        finalHash = `${pulledStr.length}:${simpleHash32(pulledStr)}`;
        finalLocalUpdatedAt = Math.max(0, Number(response.statsUpdatedAt) || Date.now());
      }
    }

    const nextMeta = await setCloudProfileMeta({
      uniqueUserId: remoteUniqueUserId || uniqueUserId,
      recoveryCode: String(response.recoveryCode || meta.recoveryCode || '').trim().slice(0, 32) || null,
      lastLocalHash: finalHash,
      lastLocalUpdatedAt: finalLocalUpdatedAt,
      statsVersion: Math.max(0, Number(response.statsVersion) || 0),
      statsUpdatedAt: Math.max(0, Number(response.statsUpdatedAt) || meta.statsUpdatedAt || 0)
    });
    await updateRecoveryCodeFieldUI(nextMeta);
    return { success: true, decision: response.decision || 'up_to_date', meta: nextMeta };
  })().finally(() => {
    prcCloudSyncInFlight = null;
  });
  return prcCloudSyncInFlight;
}

function promptCloudRestoreChoice(previousCode = '', errorMessage = '') {
  return new Promise((resolve) => {
    const existing = document.getElementById('prc-cloud-restore-modal');
    if (existing) existing.remove();
    const safeCode = normalizeCloudRecoveryCodeInput(previousCode);
    const overlay = document.createElement('div');
    overlay.id = 'prc-cloud-restore-modal';
    overlay.className = 'prc-cloud-restore-modal';
    overlay.innerHTML = `
      <div class="prc-cloud-restore-card">
        <div class="prc-cloud-restore-title">Restore your Hermeto stats?</div>
        <div class="prc-cloud-restore-subtitle">If you already have a backup code, enter it to recover your profile. If not, start fresh and we will generate a new code.</div>
        <input id="prc-cloud-restore-input" class="prc-cloud-restore-input" type="text" maxlength="24" placeholder="XXXX-XXXX-XXXX-XXXX" value="${safeCode}">
        <div id="prc-cloud-restore-error" class="prc-cloud-restore-error" style="${errorMessage ? '' : 'display:none;'}">${errorMessage || ''}</div>
        <div class="prc-cloud-restore-actions">
          <button id="prc-cloud-restore-apply" class="prc-cloud-restore-btn yes">Restore</button>
          <button id="prc-cloud-restore-skip" class="prc-cloud-restore-btn no">Start Fresh</button>
        </div>
      </div>
    `;
    const root = document.getElementById('padre-rug-check-ui') || document.body;
    root.appendChild(overlay);
    const input = overlay.querySelector('#prc-cloud-restore-input');
    const restoreBtn = overlay.querySelector('#prc-cloud-restore-apply');
    const skipBtn = overlay.querySelector('#prc-cloud-restore-skip');
    const errorEl = overlay.querySelector('#prc-cloud-restore-error');

    const close = (result) => {
      overlay.remove();
      resolve(result);
    };
    if (input) {
      input.focus();
      input.oninput = () => {
        const normalized = normalizeCloudRecoveryCodeInput(input.value);
        input.value = normalized;
      };
      input.onkeydown = (event) => {
        if (event.key === 'Enter') restoreBtn?.click();
      };
    }
    if (restoreBtn) {
      restoreBtn.onclick = () => {
        const code = normalizeCloudRecoveryCodeInput(input?.value || '');
        if (code.replace(/-/g, '').length !== 16) {
          if (errorEl) {
            errorEl.textContent = 'Please enter a valid 16-character backup code.';
            errorEl.style.display = 'block';
          }
          return;
        }
        close({ mode: 'restore', code });
      };
    }
    if (skipBtn) {
      skipBtn.onclick = () => close({ mode: 'new' });
    }
  });
}

function promptFreshStartCodeNotice(recoveryCode) {
  return new Promise((resolve) => {
    const existing = document.getElementById('prc-cloud-restore-modal');
    if (existing) existing.remove();
    const safeCode = normalizeCloudRecoveryCodeInput(recoveryCode);
    const overlay = document.createElement('div');
    overlay.id = 'prc-cloud-restore-modal';
    overlay.className = 'prc-cloud-restore-modal';
    overlay.innerHTML = `
      <div class="prc-cloud-restore-card">
        <div class="prc-cloud-restore-title">This is your backup code</div>
        <div class="prc-cloud-restore-subtitle">Use this code to recover your profile anytime.</div>
        <div class="prc-cloud-code-box">
          <input id="prc-cloud-code-value" class="prc-cloud-restore-input" type="text" readonly value="${safeCode}">
        </div>
        <div class="prc-cloud-code-actions">
          <button id="prc-cloud-code-copy" class="prc-cloud-restore-btn no">Copy</button>
          <button id="prc-cloud-code-ok" class="prc-cloud-restore-btn yes">OK</button>
        </div>
        <div class="prc-cloud-restore-subtitle prc-cloud-code-footer">Save this code to recover your profile, you can view this code on the settings section too. Let's start the hunt!</div>
      </div>
    `;
    const root = document.getElementById('padre-rug-check-ui') || document.body;
    root.appendChild(overlay);

    const copyBtn = overlay.querySelector('#prc-cloud-code-copy');
    const okBtn = overlay.querySelector('#prc-cloud-code-ok');
    const valueEl = overlay.querySelector('#prc-cloud-code-value');

    const close = () => {
      overlay.remove();
      resolve(true);
    };

    if (copyBtn) {
      copyBtn.onclick = async () => {
        const code = String(valueEl?.value || '').trim();
        if (!code) return;
        try {
          await navigator.clipboard.writeText(code);
          const old = copyBtn.textContent;
          copyBtn.textContent = 'Copied';
          setTimeout(() => { copyBtn.textContent = old; }, 1200);
        } catch (_) { }
      };
    }
    if (okBtn) okBtn.onclick = close;
  });
}

async function restoreCloudStatsByCode(code) {
  const userId = await getOrCreateUserId();
  const response = await requestMessageAsync({
    action: 'restoreCloudStats',
    userId,
    recoveryCode: normalizeCloudRecoveryCodeInput(code),
    clientFingerprint: getClientFingerprint()
  });
  if (!response || response.success !== true || !response.uniqueUserId) {
    return { success: false, message: response?.message || 'Invalid backup code' };
  }
  try {
    localStorage.setItem(PRC_UNIQUE_USER_ID_LOCAL_KEY, String(response.uniqueUserId).trim().slice(0, 64));
  } catch (_) { }
  if (response.statsPayload && typeof response.statsPayload === 'object') {
    await applyCloudStatsPayload(response.statsPayload);
  }
  const collected = await collectCloudStatsPayload();
  const collectedStr = JSON.stringify(collected);
  const collectedHash = `${collectedStr.length}:${simpleHash32(collectedStr)}`;
  const meta = await setCloudProfileMeta({
    uniqueUserId: String(response.uniqueUserId || '').trim().slice(0, 64) || null,
    recoveryCode: String(response.recoveryCode || code || '').trim().slice(0, 32) || null,
    lastLocalHash: collectedHash,
    lastLocalUpdatedAt: Math.max(0, Number(response.statsUpdatedAt) || Date.now()),
    statsVersion: Math.max(0, Number(response.statsVersion) || 0),
    statsUpdatedAt: Math.max(0, Number(response.statsUpdatedAt) || 0),
    onboardingDone: true
  });
  await updateRecoveryCodeFieldUI(meta);
  return { success: true };
}

async function ensureCloudProfileReady(force = false) {
  if (prcCloudInitPromise && !force) return prcCloudInitPromise;
  prcCloudInitPromise = (async () => {
    const meta = await getCloudProfileMeta();
    let alreadySyncedInOnboarding = false;
    let onboardingCompletedNow = meta.onboardingDone === true;
    if (!meta.onboardingDone) {
      let restored = false;
      let choseFreshStart = false;
      let cancelled = false;
      let lastCode = '';
      let lastError = '';
      for (let i = 0; i < 3 && !restored; i++) {
        const choice = await promptCloudRestoreChoice(lastCode, lastError);
        if (!choice) {
          cancelled = true;
          break;
        }
        if (choice.mode === 'new') {
          choseFreshStart = true;
          break;
        }
        if (choice.mode !== 'restore') break;
        lastCode = choice.code || '';
        const restoreResult = await restoreCloudStatsByCode(choice.code);
        if (restoreResult.success) {
          restored = true;
          lastError = '';
        } else {
          lastError = restoreResult.message || 'Could not restore with that code.';
        }
      }
      if (restored) {
        onboardingCompletedNow = true;
      }
      if (choseFreshStart && !restored) {
        let firstMeta = await getCloudProfileMeta();
        let codeReady = !!String(firstMeta?.recoveryCode || '').trim();
        for (let attempt = 0; attempt < 3 && !codeReady; attempt++) {
          const firstSync = await syncCloudStatsProfile({ force: true });
          if (firstSync?.success) {
            alreadySyncedInOnboarding = true;
            firstMeta = firstSync.meta || await getCloudProfileMeta();
            codeReady = !!String(firstMeta?.recoveryCode || '').trim();
            if (codeReady) break;
          }
          await new Promise((resolve) => setTimeout(resolve, 250));
        }
        await updateRecoveryCodeFieldUI(firstMeta);
        const displayCode = String(firstMeta?.recoveryCode || '').trim() || getOrCreateUniqueUserIdLocal();
        await promptFreshStartCodeNotice(displayCode);
        onboardingCompletedNow = true;
      }
      if (cancelled && !restored && !choseFreshStart) {
        onboardingCompletedNow = false;
      }
      await setCloudProfileMeta({ onboardingDone: onboardingCompletedNow === true });
      if (!restored && onboardingCompletedNow) {
        await updateRecoveryCodeFieldUI();
      }
    }
    if (!alreadySyncedInOnboarding) {
      const syncResult = await syncCloudStatsProfile({ force: true });
      if (!syncResult?.success) {
        console.warn('PRC: cloud profile sync init failed:', syncResult?.message || 'unknown');
      }
    }
    await updateRecoveryCodeFieldUI();
    return true;
  })();
  try {
    return await prcCloudInitPromise;
  } catch (err) {
    prcCloudInitPromise = null;
    throw err;
  }
}

async function getFlexHistory() {
  const result = await storageGetAsync([PRC_FLEX_HISTORY_KEY]);
  return Array.isArray(result?.[PRC_FLEX_HISTORY_KEY]) ? result[PRC_FLEX_HISTORY_KEY] : [];
}

async function appendFlexHistory(entry) {
  const current = await getFlexHistory();
  const next = [...current, entry].slice(-500);
  await storageSetAsync({ [PRC_FLEX_HISTORY_KEY]: next });
  await markCloudStatsLocalUpdated();
  return next;
}

async function getSealActivity() {
  const result = await storageGetAsync([PRC_SEAL_ACTIVITY_KEY]);
  const raw = result?.[PRC_SEAL_ACTIVITY_KEY];
  const base = (raw && typeof raw === 'object') ? raw : {};
  const proposalsByMint = (base.proposalsByMint && typeof base.proposalsByMint === 'object') ? base.proposalsByMint : {};
  const grantsByMint = (base.grantsByMint && typeof base.grantsByMint === 'object') ? base.grantsByMint : {};
  return { proposalsByMint, grantsByMint };
}

function getSealActivityCounts(activity) {
  const proposals = Object.keys(activity?.proposalsByMint || {}).length;
  const grants = Object.keys(activity?.grantsByMint || {}).length;
  return {
    proposals: Math.max(0, Number(proposals) || 0),
    grants: Math.max(0, Number(grants) || 0)
  };
}

async function recordSealProposalMint(mint) {
  const m = String(mint || '').trim();
  if (!m) return false;
  const activity = await getSealActivity();
  if (activity.proposalsByMint[m]) return false;
  activity.proposalsByMint[m] = Date.now();
  await storageSetAsync({ [PRC_SEAL_ACTIVITY_KEY]: activity });
  await markCloudStatsLocalUpdated();
  return true;
}

async function recordSealGrantIfEligible(mint) {
  const m = String(mint || '').trim();
  if (!m) return false;
  const activity = await getSealActivity();
  if (!activity.proposalsByMint[m]) return false;
  if (activity.grantsByMint[m]) return false;
  activity.grantsByMint[m] = Date.now();
  await storageSetAsync({ [PRC_SEAL_ACTIVITY_KEY]: activity });
  await markCloudStatsLocalUpdated();
  return true;
}

async function getHatBonusPoints() {
  const result = await storageGetAsync([PRC_HAT_BONUS_KEY]);
  return Math.max(0, Number(result?.[PRC_HAT_BONUS_KEY]) || 0);
}

async function getHatClickCount() {
  const result = await storageGetAsync([PRC_HAT_CLICK_COUNT_KEY, PRC_HAT_BONUS_KEY]);
  const explicitCount = Number(result?.[PRC_HAT_CLICK_COUNT_KEY]);
  if (Number.isFinite(explicitCount) && explicitCount >= 0) {
    return Math.floor(explicitCount);
  }
  // Backward-compatible migration: infer historical clicks from bonus points.
  const bonus = Math.max(0, Number(result?.[PRC_HAT_BONUS_KEY]) || 0);
  return Math.floor(bonus / Math.max(1, PRC_HAT_BONUS_PER_CLICK));
}

async function addHatBonusPoints(delta) {
  const current = await getHatBonusPoints();
  const next = Math.max(0, current + (Number(delta) || 0));
  await storageSetAsync({ [PRC_HAT_BONUS_KEY]: next });
  await markCloudStatsLocalUpdated();
  return next;
}

async function incrementHatClickCount(delta = 1) {
  const current = await getHatClickCount();
  const next = Math.max(0, current + Math.max(0, Number(delta) || 0));
  await storageSetAsync({ [PRC_HAT_CLICK_COUNT_KEY]: next });
  await markCloudStatsLocalUpdated();
  return next;
}

function getUnlockedBadges(stats, flexCount) {
  const flags = Number(stats?.flagsCount) || 0;
  const hunts = Number(stats?.huntedRuggers) || 0;
  const flexes = Number(flexCount) || 0;
  const hatClicks = Number(stats?.hatClicks) || 0;
  const sealProposals = Number(stats?.sealProposals) || 0;
  const sealGrants = Number(stats?.sealGrants) || 0;

  return prcBadgeDefinitions.filter((badge) => {
    if (badge.type === 'flags') return flags >= badge.threshold;
    if (badge.type === 'hunt') return hunts >= badge.threshold;
    if (badge.type === 'flex') return flexes >= badge.threshold;
    if (badge.type === 'hat_clicks') return hatClicks >= badge.threshold;
    if (badge.type === 'seal_proposals') return sealProposals >= badge.threshold;
    if (badge.type === 'seal_grants') return sealGrants >= badge.threshold;
    return false;
  });
}

function normalizeFlexLevelEntry(entry, fallback = {}, idx = 0) {
  const levelRaw = Number.parseInt(entry?.level, 10);
  const level = Number.isFinite(levelRaw) ? Math.max(0, levelRaw) : Math.max(0, Number.parseInt(fallback?.level, 10) || idx);
  const scoreRaw = Number.parseInt(entry?.score, 10);
  const score = Number.isFinite(scoreRaw) ? Math.max(0, scoreRaw) : Math.max(0, Number.parseInt(fallback?.score, 10) || 0);
  const title = String(entry?.title || fallback?.title || `LEVEL ${level}`).trim().slice(0, 96);
  const imageRaw = Number.parseInt(entry?.image, 10);
  const image = Number.isFinite(imageRaw) ? Math.max(0, imageRaw) : Math.max(0, Number.parseInt(fallback?.image, 10) || level);
  return { level, score, title, image };
}

function applyServerFlexLevels(levels) {
  if (!Array.isArray(levels) || levels.length === 0) {
    prcFlexLevels = PRC_FLEX_LEVELS_FALLBACK.map((lv) => ({ ...lv }));
    return;
  }
  const byLevel = new Map();
  levels.forEach((entry, idx) => {
    const normalized = normalizeFlexLevelEntry(entry, {}, idx);
    byLevel.set(normalized.level, normalized);
  });

  const merged = PRC_FLEX_LEVELS_FALLBACK.map((fallback, idx) => {
    const fromServer = byLevel.get(fallback.level);
    return normalizeFlexLevelEntry(fromServer || fallback, fallback, idx);
  });

  const fallbackLevels = new Set(PRC_FLEX_LEVELS_FALLBACK.map((lv) => lv.level));
  levels.forEach((entry, idx) => {
    const normalized = normalizeFlexLevelEntry(entry, {}, idx);
    if (fallbackLevels.has(normalized.level)) return;
    merged.push(normalized);
  });

  prcFlexLevels = merged.sort((a, b) => a.level - b.level || a.score - b.score);
}

function getCurrentFlexLevelByScore(scoreValue) {
  const score = Math.max(0, Number(scoreValue) || 0);
  const levels = Array.isArray(prcFlexLevels) && prcFlexLevels.length > 0
    ? prcFlexLevels
    : PRC_FLEX_LEVELS_FALLBACK;
  let current = levels[0];
  for (const lv of levels) {
    if (score >= (Number(lv.score) || 0)) current = lv;
    else break;
  }
  return current || levels[0] || { level: 0, score: 0, title: 'JUST STARTING', image: 0 };
}

function getNextFlexLevelByScore(scoreValue) {
  const score = Math.max(0, Number(scoreValue) || 0);
  const levels = Array.isArray(prcFlexLevels) && prcFlexLevels.length > 0
    ? prcFlexLevels
    : PRC_FLEX_LEVELS_FALLBACK;
  for (const lv of levels) {
    if (score < (Number(lv.score) || 0)) return lv;
  }
  return null;
}

function getFlexScoreProgress(scoreValue) {
  const score = Math.max(0, Number(scoreValue) || 0);
  const current = getCurrentFlexLevelByScore(score);
  const next = getNextFlexLevelByScore(score);
  if (!next) {
    return {
      pct: 100,
      current,
      next: null,
      remaining: 0,
      complete: true
    };
  }
  const from = Math.max(0, Number(current?.score) || 0);
  const to = Math.max(from + 1, Number(next?.score) || (from + 1));
  const pct = Math.max(0, Math.min(100, ((score - from) / (to - from)) * 100));
  return {
    pct,
    current,
    next,
    remaining: Math.max(0, to - score),
    complete: false
  };
}

function getMetricProgressFromTiers(value, tiers = [1, 5, 10, 25, 50, 100]) {
  const current = Math.max(0, Number(value) || 0);
  const ordered = (Array.isArray(tiers) ? tiers : [])
    .map((n) => Math.max(0, Number(n) || 0))
    .filter((n) => n > 0)
    .sort((a, b) => a - b);
  if (ordered.length === 0) {
    return { pct: 100, current, target: current, complete: true };
  }
  for (const t of ordered) {
    if (current < t) {
      const denom = Math.max(1, t);
      return { pct: Math.max(0, Math.min(100, (current / denom) * 100)), current, target: t, complete: false };
    }
  }
  return { pct: 100, current, target: ordered[ordered.length - 1], complete: true };
}

async function getAvatarByScore(scoreValue) {
  const current = getCurrentFlexLevelByScore(scoreValue);
  const imageIndex = Math.max(0, Number(current?.image) || 0);
  return getAssetURL(`assets/${imageIndex}.png`);
}

function getFlexTitleByScore(scoreValue) {
  const current = getCurrentFlexLevelByScore(scoreValue);
  return String(current?.title || 'JUST STARTING');
}

function computeUnlockedBadgePoints(unlockedBadges) {
  if (!Array.isArray(unlockedBadges) || unlockedBadges.length === 0) return 0;
  return unlockedBadges.reduce((sum, badge) => {
    const points = Math.max(0, Number(badge?.points) || 0);
    return sum + points;
  }, 0);
}

function computeScore(stats, badgeCount = 0, scorePerFlag = 10, scorePerHuntedRugger = 100, scorePerBadge = 100, hatBonusPoints = 0, scorePerSealProposal = 10, scorePerSealGrant = 100) {
  const flagsCount = Number(stats?.flagsCount) || 0;
  const huntedRuggers = Number(stats?.huntedRuggers) || 0;
  const sealProposals = Number(stats?.sealProposals) || 0;
  const sealGrants = Number(stats?.sealGrants) || 0;
  const perFlag = Math.max(0, Number(scorePerFlag) || 10);
  const perHunted = Math.max(0, Number(scorePerHuntedRugger) || 100);
  const perBadge = Math.max(0, Number(scorePerBadge) || 100);
  const perProposal = Math.max(0, Number(scorePerSealProposal) || 10);
  const perGrant = Math.max(0, Number(scorePerSealGrant) || 100);
  const badges = Math.max(0, Number(badgeCount) || 0);
  const hasExplicitBadgePoints = stats && Object.prototype.hasOwnProperty.call(stats, 'badgePoints');
  const explicitBadgePoints = Math.max(0, Number(stats?.badgePoints) || 0);
  const badgePoints = hasExplicitBadgePoints ? explicitBadgePoints : (badges * perBadge);
  const bonus = Math.max(0, Number(hatBonusPoints) || 0);
  return (flagsCount * perFlag) + (huntedRuggers * perHunted) + (sealProposals * perProposal) + (sealGrants * perGrant) + badgePoints + bonus;
}

function getNextBadgeProgress(type, value) {
  const current = Math.max(0, Number(value) || 0);
  const thresholds = prcBadgeDefinitions
    .filter((b) => b.type === type)
    .map((b) => Math.max(0, Number(b.threshold) || 0))
    .sort((a, b) => a - b);
  if (thresholds.length === 0) return { pct: 100, current, target: current, complete: true };

  for (const t of thresholds) {
    if (current < t) {
      const denom = Math.max(1, t);
      return {
        pct: Math.max(0, Math.min(100, (current / denom) * 100)),
        current,
        target: t,
        complete: false
      };
    }
  }

  return { pct: 100, current, target: thresholds[thresholds.length - 1], complete: true };
}

function hasBadgeType(type) {
  return prcBadgeDefinitions.some((b) => String(b?.type || '') === String(type || ''));
}

function getBadgeFxTheme(badgeId) {
  const themes = {
    a1: { core: 'rgba(96, 210, 255, 0.56)', mid: 'rgba(84, 162, 255, 0.28)', particle: 'rgba(96, 210, 255, 0.95)' },
    a2: { core: 'rgba(120, 255, 182, 0.56)', mid: 'rgba(58, 224, 163, 0.28)', particle: 'rgba(120, 255, 182, 0.95)' },
    a3: { core: 'rgba(255, 88, 88, 0.56)', mid: 'rgba(255, 146, 84, 0.3)', particle: 'rgba(255, 112, 96, 0.95)' },
    b1: { core: 'rgba(255, 214, 102, 0.58)', mid: 'rgba(255, 170, 82, 0.28)', particle: 'rgba(255, 214, 102, 0.95)' },
    b2: { core: 'rgba(255, 156, 96, 0.58)', mid: 'rgba(255, 109, 84, 0.3)', particle: 'rgba(255, 156, 96, 0.95)' },
    b3: { core: 'rgba(186, 126, 255, 0.58)', mid: 'rgba(126, 84, 255, 0.3)', particle: 'rgba(198, 148, 255, 0.95)' },
    b4: { core: 'rgba(255, 112, 205, 0.58)', mid: 'rgba(255, 80, 144, 0.3)', particle: 'rgba(255, 132, 212, 0.95)' },
    b5: { core: 'rgba(255, 90, 132, 0.58)', mid: 'rgba(255, 178, 89, 0.32)', particle: 'rgba(255, 112, 148, 0.95)' },
    c1: { core: 'rgba(108, 168, 255, 0.58)', mid: 'rgba(95, 232, 255, 0.28)', particle: 'rgba(128, 190, 255, 0.95)' },
    c2: { core: 'rgba(154, 126, 255, 0.58)', mid: 'rgba(108, 214, 255, 0.28)', particle: 'rgba(175, 148, 255, 0.95)' },
    c3: { core: 'rgba(255, 214, 126, 0.6)', mid: 'rgba(255, 156, 88, 0.3)', particle: 'rgba(255, 222, 156, 0.98)' },
    d1: { core: 'rgba(112, 225, 255, 0.58)', mid: 'rgba(85, 169, 255, 0.3)', particle: 'rgba(122, 226, 255, 0.98)' },
    d2: { core: 'rgba(124, 255, 214, 0.58)', mid: 'rgba(74, 224, 171, 0.3)', particle: 'rgba(142, 255, 224, 0.98)' },
    d3: { core: 'rgba(255, 215, 108, 0.62)', mid: 'rgba(255, 148, 74, 0.32)', particle: 'rgba(255, 230, 146, 0.99)' },
    e1: { core: 'rgba(191, 161, 255, 0.58)', mid: 'rgba(126, 96, 255, 0.3)', particle: 'rgba(205, 186, 255, 0.98)' },
    e2: { core: 'rgba(255, 145, 205, 0.58)', mid: 'rgba(255, 90, 166, 0.32)', particle: 'rgba(255, 175, 221, 0.98)' },
    e3: { core: 'rgba(255, 118, 140, 0.6)', mid: 'rgba(255, 196, 108, 0.34)', particle: 'rgba(255, 140, 166, 0.99)' }
  };
  return themes[badgeId] || { core: 'rgba(125, 82, 255, 0.46)', mid: 'rgba(88, 255, 234, 0.2)', particle: 'rgba(188, 163, 255, 0.95)' };
}

function dataUrlToFile(dataUrl, filename) {
  const parts = dataUrl.split(',');
  const mime = (parts[0].match(/:(.*?);/) || [])[1] || 'image/png';
  const binary = atob(parts[1] || '');
  const len = binary.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
  return new File([bytes], filename, { type: mime });
}

function loadImageAsync(src) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = src;
  });
}

function drawImageContain(ctx, img, x, y, w, h) {
  const iw = img.naturalWidth || img.width || w;
  const ih = img.naturalHeight || img.height || h;
  if (!iw || !ih) return;
  const scale = Math.min(w / iw, h / ih);
  const dw = iw * scale;
  const dh = ih * scale;
  const dx = x + (w - dw) / 2;
  const dy = y + (h - dh) / 2;
  ctx.drawImage(img, dx, dy, dw, dh);
}

function drawImageCover(ctx, img, x, y, w, h) {
  const iw = img.naturalWidth || img.width || w;
  const ih = img.naturalHeight || img.height || h;
  if (!iw || !ih) return;
  const scale = Math.max(w / iw, h / ih);
  const dw = iw * scale;
  const dh = ih * scale;
  const dx = x + (w - dw) / 2;
  const dy = y + (h - dh) / 2;
  ctx.drawImage(img, dx, dy, dw, dh);
}

function setPanelScreen(target) {
  const mainScreen = document.getElementById('prc-screen-main');
  const statsScreen = document.getElementById('prc-screen-stats');
  if (!mainScreen || !statsScreen) return;
  const showStats = target === 'stats';
  const from = showStats ? mainScreen : statsScreen;
  const to = showStats ? statsScreen : mainScreen;
  if (from === to) return;

  from.classList.add('prc-screen-fading');
  from.classList.remove('prc-screen-active');

  setTimeout(() => {
    from.style.display = 'none';
    from.classList.remove('prc-screen-fading');
    to.style.display = 'block';
    requestAnimationFrame(() => {
      to.classList.add('prc-screen-active');
    });
  }, 160);
}

let statsRewardGlowTimeout = null;
function triggerStatsRewardGlow(cycles = 3) {
  const openStatsBtn = document.getElementById('prc-open-stats');
  const mainScreen = document.getElementById('prc-screen-main');
  if (!openStatsBtn || !mainScreen || mainScreen.style.display === 'none') return;
  const loops = Math.max(1, Number(cycles) || 3);
  openStatsBtn.classList.remove('prc-reward-glow');
  openStatsBtn.style.setProperty('--prc-reward-cycles', String(loops));
  void openStatsBtn.offsetWidth;
  openStatsBtn.classList.add('prc-reward-glow');
  if (statsRewardGlowTimeout) clearTimeout(statsRewardGlowTimeout);
  statsRewardGlowTimeout = setTimeout(() => {
    openStatsBtn.classList.remove('prc-reward-glow');
    statsRewardGlowTimeout = null;
  }, (loops * 900) + 140);
}

function refreshStatsIfVisible() {
  const statsScreen = document.getElementById('prc-screen-stats');
  if (!statsScreen || statsScreen.style.display === 'none') return;
  fetchUserStatsSnapshot().then((snapshot) => {
    lastStatsSnapshot = snapshot;
    renderStatsUI(snapshot);
  });
}

async function fetchUserStatsSnapshot() {
  await ensureCloudProfileReady();
  await syncCloudStatsProfile();
  const userId = await getOrCreateUserId();
  const uniqueUserId = getOrCreateUniqueUserIdLocal();
  const response = await requestMessageAsync({
    action: 'getUserStats',
    userId,
    uniqueUserId,
    clientFingerprint: getClientFingerprint()
  });

  const safeResponse = response && typeof response === 'object' ? response : {};
  applyServerBadgeConfig(safeResponse.badgeConfig);
  applyServerFlexLevels(safeResponse.flexLevels);
  const scorePerFlag = Math.max(0, Number(safeResponse.scorePerFlag) || 10);
  const scorePerHuntedRugger = Math.max(0, Number(safeResponse.scorePerHuntedRugger) || 100);
  const scorePerHatClick = Math.max(0, Number(safeResponse.scorePerHatClick) || PRC_HAT_BONUS_PER_CLICK);
  const scorePerBadge = 100;
  const scorePerSealProposal = Math.max(0, Number(safeResponse.scorePerSealProposal) || 10);
  const scorePerSealGrant = Math.max(0, Number(safeResponse.scorePerSealGrant) || 100);
  prcHatBonusPerClick = scorePerHatClick;
  const hatBonusPoints = await getHatBonusPoints();
  const hatClicks = await getHatClickCount();
  const sealActivity = await getSealActivity();
  const sealCounts = getSealActivityCounts(sealActivity);
  const flagsCount = Number(safeResponse.flagsCount) || 0;
  const huntedRuggers = Number(safeResponse.huntedRuggers) || 0;
  const backendSealProposals = Number(safeResponse.sealProposals);
  const backendSealGrants = Number(safeResponse.sealGrants);
  const sealProposals = Number.isFinite(backendSealProposals) ? Math.max(0, backendSealProposals) : sealCounts.proposals;
  const sealGrants = Number.isFinite(backendSealGrants) ? Math.max(0, backendSealGrants) : sealCounts.grants;
  const stats = {
    flagsCount,
    huntedRuggers,
    sealProposals,
    sealGrants,
    score: 0,
    scorePerFlag,
    scorePerHuntedRugger,
    scorePerHatClick,
    scorePerBadge,
    scorePerSealProposal,
    scorePerSealGrant,
    hatBonusPoints,
    hatClicks
  };
  const flexHistory = await getFlexHistory();
  const flexCount = flexHistory.length;
  const unlocked = getUnlockedBadges(stats, flexCount);
  stats.badgePoints = computeUnlockedBadgePoints(unlocked);
  stats.score = computeScore(
    stats,
    unlocked.length,
    scorePerFlag,
    scorePerHuntedRugger,
    scorePerBadge,
    hatBonusPoints,
    scorePerSealProposal,
    scorePerSealGrant
  );

  await storageSetAsync({
    [PRC_BADGE_STATE_KEY]: {
      updatedAt: Date.now(),
      stats,
      flexCount,
      unlockedBadgeIds: unlocked.map((b) => b.id)
    }
  });

  return {
    stats,
    flexCount,
    unlockedBadges: unlocked
  };
}

function renderStatsUI(snapshot) {
  if (!snapshot) return;
  const flagsEl = document.getElementById('prc-stats-flags');
  const huntsEl = document.getElementById('prc-stats-hunts');
  const proposalsEl = document.getElementById('prc-stats-seal-proposals');
  const grantsEl = document.getElementById('prc-stats-seal-grants');
  const scoreEl = document.getElementById('prc-stats-score');
  const flagsProgressEl = document.getElementById('prc-stats-flags-progress');
  const huntsProgressEl = document.getElementById('prc-stats-hunts-progress');
  const proposalsProgressEl = document.getElementById('prc-stats-seal-proposals-progress');
  const grantsProgressEl = document.getElementById('prc-stats-seal-grants-progress');
  const scoreProgressEl = document.getElementById('prc-stats-score-progress');
  const scoreNextEl = document.getElementById('prc-stats-score-next');
  const avatarEl = document.getElementById('prc-stats-avatar');
  const titlesEl = document.getElementById('prc-earned-titles');
  const galleryEl = document.getElementById('prc-badges-gallery');
  const flexBtn = document.getElementById('prc-flex-btn');
  if (!flagsEl || !huntsEl || !proposalsEl || !grantsEl || !scoreEl || !flagsProgressEl || !huntsProgressEl || !proposalsProgressEl || !grantsProgressEl || !scoreProgressEl || !scoreNextEl || !avatarEl || !titlesEl || !galleryEl) return;

  flagsEl.textContent = String(snapshot.stats.flagsCount);
  huntsEl.textContent = String(snapshot.stats.huntedRuggers);
  proposalsEl.textContent = String(Number(snapshot.stats.sealProposals) || 0);
  grantsEl.textContent = String(Number(snapshot.stats.sealGrants) || 0);
  scoreEl.textContent = String(Number(snapshot.stats.score) || 0);
  const flagsProgress = getNextBadgeProgress('flags', snapshot.stats.flagsCount);
  const huntsProgress = getNextBadgeProgress('hunt', snapshot.stats.huntedRuggers);
  const proposalsProgress = hasBadgeType('seal_proposals')
    ? getNextBadgeProgress('seal_proposals', snapshot.stats.sealProposals)
    : getMetricProgressFromTiers(snapshot.stats.sealProposals, [1, 5, 10, 25, 50, 100]);
  const grantsProgress = getNextBadgeProgress('seal_grants', snapshot.stats.sealGrants);
  const flexScoreProgress = getFlexScoreProgress(snapshot.stats.score);
  flagsProgressEl.style.width = `${flagsProgress.pct.toFixed(1)}%`;
  huntsProgressEl.style.width = `${huntsProgress.pct.toFixed(1)}%`;
  proposalsProgressEl.style.width = `${proposalsProgress.pct.toFixed(1)}%`;
  grantsProgressEl.style.width = `${grantsProgress.pct.toFixed(1)}%`;
  scoreProgressEl.style.width = `${flexScoreProgress.pct.toFixed(1)}%`;
  flagsProgressEl.title = flagsProgress.complete ? 'Max badge reached' : `${flagsProgress.current}/${flagsProgress.target}`;
  huntsProgressEl.title = huntsProgress.complete ? 'Max badge reached' : `${huntsProgress.current}/${huntsProgress.target}`;
  proposalsProgressEl.title = proposalsProgress.complete ? 'Max tier reached' : `${proposalsProgress.current}/${proposalsProgress.target}`;
  grantsProgressEl.title = grantsProgress.complete ? 'Max badge reached' : `${grantsProgress.current}/${grantsProgress.target}`;
  if (flexScoreProgress.complete || !flexScoreProgress.next) {
    scoreNextEl.textContent = 'Max flex level reached';
    scoreProgressEl.title = 'Max flex level reached';
  } else {
    scoreNextEl.textContent = `${flexScoreProgress.remaining} points to ${String(flexScoreProgress.next.title || `LEVEL ${flexScoreProgress.next.level}`)}`;
    scoreProgressEl.title = `${Math.max(0, Number(snapshot.stats.score) || 0)}/${flexScoreProgress.next.score}`;
  }
  titlesEl.innerHTML = '';
  if (flexBtn) {
    flexBtn.style.display = 'inline-flex';
  }

  const unlockedIds = new Set(snapshot.unlockedBadges.map((badge) => badge.id));
  const sortedBadges = [...prcBadgeDefinitions].sort((a, b) => {
    const aUnlocked = unlockedIds.has(a.id) ? 1 : 0;
    const bUnlocked = unlockedIds.has(b.id) ? 1 : 0;
    return bUnlocked - aUnlocked;
  });
  galleryEl.innerHTML = sortedBadges.map((badge) => {
    const unlocked = unlockedIds.has(badge.id);
    const tooltip = unlocked ? badge.title : `Locked: ${badge.requirement}`;
    return `
      <div class="prc-badge-item ${unlocked ? 'unlocked' : 'locked'}" title="${tooltip}" data-badge-id="${badge.id}" data-earned="${unlocked ? '1' : '0'}">
        <img src="${safeGetURL('logo.png')}" alt="${badge.id}" data-badge-id="${badge.id}" data-earned="${unlocked ? '1' : '0'}" data-asset-path="${badge.image}">
      </div>
    `;
  }).join('');
  galleryEl.querySelectorAll('img[data-asset-path]').forEach(async (imgEl) => {
    const path = imgEl.getAttribute('data-asset-path');
    const src = await getAssetURL(path);
    if (src) imgEl.src = src;
  });

  galleryEl.onclick = async (event) => {
    const target = event.target && event.target.closest ? event.target.closest('[data-badge-id]') : null;
    const badgeId = target?.getAttribute('data-badge-id');
    if (!badgeId) return;
    const badge = prcBadgeDefinitions.find((b) => b.id === badgeId);
    if (!badge) return;
    const detail = document.getElementById('prc-badge-detail');
    const detailImage = document.getElementById('prc-badge-detail-image');
    const detailAction = document.getElementById('prc-badge-detail-action');
    const detailState = document.getElementById('prc-badge-detail-state');
    const detailPoints = document.getElementById('prc-badge-detail-points');
    const detailStage = document.getElementById('prc-badge-detail-stage');
    if (!detail || !detailImage || !detailAction || !detailState || !detailPoints || !detailStage) return;
    const earned = target.getAttribute('data-earned') === '1';
    const fxTheme = getBadgeFxTheme(badgeId);
    detailStage.style.setProperty('--prc-badge-glow-core', fxTheme.core);
    detailStage.style.setProperty('--prc-badge-glow-mid', fxTheme.mid);
    detailStage.style.setProperty('--prc-badge-particle-color', fxTheme.particle);
    detailImage.src = await getAssetURL(badge.image);
    detailImage.classList.toggle('locked', !earned);
    detailStage.classList.toggle('locked', !earned);
    detailAction.textContent = badge.requirement;
    detailPoints.textContent = `${Math.max(0, Number(badge.points) || 0)} points`;
    detailState.textContent = earned ? 'Earned' : 'Not earned';
    detailState.className = `prc-badge-detail-state ${earned ? 'earned' : 'locked'}`;
    detail.style.display = 'flex';
  };

  buildFlexCard(snapshot, { includeHandle: false }).then((card) => {
    if (card && avatarEl) avatarEl.src = card;
  });
}

async function buildFlexCard(snapshot, options = {}) {
  const includeHandle = options?.includeHandle === true;
  const includeBrandTag = options?.includeBrandTag === true;
  const includeScoreTag = options?.includeScoreTag === true;
  const applyEdgeMask = options?.applyEdgeMask !== false;
  const handleTextRaw = String(options?.handleText || '').trim();
  if (!snapshot || !Array.isArray(snapshot.unlockedBadges)) return getAssetURL('assets/0.png');

  const canvas = document.createElement('canvas');
  canvas.width = 1080;
  canvas.height = 1080;
  const ctx = canvas.getContext('2d');
  if (!ctx) return null;

  // Solid base to avoid any transparency in exported PNG.
  ctx.fillStyle = '#070b14';
  ctx.fillRect(0, 0, 1080, 1080);

  const avatarSrc = await getAvatarByScore(snapshot?.stats?.score);
  const avatar = await loadImageAsync(avatarSrc);
  // Fill the full canvas area to avoid top/bottom bands.
  drawImageCover(ctx, avatar, 0, 0, 1080, 1080);

  if (applyEdgeMask) {
    // Transparent gradient masks (top + bottom) for flex style.
    ctx.save();
    ctx.globalCompositeOperation = 'destination-out';

    const topFade = ctx.createLinearGradient(0, 0, 0, 250);
    topFade.addColorStop(0, 'rgba(0,0,0,1)');
    topFade.addColorStop(0.28, 'rgba(0,0,0,0.72)');
    topFade.addColorStop(0.62, 'rgba(0,0,0,0.28)');
    topFade.addColorStop(1, 'rgba(0,0,0,0)');
    ctx.fillStyle = topFade;
    ctx.fillRect(0, 0, 1080, 250);

    const bottomFade = ctx.createLinearGradient(0, 830, 0, 1080);
    bottomFade.addColorStop(0, 'rgba(0,0,0,0)');
    bottomFade.addColorStop(0.35, 'rgba(0,0,0,0.26)');
    bottomFade.addColorStop(0.72, 'rgba(0,0,0,0.72)');
    bottomFade.addColorStop(1, 'rgba(0,0,0,1)');
    ctx.fillStyle = bottomFade;
    ctx.fillRect(0, 830, 1080, 250);

    ctx.restore();
  }

  const badges = snapshot.unlockedBadges.slice(-11);
  const cols = 4;
  const rows = Math.ceil(Math.max(1, badges.length) / cols);
  const areaX = 360; // Start after character area
  const areaY = 200;
  const areaW = 690;
  const areaH = 520;
  const gap = 12;
  const size = Math.floor(Math.min(
    150,
    (areaW - ((cols - 1) * gap)) / cols,
    (areaH - ((rows - 1) * gap)) / rows
  ));
  const startX = areaX;
  const startY = areaY;

  for (let i = 0; i < badges.length; i++) {
    const badge = badges[i];
    const col = i % cols;
    const row = Math.floor(i / cols);
    const x = startX + col * (size + gap);
    const y = startY + row * (size + gap);
    try {
      const badgeSrc = await getAssetURL(badge.image);
      const img = await loadImageAsync(badgeSrc);
      ctx.save();
      ctx.shadowColor = 'rgba(0, 0, 0, 0.55)';
      ctx.shadowBlur = 20;
      ctx.shadowOffsetX = 0;
      ctx.shadowOffsetY = 6;
      ctx.drawImage(img, x, y, size, size);
      ctx.restore();
    } catch (err) { }
  }

  const flexTitle = getFlexTitleByScore(snapshot?.stats?.score);
  ctx.textAlign = 'center';
  ctx.font = '700 56px "Trajan Pro", "Cinzel", "Times New Roman", serif';
  // Full-width translucent strip at the lower image area, behind the title.
  ctx.fillStyle = 'rgba(0,0,0,0.52)';
  ctx.fillRect(0, 709, 1080, 190);
  if (includeHandle) {
    let normalizedHandle = handleTextRaw;
    if (normalizedHandle && !normalizedHandle.startsWith('@')) normalizedHandle = `@${normalizedHandle}`;
    if (normalizedHandle) {
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.font = '700 36px "Cinzel", "Georgia", serif';
      ctx.lineWidth = 2;
      ctx.strokeStyle = '#000000';
      ctx.fillStyle = '#d8b4ff';
      ctx.strokeText(normalizedHandle, 540, 748);
      ctx.fillText(normalizedHandle, 540, 748);
      ctx.textBaseline = 'alphabetic';
    }
  }

  if (includeBrandTag) {
    ctx.textAlign = 'right';
    ctx.textBaseline = 'alphabetic';
    ctx.font = '700 30px "Cinzel", "Georgia", serif';
    ctx.lineWidth = 2;
    ctx.strokeStyle = '#000000';
    ctx.fillStyle = '#f4ecff';
    ctx.strokeText('HERMETO.FUN', 1040, 1000);
    ctx.fillText('HERMETO.FUN', 1040, 1000);
  }

  if (includeScoreTag) {
    const scoreValue = Number(snapshot?.stats?.score) || 0;
    ctx.textAlign = 'left';
    ctx.textBaseline = 'alphabetic';
    ctx.font = '700 30px "Cinzel", "Georgia", serif';
    ctx.lineWidth = 2;
    ctx.strokeStyle = '#000000';
    ctx.fillStyle = '#ffe6a6';
    ctx.strokeText(`Hunter Score: ${scoreValue}`, 42, 1000);
    ctx.fillText(`Hunter Score: ${scoreValue}`, 42, 1000);
  }
  ctx.textAlign = 'center';
  ctx.font = '700 56px "Trajan Pro", "Cinzel", "Times New Roman", serif';
  ctx.shadowColor = 'rgba(0,0,0,0.85)';
  ctx.shadowBlur = 16;
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 4;
  ctx.fillStyle = '#f2e9ff';
  // Keep title over the lower part of the image (overlapping, not below it).
  ctx.textBaseline = 'middle';
  ctx.fillText(String(flexTitle || '').toUpperCase(), 540, 805);
  ctx.textBaseline = 'alphabetic';
  ctx.shadowColor = 'transparent';
  ctx.shadowBlur = 0;
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 0;

  return canvas.toDataURL('image/png');
}

function promptFlexHandleChoice(prefill = '') {
  return new Promise((resolve) => {
    const existing = document.getElementById('prc-flex-handle-modal');
    if (existing) existing.remove();

    const escapedPrefill = String(prefill || '')
      .replace(/&/g, '&amp;')
      .replace(/"/g, '&quot;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;');
    const overlay = document.createElement('div');
    overlay.id = 'prc-flex-handle-modal';
    overlay.className = 'prc-flex-handle-modal';
    overlay.innerHTML = `
      <div class="prc-flex-handle-card">
        <div class="prc-flex-handle-title">Include your handle on the card?</div>
        <input id="prc-flex-handle-input" class="prc-flex-handle-input" type="text" maxlength="32" placeholder="your_handle" value="${escapedPrefill}">
        <div class="prc-flex-handle-actions">
          <button id="prc-flex-handle-yes" class="prc-flex-handle-btn yes">Yes</button>
          <button id="prc-flex-handle-no" class="prc-flex-handle-btn no">No</button>
        </div>
      </div>
    `;
    const panelRoot = document.getElementById('padre-rug-check-ui');
    (panelRoot || document.body).appendChild(overlay);

    const input = overlay.querySelector('#prc-flex-handle-input');
    const yesBtn = overlay.querySelector('#prc-flex-handle-yes');
    const noBtn = overlay.querySelector('#prc-flex-handle-no');

    const close = (result) => {
      overlay.remove();
      resolve(result);
    };

    if (input) {
      input.focus();
      input.select();
    }

    if (yesBtn) {
      yesBtn.onclick = () => {
        const handle = String(input?.value || '').trim().replace(/^@+/, '');
        if (!handle) {
          input?.focus();
          return;
        }
        close({ includeHandle: true, handle });
      };
    }

    if (noBtn) {
      noBtn.onclick = () => close({ includeHandle: false, handle: '' });
    }

    overlay.onclick = (event) => {
      if (event.target === overlay) close(null);
    };
  });
}

async function handleFlexAction() {
  if (!lastStatsSnapshot) {
    lastStatsSnapshot = await fetchUserStatsSnapshot();
  }
  if (!lastStatsSnapshot) return;
  if (!Array.isArray(lastStatsSnapshot.unlockedBadges)) {
    lastStatsSnapshot.unlockedBadges = [];
  }

  const stored = await storageGetAsync([PRC_FLEX_HANDLE_KEY]);
  const prefillHandle = String(stored?.[PRC_FLEX_HANDLE_KEY] || '').trim();
  const handleChoice = await promptFlexHandleChoice(prefillHandle);
  if (!handleChoice) return;
  const safeHandle = String(handleChoice.handle || '').trim().replace(/^@+/, '').slice(0, 32);
  const includeHandle = handleChoice.includeHandle === true && !!safeHandle;

  if (safeHandle) {
    await storageSetAsync({ [PRC_FLEX_HANDLE_KEY]: safeHandle });
    await markCloudStatsLocalUpdated();
  }

  const card = await buildFlexCard(lastStatsSnapshot, {
    includeHandle,
    handleText: safeHandle,
    includeBrandTag: true,
    includeScoreTag: true,
    applyEdgeMask: false
  });
  if (!card) return;

  const entry = {
    createdAt: Date.now(),
    badgeIds: lastStatsSnapshot.unlockedBadges.map((b) => b.id),
    badgeCount: lastStatsSnapshot.unlockedBadges.length
  };
  const history = await appendFlexHistory(entry);

  const link = document.createElement('a');
  link.href = card;
  link.download = `hermeto-flex-${Date.now()}.png`;
  link.click();

  const refreshed = {
    ...(lastStatsSnapshot || {}),
    flexCount: history.length,
    unlockedBadges: getUnlockedBadges(lastStatsSnapshot.stats, history.length)
  };
  refreshed.stats = {
    ...(refreshed.stats || {}),
    scorePerFlag: Number(refreshed.stats?.scorePerFlag) || 10,
    scorePerHuntedRugger: Number(refreshed.stats?.scorePerHuntedRugger) || 100,
    scorePerSealProposal: Number(refreshed.stats?.scorePerSealProposal) || 10,
    scorePerSealGrant: Number(refreshed.stats?.scorePerSealGrant) || 100,
    scorePerBadge: Number(refreshed.stats?.scorePerBadge) || 100,
    hatBonusPoints: await getHatBonusPoints()
  };
  refreshed.stats.badgePoints = computeUnlockedBadgePoints(refreshed.unlockedBadges);
  refreshed.stats.score = computeScore(
    refreshed.stats,
    refreshed.unlockedBadges.length,
    refreshed.stats.scorePerFlag,
    refreshed.stats.scorePerHuntedRugger,
    refreshed.stats.scorePerBadge,
    refreshed.stats.hatBonusPoints,
    refreshed.stats.scorePerSealProposal,
    refreshed.stats.scorePerSealGrant
  );
  lastStatsSnapshot = refreshed;
  renderStatsUI(refreshed);
  await storageSetAsync({
    [PRC_BADGE_STATE_KEY]: {
      updatedAt: Date.now(),
      stats: refreshed.stats,
      flexCount: refreshed.flexCount,
      unlockedBadgeIds: refreshed.unlockedBadges.map((b) => b.id)
    }
  });
  await markCloudStatsLocalUpdated();
}

let analysisGeneration = 0;
const PRC_PANEL_POS_KEY = 'prcPanelPosition';
const PRC_RUGGER_THRESHOLD_KEY = 'prcRuggerThreshold';
const PRC_USER_ID_KEY = 'prcUserId';
const PRC_UNIQUE_USER_ID_LOCAL_KEY = 'prcUniqueUserId';
const PRC_FLEX_HISTORY_KEY = 'prcFlexHistory';
const PRC_BADGE_STATE_KEY = 'prcBadgeState';
const PRC_SEAL_ACTIVITY_KEY = 'prcSealActivity';
const PRC_FLEX_HANDLE_KEY = 'prcFlexHandle';
const PRC_HAT_BONUS_KEY = 'prcHatBonusPoints';
const PRC_HAT_CLICK_COUNT_KEY = 'prcHatClickCount';
const PRC_CLOUD_PROFILE_META_KEY = 'prcCloudProfileMetaV1';
const PRC_CLOUD_ONBOARDING_KEY = 'prcCloudOnboardingDoneV1';
const PRC_CLOUD_SCHEMA_VERSION = 1;
const PRC_CLOUD_LOCAL_COOLDOWN_STORAGE_KEY = 'prcLocalCreatorCooldownsV1';
let prcPanelResizeBound = false;
let currentRuggerThreshold = 5;
let ruggerThresholdAuthoritative = false;
let huntUiSyncInProgress = false;
let lastConfirmedHuntState = null;
let lastStatsSnapshot = null;
let prcCloudInitPromise = null;
let prcCloudSyncInFlight = null;
const PRC_SEAL_MIN_AGE_DAYS = 180;
const PRC_SEAL_CACHE_TTL_MS = 90000;
const sealStatusCache = new Map();
const sealPinnedOnSet = new Set();
let sealUiRequestNonce = 0;
let currentSealContext = null;
let sealVoteInProgress = false;
const PRC_SITE_TOGGLE_KEYS = {
  padre: 'prcAutoOpen_padre'
};
let siteAutoOpenState = null;
const PRC_BADGE_DEFINITIONS_FALLBACK = [
  { id: 'a1', type: 'hunt', threshold: 5, image: 'assets/a1.png', title: 'Anti Rugger Initiate', requirement: 'Hunt 5 ruggers', points: 100 },
  { id: 'a2', type: 'hunt', threshold: 25, image: 'assets/a2.png', title: 'Rug Hunter', requirement: 'Hunt 25 ruggers', points: 220 },
  { id: 'a3', type: 'hunt', threshold: 50, image: 'assets/a3.png', title: 'Rugger Exterminator', requirement: 'Hunt 50 ruggers', points: 420 },
  { id: 'b1', type: 'flags', threshold: 5, image: 'assets/b1.png', title: 'First Flag', requirement: 'Submit 5 flags', points: 70 },
  { id: 'b2', type: 'flags', threshold: 15, image: 'assets/b2.png', title: 'Flag Runner', requirement: 'Submit 15 flags', points: 130 },
  { id: 'b3', type: 'flags', threshold: 50, image: 'assets/b3.png', title: 'Flag Machine', requirement: 'Submit 50 flags', points: 220 },
  { id: 'b4', type: 'flags', threshold: 80, image: 'assets/b4.png', title: 'Flag Commander', requirement: 'Submit 80 flags', points: 320 },
  { id: 'b5', type: 'flags', threshold: 100, image: 'assets/b5.png', title: 'Flag Legend', requirement: 'Submit 100 flags', points: 450 },
  { id: 'c1', type: 'flex', threshold: 5, image: 'assets/c1.png', title: 'Show-Off Novice', requirement: 'Flex 5 cards', points: 80 },
  { id: 'c2', type: 'flex', threshold: 10, image: 'assets/c2.png', title: 'Show-Off Pro', requirement: 'Flex 10 cards', points: 150 },
  { id: 'c3', type: 'flex', threshold: 25, image: 'assets/c3.png', title: 'Show-Off Mythic', requirement: 'Flex 25 cards', points: 260 },
  { id: 'd1', type: 'hat_clicks', threshold: 25, image: 'assets/d1.png', title: 'Hat Touched I', requirement: 'Click Hermeto hat 25 times', points: 90 },
  { id: 'd2', type: 'hat_clicks', threshold: 50, image: 'assets/d2.png', title: 'Hat Touched II', requirement: 'Click Hermeto hat 50 times', points: 180 },
  { id: 'd3', type: 'hat_clicks', threshold: 400, image: 'assets/d3.png', title: 'Hat Touched III', requirement: 'Click Hermeto hat 400 times', points: 420 },
  { id: 'e1', type: 'seal_grants', threshold: 1, image: 'assets/e1.png', title: 'Seal Witness I', requirement: 'Get 1 granted Seal vote', points: 120 },
  { id: 'e2', type: 'seal_grants', threshold: 4, image: 'assets/e2.png', title: 'Seal Witness II', requirement: 'Get 4 granted Seal votes', points: 260 },
  { id: 'e3', type: 'seal_grants', threshold: 10, image: 'assets/e3.png', title: 'Seal Witness III', requirement: 'Get 10 granted Seal votes', points: 520 }
];
let prcBadgeDefinitions = PRC_BADGE_DEFINITIONS_FALLBACK.map((b) => ({ ...b }));

function applyServerBadgeConfig(badgeConfig) {
  if (!Array.isArray(badgeConfig) || badgeConfig.length === 0) {
    prcBadgeDefinitions = PRC_BADGE_DEFINITIONS_FALLBACK.map((b) => ({ ...b }));
    return;
  }

  const normalize = (entry, fallback = {}, index = 0) => {
    const id = String(entry?.id || fallback.id || `badge_${index + 1}`).trim().slice(0, 32);
    const type = String(entry?.type || fallback.type || 'custom').trim().slice(0, 24);
    const image = String(entry?.image || fallback.image || '').trim().slice(0, 128);
    const title = String(entry?.title || fallback.title || id).trim().slice(0, 96);
    const requirement = String(entry?.requirement || fallback.requirement || '').trim().slice(0, 160);
    const threshold = Math.max(0, parseInt(entry?.threshold, 10) || parseInt(fallback?.threshold, 10) || 0);
    const points = Math.max(0, parseInt(entry?.points, 10) || parseInt(fallback?.points, 10) || 100);
    return { id, type, image, title, requirement, threshold, points };
  };

  const byId = new Map();
  badgeConfig.forEach((entry, idx) => {
    const n = normalize(entry, {}, idx);
    if (n.id) byId.set(n.id, n);
  });

  const merged = PRC_BADGE_DEFINITIONS_FALLBACK.map((fallback, idx) => {
    const fromServer = byId.get(fallback.id);
    return normalize(fromServer || fallback, fallback, idx);
  });

  const fallbackIds = new Set(PRC_BADGE_DEFINITIONS_FALLBACK.map((b) => b.id));
  badgeConfig.forEach((entry, idx) => {
    const n = normalize(entry, {}, idx);
    if (!n.id || fallbackIds.has(n.id)) return;
    merged.push(n);
  });

  prcBadgeDefinitions = merged;
}
const PRC_FLEX_LEVELS_FALLBACK = [
  { level: 0, score: 0, title: 'JUST STARTING', image: 0 },
  { level: 1, score: 250, title: 'TRENCH WATCHER', image: 1 },
  { level: 2, score: 600, title: 'SIGNAL SCRIBE', image: 2 },
  { level: 3, score: 1000, title: 'ANTI RUGGER INITIATE', image: 3 },
  { level: 4, score: 1500, title: 'RUG HUNTER', image: 4 },
  { level: 5, score: 2200, title: 'RUGGER EXTERMINATOR', image: 5 },
  { level: 6, score: 3000, title: 'FLAG RUNNER', image: 6 },
  { level: 7, score: 4000, title: 'FLAG MACHINE', image: 7 },
  { level: 8, score: 5200, title: 'FLAG COMMANDER', image: 8 },
  { level: 9, score: 6700, title: 'FLAG LEGEND', image: 9 },
  { level: 10, score: 8500, title: 'SHOW-OFF PRO', image: 10 },
  { level: 11, score: 10500, title: 'SHOW-OFF MYTHIC', image: 11 },
  { level: 12, score: 13000, title: 'TRENCH ORACLE', image: 12 },
  { level: 13, score: 16000, title: 'SEAL ARCHITECT', image: 13 },
  { level: 14, score: 19500, title: 'HERMETIC VANGUARD', image: 14 },
  { level: 15, score: 23500, title: 'AURUM ASCENDANT', image: 15 },
  { level: 16, score: 28000, title: 'VOID CARTOGRAPHER', image: 16 },
  { level: 17, score: 33000, title: 'COSMIC PUMPSTLE', image: 17 }
];
let prcFlexLevels = PRC_FLEX_LEVELS_FALLBACK.map((lv) => ({ ...lv }));
const PRC_HEADER_PHRASES = [
  'Fix the Assemblage Point, the center holds the world.',
  'Master your Intent, the Great Work performs itself.',
  'The Tao that is traded is not the eternal Tao.',
  'Transmute your Lead, the Golden Mean is silence.',
  'As Above so Below, the market reflects the soul.',
  'Gentle Vibration overcomes the hardest resistance of the chart.',
  'Seek the Gnosis within, the Sacred Geometry is you.',
  'Cause and Effect are one, plant the seed, be still.',
  'True Sovereignty is found where the ego finally dissolves.',
  'Align the Rhythm, flow like water through the code.',
  'Beyond all Polarity, the One remains perfectly still.',
  'The Pumpstle decrees your Mind is the ultimate forge.',
  'Nature does not hurry yet the Transmutation is complete.',
  'Watch the Correspondence, the micro mirrors the macro.',
  'A journey of ten-thousand X begins with one Intent.',
  'Keep your Vessel empty to receive the Golden flow.',
  'The Riddle is the key, the Silence is the lock.',
  'Everything is Vibration, tune your heart to the Infinite.',
  'The ALL is Mind, the meme is its Sacred Form.',
  'Fix the Assemblage Point, the patient holder inherits the Gold.',
  'Master your Intent, do not chase the Polarity of FOMO.',
  'The Tao of the pump flows where the ego dies.',
  'Transmute the Lead of FUD into the Gnosis of diamonds.',
  'As Above so on Chain, the chart mirrors the soul.',
  'Organic Vibration overcomes the hardest algorithm.',
  'Seek the Sacred Geometry in code not in promises.',
  'Cause and Effect, sow the meme, await the Transmutation.',
  'The wise Dev acts without forcing the market’s Rhythm.',
  'True Sovereignty is ignoring the noise of the masses.',
  'Flow like SOL, liquidity always finds its Tao.',
  'Beyond Green and Red, the Vibration is One.',
  'The Pumpstle decrees conviction is your only true Asset.',
  'Nature does not hurry, the Moon arrives in its own time.',
  'Watch the Correspondence, a smol today is a giant tomorrow.',
  'A journey of a thousand X begins with one Swap.',
  'Keep your Wallet empty of doubt and full of Intent.',
  'The Riddle is the filter, patience is the Key.',
  'Everything is Vibration, the ticker is your mantra.'
];

function getSealCacheKey(mint, creator) {
  return `${String(mint || '')}|${String(creator || '')}`;
}

function clearSealFooterUI() {
  const slot = document.getElementById('prc-seal-slot');
  const voteSlot = document.getElementById('prc-seal-vote-slot');
  const voteWrap = document.getElementById('prc-seal-vote-wrap');
  const badgeWrap = document.getElementById('prc-seal-badge-wrap');
  const note = document.getElementById('prc-seal-note');
  if (slot) slot.classList.remove('badge-mode');
  if (voteSlot) voteSlot.style.display = 'none';
  if (slot) slot.style.display = 'none';
  if (voteWrap) voteWrap.style.display = 'none';
  if (badgeWrap) badgeWrap.style.display = 'none';
  if (note) {
    note.style.display = 'none';
    note.textContent = '';
  }
}

function applySealSafeRiskBadge() {
  const badge = document.getElementById('prc-risk-badge');
  if (!badge) return;
  badge.textContent = 'SAFE';
  badge.className = 'prc-risk-badge safe';
}

function applySealFooterCtoMode() {
  const footerBgImg = document.getElementById('prc-footer-bg-img');
  const footerSection = document.querySelector('.prc-footer-section');
  const huntContainer = document.getElementById('prc-hunt-container');
  const ruggedMsg = document.getElementById('prc-rugged-verified-msg');

  if (huntContainer) huntContainer.style.display = 'none';
  if (ruggedMsg) ruggedMsg.style.display = 'none';
  if (footerBgImg) {
    footerBgImg.src = safeGetURL('cto.png');
    footerBgImg.style.opacity = '1';
  }
  if (footerSection) footerSection.setAttribute('data-mode', 'cto');
}

function setSealFooterVoteUI(votes, canVote, noteText = '') {
  const slot = document.getElementById('prc-seal-slot');
  const voteSlot = document.getElementById('prc-seal-vote-slot');
  const voteWrap = document.getElementById('prc-seal-vote-wrap');
  const badgeWrap = document.getElementById('prc-seal-badge-wrap');
  const voteBtn = document.getElementById('prc-seal-vote-btn');
  const voteCount = document.getElementById('prc-seal-vote-count');
  const note = document.getElementById('prc-seal-note');
  if (!voteSlot || !voteWrap || !voteBtn || !voteCount) return;

  if (slot) slot.classList.remove('badge-mode');
  if (slot) slot.style.display = 'none';
  voteSlot.style.display = 'flex';
  voteWrap.style.display = 'inline-flex';
  if (badgeWrap) badgeWrap.style.display = 'none';
  voteCount.textContent = `${Math.max(0, Number(votes) || 0)} VOTES`;
  voteBtn.disabled = !canVote || sealVoteInProgress;
  if (sealVoteInProgress) {
    voteBtn.textContent = 'VOTING...';
  } else {
    voteBtn.innerHTML = `PROPOSE FOR <span class="prc-seal-btn-white">SEAL OF HERMETO</span>`;
  }

  if (note) {
    const text = noteText ? String(noteText) : '';
    const hideNote =
      /already\s+(proposed|voted)|you\s+already/i.test(text) ||
      /seal status unavailable/i.test(text);
    const finalText = hideNote ? '' : text;
    note.textContent = finalText;
    note.style.display = finalText ? 'block' : 'none';
  }
}

function setSealFooterBadgeUI(noteText = '') {
  const slot = document.getElementById('prc-seal-slot');
  const voteSlot = document.getElementById('prc-seal-vote-slot');
  const voteWrap = document.getElementById('prc-seal-vote-wrap');
  const badgeWrap = document.getElementById('prc-seal-badge-wrap');
  if (!slot || !badgeWrap) return;

  if (voteSlot) voteSlot.style.display = 'none';
  slot.classList.add('badge-mode');
  slot.style.display = 'flex';
  badgeWrap.style.display = 'inline-flex';
  if (voteWrap) voteWrap.style.display = 'none';
}

function shouldShowSealControl(data) {
  const tokenAgeDays = Number(data?.tokenAgeDays) || 0;
  const hasMatureAge = tokenAgeDays >= PRC_SEAL_MIN_AGE_DAYS;
  const flagsCount = Number(data?.flagCount) || 0;
  const okMode = data?.riskLevel === 'safe' && flagsCount === 0;
  const ctoMode = Boolean(data?.isSurvivor);
  return hasMatureAge && (okMode || ctoMode);
}

async function getSealStatusForToken(context, force = false) {
  const key = getSealCacheKey(context.mint, context.creator);
  const now = Date.now();
  const cached = sealStatusCache.get(key);
  if (!force && cached && (now - cached.at) < PRC_SEAL_CACHE_TTL_MS) return cached.value;

  const userId = await getOrCreateUserId();
  const response = await requestMessageAsync({
    action: 'getSealState',
    mint: context.mint,
    creator: context.creator,
    tokenSymbol: context.symbol,
    tokenAgeDays: context.tokenAgeDays,
    userId,
    clientFingerprint: getClientFingerprint()
  });
  const rawIsOnSeal = response?.isOnSeal ?? response?.is_on_seal ?? response?.onSeal ?? response?.on_seal;
  const parsedIsOnSeal = (rawIsOnSeal === true) || (rawIsOnSeal === 1) || (rawIsOnSeal === '1') || (String(rawIsOnSeal || '').toLowerCase() === 'true');
  const value = {
    success: Boolean(response?.success),
    votes: Math.max(0, Number(response?.votes) || 0),
    isOnSeal: parsedIsOnSeal,
    canVote: response?.canVote === undefined
      ? (Boolean(response?.success) && !parsedIsOnSeal)
      : Boolean(response.canVote),
    message: response?.message || ''
  };
  sealStatusCache.set(key, { value, at: now });
  return value;
}

async function refreshSealFooterForToken(data, force = false) {
  const mint = String(data?.mint || '').trim();
  const creator = String(data?.creator || '').trim();
  const symbol = String(data?.symbol || '').trim();
  const tokenAgeDays = Number(data?.tokenAgeDays) || 0;
  const voteEligible = shouldShowSealControl(data);

  currentSealContext = { mint, creator, symbol, tokenAgeDays, eligible: voteEligible };

  if (!mint || !creator) {
    clearSealFooterUI();
    return;
  }

  const key = getSealCacheKey(mint, creator);
  const flagsCount = Number(data?.flagCount) || 0;
  const isSafe = String(data?.riskLevel || 'safe').toLowerCase() === 'safe';
  const isRugged = Boolean(data?.rugged);
  const isDumped = Boolean(data?.devSold || (Number(data?.devPct) === 0 && Number(data?.score) < 2000)); 
  const isClean = isSafe && flagsCount === 0 && !isRugged && !isDumped;

  if (sealPinnedOnSet.has(key)) {
    if (!isClean) applySealFooterCtoMode();
    const grantRecorded = await recordSealGrantIfEligible(mint);
    if (grantRecorded) refreshStatsIfVisible();
    setSealFooterBadgeUI();
    return;
  }

  const nonce = ++sealUiRequestNonce;
  clearSealFooterUI();
  const status = await getSealStatusForToken({ mint, creator, symbol, tokenAgeDays }, force);
  if (nonce !== sealUiRequestNonce) return;

  if (status.isOnSeal) {
    sealPinnedOnSet.add(key);
    if (!isClean) applySealFooterCtoMode();
    const grantRecorded = await recordSealGrantIfEligible(mint);
    if (grantRecorded) refreshStatsIfVisible();
    setSealFooterBadgeUI();

    // Show blue message in main UI
    const sealMsgEl = document.getElementById('prc-seal-msg');
    if (sealMsgEl) {
      sealMsgEl.textContent = '🏆 Token is on Seal Of Hermeto';
      sealMsgEl.style.display = 'flex';
    }
    return;
  }
  if (!status.success) {
    clearSealFooterUI();
    return;
  }
  if (!voteEligible) {
    clearSealFooterUI();
    return;
  }
  setSealFooterVoteUI(status.votes, status.canVote, status.message || '');
}
const PRC_HEADER_TITLE_VISIBLE_MS = 20000;
const PRC_HEADER_TITLE_FADE_MS = 900;
const PRC_HEADER_PHRASE_VISIBLE_MS = 20000;
const PRC_HEADER_PHRASE_GAP_MS = 260;
const PRC_HEADER_PHRASE_CHAR_HIDE_STEP_MS = 28;
const PRC_HEADER_PHRASE_CHAR_HIDE_MS = 220;
const PRC_HEADER_PHRASE_HIDE_TAIL_MS = 80;
const PRC_HAT_EVENT_MIN_MS = 60000;
const PRC_HAT_EVENT_MAX_MS = 120000;
const PRC_HAT_GLOW_WINDOW_MS = 3200;
const PRC_HAT_POST_SHAKE_DRAG_LOCK_MS = 2000;
const PRC_HAT_BONUS_PER_CLICK = 5;
let prcHatBonusPerClick = PRC_HAT_BONUS_PER_CLICK;
let prcHeaderLoopGeneration = 0;
let prcHeaderLoopTimers = [];
let prcHeaderPhrasePool = [];
let prcLastHeaderPhrase = '';
let prcHeaderResumeTimer = null;
let prcHeaderVisibilityHooksBound = false;
let prcHeaderWatchdogTimer = null;
let prcHeaderLastActiveAt = 0;
let prcHatLoopGeneration = 0;
let prcHatLoopTimer = null;
let prcHatGlowActive = false;
let prcHatActiveElement = null;
let prcHeaderDragLockUntil = 0;

function clearHeaderLoopTimers() {
  for (const timer of prcHeaderLoopTimers) {
    clearTimeout(timer);
  }
  prcHeaderLoopTimers = [];
}

function markHeaderActive() {
  prcHeaderLastActiveAt = Date.now();
}

function setHeaderPhraseText(phraseEl, phrase) {
  if (!phraseEl) return 0;
  const text = String(phrase || '');
  phraseEl.textContent = '';
  const fragment = document.createDocumentFragment();
  let charCount = 0;
  const parts = text.split(/(\s+)/);
  for (const part of parts) {
    if (!part) continue;
    if (/^\s+$/.test(part)) {
      fragment.appendChild(document.createTextNode(part));
      continue;
    }
    const wordSpan = document.createElement('span');
    wordSpan.className = 'prc-header-phrase-word';
    const chars = Array.from(part);
    for (const ch of chars) {
      const span = document.createElement('span');
      span.className = 'prc-header-phrase-char';
      span.textContent = ch;
      wordSpan.appendChild(span);
      charCount += 1;
    }
    fragment.appendChild(wordSpan);
  }
  phraseEl.appendChild(fragment);
  phraseEl.setAttribute('aria-label', text);
  return charCount;
}

function getHeaderPhraseHideDurationMs(charCount) {
  const count = Math.max(1, Number(charCount) || 0);
  return ((count - 1) * PRC_HEADER_PHRASE_CHAR_HIDE_STEP_MS) + PRC_HEADER_PHRASE_CHAR_HIDE_MS + PRC_HEADER_PHRASE_HIDE_TAIL_MS;
}

function animateHeaderPhraseHideSequence(phraseEl) {
  if (!phraseEl) return 0;
  const chars = Array.from(phraseEl.querySelectorAll('.prc-header-phrase-char'));
  if (!chars.length) {
    phraseEl.classList.remove('prc-header-phrase-show', 'prc-header-phrase-letter-hide');
    return 0;
  }

  phraseEl.classList.remove('prc-header-phrase-letter-hide');
  void phraseEl.offsetWidth;
  for (let idx = chars.length - 1, seq = 0; idx >= 0; idx -= 1, seq += 1) {
    chars[idx].style.setProperty('--prc-char-hide-delay', `${seq * PRC_HEADER_PHRASE_CHAR_HIDE_STEP_MS}ms`);
  }
  phraseEl.classList.add('prc-header-phrase-letter-hide');
  markHeaderActive();
  return getHeaderPhraseHideDurationMs(chars.length);
}

function clearHeaderWatchdogTimer() {
  if (prcHeaderWatchdogTimer) {
    clearInterval(prcHeaderWatchdogTimer);
    prcHeaderWatchdogTimer = null;
  }
}

function ensureHeaderTitleImage(titleEl) {
  if (!titleEl) return;
  const primary = safeGetURL('hermeto_title.png');
  const fallback = safeGetURL('logo.png');
  const currentSrc = String(titleEl.getAttribute('src') || '').trim();
  if (!currentSrc) {
    titleEl.src = primary || fallback || '';
  }
  if (titleEl.dataset.prcHeaderImgBound === '1') return;
  titleEl.dataset.prcHeaderImgBound = '1';
  titleEl.addEventListener('load', () => {
    markHeaderActive();
  });
  titleEl.addEventListener('error', () => {
    if (fallback && titleEl.src !== fallback) {
      titleEl.src = fallback;
    }
    markHeaderActive();
  });
}

function startHeaderWatchdog() {
  clearHeaderWatchdogTimer();
  prcHeaderWatchdogTimer = setInterval(() => {
    if (!isContextValid()) return;
    const titleEl = document.getElementById('prc-header-title-img');
    const phraseEl = document.getElementById('prc-header-phrase');
    if (!titleEl || !phraseEl) return;

    ensureHeaderTitleImage(titleEl);
    const phraseText = String(phraseEl.textContent || '').trim();
    if (!phraseText) {
      setHeaderPhraseText(phraseEl, getNextHeaderPhrase());
      markHeaderActive();
    }

    const titleOpacity = Number.parseFloat(window.getComputedStyle(titleEl).opacity || '0');
    const phraseOpacity = Number.parseFloat(window.getComputedStyle(phraseEl).opacity || '0');
    const now = Date.now();
    const staleForMs = now - (prcHeaderLastActiveAt || 0);
    const allHidden = titleOpacity <= 0.04 && phraseOpacity <= 0.04;

    if ((allHidden && staleForMs > 2200) || staleForMs > 55000) {
      phraseEl.classList.remove('prc-header-phrase-show', 'prc-header-phrase-letter-hide');
      void phraseEl.offsetWidth;
      phraseEl.classList.add('prc-header-phrase-show');
      queueHeaderLoopRestart(0);
      markHeaderActive();
    }
  }, 2000);
}

function queueHeaderLoopRestart(delayMs = 100) {
  if (prcHeaderResumeTimer) {
    clearTimeout(prcHeaderResumeTimer);
    prcHeaderResumeTimer = null;
  }
  prcHeaderResumeTimer = setTimeout(() => {
    prcHeaderResumeTimer = null;
    if (!isContextValid()) return;
    const titleEl = document.getElementById('prc-header-title-img');
    const phraseEl = document.getElementById('prc-header-phrase');
    if (!titleEl || !phraseEl) return;
    startHeaderTitlePhraseLoop();
  }, Math.max(0, Number(delayMs) || 0));
}

function bindHeaderVisibilityRecovery() {
  if (prcHeaderVisibilityHooksBound) return;
  prcHeaderVisibilityHooksBound = true;

  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'visible') {
      queueHeaderLoopRestart(80);
    }
  });

  window.addEventListener('focus', () => {
    queueHeaderLoopRestart(80);
  });
}

function shuffleArray(values) {
  const arr = Array.isArray(values) ? [...values] : [];
  for (let i = arr.length - 1; i > 0; i -= 1) {
    const j = Math.floor(Math.random() * (i + 1));
    const tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
  }
  return arr;
}

function getNextHeaderPhrase() {
  if (!prcHeaderPhrasePool.length) {
    prcHeaderPhrasePool = shuffleArray(PRC_HEADER_PHRASES);
    if (prcHeaderPhrasePool.length > 1 && prcHeaderPhrasePool[0] === prcLastHeaderPhrase) {
      const swapIdx = 1 + Math.floor(Math.random() * (prcHeaderPhrasePool.length - 1));
      const tmp = prcHeaderPhrasePool[0];
      prcHeaderPhrasePool[0] = prcHeaderPhrasePool[swapIdx];
      prcHeaderPhrasePool[swapIdx] = tmp;
    }
  }
  const phrase = prcHeaderPhrasePool.shift() || PRC_HEADER_PHRASES[0] || '';
  prcLastHeaderPhrase = phrase;
  return phrase;
}

function startHeaderTitlePhraseLoop() {
  const titleEl = document.getElementById('prc-header-title-img');
  const phraseEl = document.getElementById('prc-header-phrase');
  if (!titleEl || !phraseEl) return;
  ensureHeaderTitleImage(titleEl);
  startHeaderWatchdog();
  phraseEl.style.setProperty('--prc-header-phrase-duration', `${Math.max(1000, PRC_HEADER_PHRASE_VISIBLE_MS)}ms`);
  phraseEl.style.setProperty('--prc-header-phrase-char-hide-ms', `${PRC_HEADER_PHRASE_CHAR_HIDE_MS}ms`);

  clearHeaderLoopTimers();
  prcHeaderLoopGeneration += 1;
  const generation = prcHeaderLoopGeneration;

  const schedule = (fn, delayMs) => {
    const t = setTimeout(() => {
      if (!isContextValid() || generation !== prcHeaderLoopGeneration) return;
      fn();
    }, delayMs);
    prcHeaderLoopTimers.push(t);
  };

  const showTitle = () => {
    phraseEl.classList.remove('prc-header-phrase-show', 'prc-header-phrase-letter-hide');
    titleEl.classList.remove('prc-header-title-hide', 'prc-header-title-show');
    void titleEl.offsetWidth;
    titleEl.classList.add('prc-header-title-show');
    markHeaderActive();
  };

  const hideTitle = () => {
    titleEl.classList.remove('prc-header-title-show', 'prc-header-title-hide');
    void titleEl.offsetWidth;
    titleEl.classList.add('prc-header-title-hide');
  };

  const showPhrase = () => {
    const phrase = getNextHeaderPhrase();
    const charCount = setHeaderPhraseText(phraseEl, phrase);
    phraseEl.classList.remove('prc-header-phrase-show', 'prc-header-phrase-letter-hide');
    void phraseEl.offsetWidth;
    phraseEl.classList.add('prc-header-phrase-show');
    markHeaderActive();
    return getHeaderPhraseHideDurationMs(charCount);
  };

  const runCycle = () => {
    showTitle();
    schedule(() => hideTitle(), PRC_HEADER_TITLE_VISIBLE_MS);
    const phraseStartDelay = PRC_HEADER_TITLE_VISIBLE_MS + PRC_HEADER_TITLE_FADE_MS + PRC_HEADER_PHRASE_GAP_MS;
    schedule(() => {
      const hideDurationMs = showPhrase();
      schedule(() => {
        animateHeaderPhraseHideSequence(phraseEl);
      }, PRC_HEADER_PHRASE_VISIBLE_MS);
      schedule(() => runCycle(), PRC_HEADER_PHRASE_VISIBLE_MS + hideDurationMs);
    }, phraseStartDelay);
  };

  runCycle();
}

function clearHatLoopTimer() {
  if (prcHatLoopTimer) {
    clearTimeout(prcHatLoopTimer);
    prcHatLoopTimer = null;
  }
}

function randomBetween(min, max) {
  const low = Math.min(min, max);
  const high = Math.max(min, max);
  return Math.floor(Math.random() * (high - low + 1)) + low;
}

function emitHatSparks(hatEl, amount = 10) {
  const parent = hatEl?.parentElement;
  if (!hatEl || !parent) return;
  for (let i = 0; i < amount; i += 1) {
    const spark = document.createElement('span');
    spark.className = 'prc-hat-spark';
    const dx = randomBetween(-34, 34);
    const dy = randomBetween(-58, -22);
    const dur = randomBetween(520, 920);
    spark.style.setProperty('--prc-spark-dx', `${dx}px`);
    spark.style.setProperty('--prc-spark-dy', `${dy}px`);
    spark.style.setProperty('--prc-spark-dur', `${dur}ms`);
    spark.style.left = `${hatEl.offsetLeft + (hatEl.offsetWidth * 0.52)}px`;
    spark.style.top = `${hatEl.offsetTop + (hatEl.offsetHeight * 0.32)}px`;
    parent.appendChild(spark);
    setTimeout(() => spark.remove(), dur + 120);
  }
}

function showHatPointsFloat(hatEl, clickClientX = null, clickClientY = null, text = '+5 Points') {
  const parent = hatEl?.parentElement;
  if (!hatEl || !parent) return;
  const float = document.createElement('div');
  float.className = 'prc-hat-points-float';
  float.textContent = text;
  if (Number.isFinite(clickClientX) && Number.isFinite(clickClientY)) {
    const parentRect = parent.getBoundingClientRect();
    float.style.left = `${clickClientX - parentRect.left}px`;
    float.style.top = `${clickClientY - parentRect.top}px`;
  } else {
    float.style.left = `${hatEl.offsetLeft + (hatEl.offsetWidth * 0.5)}px`;
    float.style.top = `${hatEl.offsetTop + (hatEl.offsetHeight * 0.2)}px`;
  }
  parent.appendChild(float);
  setTimeout(() => float.remove(), 2200);
}

async function applyHatBonusPoints(delta = null) {
  const normalizedDelta = Math.max(0, Number(delta));
  const pointsDelta = Number.isFinite(normalizedDelta)
    ? normalizedDelta
    : Math.max(0, Number(prcHatBonusPerClick) || PRC_HAT_BONUS_PER_CLICK);
  const [nextBonus, nextClicks] = await Promise.all([
    addHatBonusPoints(pointsDelta),
    incrementHatClickCount(1)
  ]);
  if (!lastStatsSnapshot) return nextBonus;
  const current = { ...(lastStatsSnapshot.stats || {}) };
  current.hatBonusPoints = nextBonus;
  current.hatClicks = nextClicks;
  current.scorePerFlag = Number(current.scorePerFlag) || 10;
  current.scorePerHuntedRugger = Number(current.scorePerHuntedRugger) || 100;
  current.scorePerHatClick = Number(current.scorePerHatClick) || Math.max(0, Number(prcHatBonusPerClick) || PRC_HAT_BONUS_PER_CLICK);
  current.scorePerSealProposal = Number(current.scorePerSealProposal) || 10;
  current.scorePerSealGrant = Number(current.scorePerSealGrant) || 100;
  current.scorePerBadge = Number(current.scorePerBadge) || 100;
  const nextUnlockedBadges = getUnlockedBadges(current, Number(lastStatsSnapshot.flexCount) || 0);
  current.badgePoints = computeUnlockedBadgePoints(nextUnlockedBadges);
  const badgeCount = nextUnlockedBadges.length;
  current.score = computeScore(
    current,
    badgeCount,
    current.scorePerFlag,
    current.scorePerHuntedRugger,
    current.scorePerBadge,
    current.hatBonusPoints,
    current.scorePerSealProposal,
    current.scorePerSealGrant
  );
  lastStatsSnapshot = {
    ...lastStatsSnapshot,
    stats: current,
    unlockedBadges: nextUnlockedBadges
  };
  renderStatsUI(lastStatsSnapshot);
  await storageSetAsync({
    [PRC_BADGE_STATE_KEY]: {
      updatedAt: Date.now(),
      stats: current,
      flexCount: Number(lastStatsSnapshot.flexCount) || 0,
      unlockedBadgeIds: (lastStatsSnapshot.unlockedBadges || []).map((b) => b.id)
    }
  });
  return nextBonus;
}

async function tryClaimHatBonus(anchorEl = null, clickClientX = null, clickClientY = null) {
  if (!prcHatGlowActive) return false;
  const sourceEl = anchorEl || prcHatActiveElement;
  if (!sourceEl) return false;
  if (prcHatActiveElement) {
    prcHatActiveElement.classList.add('prc-hat-claimed');
    setTimeout(() => prcHatActiveElement?.classList.remove('prc-hat-claimed'), 380);
  }
  const hatPoints = Math.max(0, Number(lastStatsSnapshot?.stats?.scorePerHatClick) || Number(prcHatBonusPerClick) || PRC_HAT_BONUS_PER_CLICK);
  emitHatSparks(sourceEl, 7);
  showHatPointsFloat(sourceEl, clickClientX, clickClientY, `+${hatPoints} Points`);
  await applyHatBonusPoints(hatPoints);
  triggerStatsRewardGlow(2);
  return true;
}

function startHeaderHatEventLoop() {
  const hatEl = document.querySelector('.prc-header-hat-img');
  if (!hatEl) return;
  const headerEl = hatEl.closest('.prc-header');

  clearHatLoopTimer();
  prcHatLoopGeneration += 1;
  const generation = prcHatLoopGeneration;
  prcHatGlowActive = false;
  prcHatActiveElement = hatEl;
  hatEl.classList.remove('prc-hat-trigger');
  if (headerEl) headerEl.classList.remove('prc-hat-catch-mode');

  hatEl.onmousedown = (event) => {
    event.preventDefault();
    event.stopPropagation();
  };
  hatEl.ondblclick = (event) => {
    event.preventDefault();
    event.stopPropagation();
  };
  hatEl.onclick = async (event) => {
    event.preventDefault();
    event.stopPropagation();
    await tryClaimHatBonus(hatEl, event.clientX, event.clientY);
  };

  const scheduleNext = () => {
    clearHatLoopTimer();
    const delay = randomBetween(PRC_HAT_EVENT_MIN_MS, PRC_HAT_EVENT_MAX_MS);
    prcHatLoopTimer = setTimeout(() => {
      if (!isContextValid() || generation !== prcHatLoopGeneration) return;

      prcHatGlowActive = true;
      prcHeaderDragLockUntil = Date.now() + PRC_HAT_GLOW_WINDOW_MS + PRC_HAT_POST_SHAKE_DRAG_LOCK_MS;
      if (headerEl) headerEl.classList.add('prc-hat-catch-mode');
      hatEl.classList.remove('prc-hat-trigger');
      void hatEl.offsetWidth;
      hatEl.classList.add('prc-hat-trigger');
      emitHatSparks(hatEl, 12);

      setTimeout(() => {
        if (generation !== prcHatLoopGeneration) return;
        prcHatGlowActive = false;
        if (headerEl) headerEl.classList.remove('prc-hat-catch-mode');
      }, PRC_HAT_GLOW_WINDOW_MS);

      setTimeout(() => {
        if (generation !== prcHatLoopGeneration) return;
        hatEl.classList.remove('prc-hat-trigger');
        scheduleNext();
      }, PRC_HAT_GLOW_WINDOW_MS + 480);
    }, delay);
  };

  scheduleNext();
}

function getDefaultSiteAutoOpenState() {
  return {
    [PRC_SITE_TOGGLE_KEYS.padre]: true
  };
}

function parseRuggerThreshold(input, fallback = currentRuggerThreshold || 5) {
  const parsed = parseInt(input, 10);
  if (Number.isFinite(parsed) && parsed > 0) return parsed;
  const fallbackParsed = parseInt(fallback, 10);
  return Number.isFinite(fallbackParsed) && fallbackParsed > 0 ? fallbackParsed : 5;
}

function hasValidThresholdValue(value) {
  const parsed = parseInt(value, 10);
  return Number.isFinite(parsed) && parsed > 0;
}

function applyRuggerThresholdToLabels() {
  const labelValue = ruggerThresholdAuthoritative
    ? String(parseRuggerThreshold(currentRuggerThreshold, 5))
    : '--';
  const countTag = document.getElementById('prc-flag-count-text');
  const scammerAlertText = document.getElementById('prc-scammer-alert-text');
  if (countTag) {
    const currentFlags = parseInt(String(countTag.textContent || '0').split('/')[0], 10);
    const flagsNum = Number.isFinite(currentFlags) && currentFlags >= 0 ? currentFlags : 0;
    countTag.textContent = `${flagsNum}/${labelValue} Flags`;
  }
  if (scammerAlertText) {
    scammerAlertText.textContent = `\uD83D\uDEA8 SCAMMER ALERT (${labelValue}+ FLAGS) \uD83D\uDEA8`;
  }
}

async function refreshRuggerThresholdFromBackend() {
  if (!isContextValid()) return;
  try {
    const userId = await getOrCreateUserId();
    const uniqueUserId = getOrCreateUniqueUserIdLocal();
    const resp = await requestMessageAsync({
      action: 'getUserStats',
      userId,
      uniqueUserId,
      clientFingerprint: getClientFingerprint()
    });
    const next = parseRuggerThreshold(
      resp?.ruggerThreshold ?? resp?.threshold ?? resp?.flagThreshold ?? resp?.scammerThreshold,
      currentRuggerThreshold
    );
    if (hasValidThresholdValue(next)) {
      currentRuggerThreshold = next;
      ruggerThresholdAuthoritative = true;
      safeStorage()?.set({ [PRC_RUGGER_THRESHOLD_KEY]: currentRuggerThreshold });
      applyRuggerThresholdToLabels();
    }
  } catch (_) { }
}

function hydrateRuggerThresholdState() {
  const storage = safeStorage();
  if (!storage) return;
  storage.get([PRC_RUGGER_THRESHOLD_KEY], (result) => {
    if (hasValidThresholdValue(result?.[PRC_RUGGER_THRESHOLD_KEY])) {
      currentRuggerThreshold = parseRuggerThreshold(result?.[PRC_RUGGER_THRESHOLD_KEY], currentRuggerThreshold);
      ruggerThresholdAuthoritative = true;
    }
    applyRuggerThresholdToLabels();
  });
  void refreshRuggerThresholdFromBackend();
}

function captureConfirmedHuntState(creator, flagsCount, cooldownUntil, ruggerThreshold, holdMs = 15000) {
  if (!creator || creator === 'Unknown') return;
  lastConfirmedHuntState = {
    creator,
    flagsCount: Number(flagsCount) || 0,
    cooldownUntil: Number(cooldownUntil) || 0,
    ruggerThreshold: parseRuggerThreshold(ruggerThreshold, currentRuggerThreshold),
    expiresAt: Date.now() + Math.max(3000, Number(holdMs) || 15000)
  };
}

function getProtectedHuntState(creator, incoming) {
  const inc = {
    flagsCount: Number(incoming?.flagsCount) || 0,
    cooldownUntil: Number(incoming?.cooldownUntil) || 0,
    ruggerThreshold: parseRuggerThreshold(incoming?.ruggerThreshold, currentRuggerThreshold)
  };
  const protectedState = lastConfirmedHuntState;
  if (!protectedState) return inc;
  if (protectedState.expiresAt <= Date.now()) return inc;
  if (!creator || creator !== protectedState.creator) return inc;

  return {
    flagsCount: Math.max(inc.flagsCount, protectedState.flagsCount),
    cooldownUntil: Math.max(inc.cooldownUntil, protectedState.cooldownUntil),
    ruggerThreshold: parseRuggerThreshold(inc.ruggerThreshold, protectedState.ruggerThreshold)
  };
}

function getCurrentSiteAutoOpenKey() {
  const host = (location.hostname || '').toLowerCase();
  if (host.includes('padre.gg')) return PRC_SITE_TOGGLE_KEYS.padre;
  return null;
}

function hydrateSiteAutoOpenState() {
  if (siteAutoOpenState) return;
  siteAutoOpenState = getDefaultSiteAutoOpenState();
  const storage = safeStorage();
  if (!storage) return;
  const keys = [...Object.values(PRC_SITE_TOGGLE_KEYS), 'prcAutoOpen'];
  storage.get(keys, (result) => {
    const legacyGlobalOff = result?.prcAutoOpen === false;
    for (const key of Object.values(PRC_SITE_TOGGLE_KEYS)) {
      if (legacyGlobalOff) {
        siteAutoOpenState[key] = false;
      } else {
        siteAutoOpenState[key] = result?.[key] !== false;
      }
    }
  });
}

function isAutoOpenEnabledForCurrentSite() {
  const key = getCurrentSiteAutoOpenKey();
  if (!key) return true;
  if (!siteAutoOpenState) return true;
  return siteAutoOpenState[key] !== false;
}

function getPadreRightRailWidth() {
  if (!location.hostname.includes('padre.gg')) return 0;
  const viewportW = window.innerWidth;
  const viewportH = window.innerHeight;
  let maxWidth = 0;
  const candidates = document.body ? document.body.querySelectorAll('*') : [];
  for (const el of candidates) {
    if (!el || el.id === 'padre-rug-check-ui' || el.id === 'prc-minimized-btn') continue;
    if (el.closest && el.closest('#padre-rug-check-ui')) continue;
    const rect = el.getBoundingClientRect();
    if (rect.width < 180 || rect.height < Math.min(220, viewportH * 0.35)) continue;
    if (rect.right < viewportW - 4) continue;
    if (rect.left < viewportW * 0.6) continue;
    const style = window.getComputedStyle(el);
    if (style.display === 'none' || style.visibility === 'hidden') continue;
    maxWidth = Math.max(maxWidth, rect.width);
  }
  return Math.min(420, Math.max(0, Math.round(maxWidth)));
}

function clampPanelPosition(left, top, panelWidth, panelHeight) {
  const margin = 8;
  const maxLeft = Math.max(margin, window.innerWidth - panelWidth - margin);
  const maxTop = Math.max(margin, window.innerHeight - panelHeight - margin);
  return {
    left: Math.min(Math.max(left, margin), maxLeft),
    top: Math.min(Math.max(top, margin), maxTop)
  };
}

function applyDockedPosition(container) {
  if (!container) return;
  const panelWidth = container.offsetWidth || 340;
  const panelHeight = container.offsetHeight || Math.max(320, window.innerHeight - 100);
  const detectedRailWidth = getPadreRightRailWidth();
  const railWidth = detectedRailWidth > 0
    ? detectedRailWidth
    : (location.hostname.includes('padre.gg') ? 240 : 0);
  const gap = location.hostname.includes('padre.gg') ? 8 : 12;
  const fallbackRightGap = 16;
  const targetLeft = railWidth > 0
    ? (window.innerWidth - railWidth - panelWidth - gap)
    : (window.innerWidth - panelWidth - fallbackRightGap);
  const targetTop = location.hostname.includes('padre.gg') ? 180 : 110;
  const pos = clampPanelPosition(targetLeft, targetTop, panelWidth, panelHeight);
  container.style.left = `${pos.left}px`;
  container.style.top = `${pos.top}px`;
  container.style.right = 'auto';
}

function moveFooterUnderRiskBadge(root = document) {
  const results = root.querySelector('#prc-results');
  const riskBadge = results?.querySelector('#prc-risk-badge');
  const footerSection = results?.querySelector('.prc-footer-section');
  if (!results || !riskBadge || !footerSection) return;

  // Keep footer + rugged message together by moving their shared wrapper.
  const footerWrapper = footerSection.parentElement;
  if (!footerWrapper || footerWrapper === results || footerWrapper === riskBadge.parentElement) return;
  if (riskBadge.nextElementSibling !== footerWrapper) {
    riskBadge.insertAdjacentElement('afterend', footerWrapper);
  }

  footerWrapper.style.margin = '8px -16px 0 -16px';
  const footerImg = root.querySelector('#prc-footer-bg-img');
  if (footerImg) footerImg.style.borderRadius = '0';
}

function moveCreatorDetailsUnderAppHeader(root = document) {
  const results = root.querySelector('#prc-results');
  if (!results) return;

  const creatorRow = results.querySelector('#prc-creator');
  const tokenBg = results.querySelector('#prc-token-bg');
  const creatorSection = creatorRow?.closest('.prc-section');
  const tokenInfoSection = tokenBg?.closest('.prc-section');
  if (!creatorSection || !tokenInfoSection || creatorSection === tokenInfoSection) return;

  const footerSection = results.querySelector('.prc-footer-section');
  const footerWrapper = footerSection?.parentElement;
  if (footerWrapper && footerWrapper.parentElement === results) {
    if (footerWrapper.nextElementSibling !== creatorSection) {
      footerWrapper.insertAdjacentElement('afterend', creatorSection);
    }
    if (creatorSection.nextElementSibling !== tokenInfoSection) {
      creatorSection.insertAdjacentElement('afterend', tokenInfoSection);
    }
    return;
  }

  if (tokenInfoSection.previousElementSibling !== creatorSection) {
    tokenInfoSection.insertAdjacentElement('beforebegin', creatorSection);
  }
}

function moveTokenRiskAfterCreator(root = document) {
  const results = root.querySelector('#prc-results');
  if (!results) return;

  const creatorRow = results.querySelector('#prc-creator');
  const scoreRow = results.querySelector('#prc-score');
  const creatorSection = creatorRow?.closest('.prc-section');
  const tokenRiskSection = scoreRow?.closest('.prc-section');
  if (!creatorSection || !tokenRiskSection || creatorSection === tokenRiskSection) return;

  if (creatorSection.nextElementSibling !== tokenRiskSection) {
    creatorSection.insertAdjacentElement('afterend', tokenRiskSection);
  }
}

function moveSealVoteUnderAppHeader(root = document) {
  const results = root.querySelector('#prc-results');
  if (!results) return;

  const voteSlot = results.querySelector('#prc-seal-vote-slot');
  if (!voteSlot) return;

  const creatorRow = results.querySelector('#prc-creator');
  const creatorSection = creatorRow?.closest('.prc-section');
  const footerSection = results.querySelector('.prc-footer-section');
  const footerWrapper = footerSection?.parentElement;

  if (footerWrapper && footerWrapper.parentElement === results) {
    if (footerWrapper.nextElementSibling !== voteSlot) {
      footerWrapper.insertAdjacentElement('afterend', voteSlot);
    }
    if (creatorSection && voteSlot.nextElementSibling !== creatorSection) {
      voteSlot.insertAdjacentElement('afterend', creatorSection);
    }
    return;
  }

  if (creatorSection) {
    creatorSection.insertAdjacentElement('beforebegin', voteSlot);
  }
}

function injectUI() {
  if (!isContextValid()) return;
  analysisGeneration++; // Cancel any in-flight retries from previous token
  console.log(`PRC: injectUI called, generation=${analysisGeneration}`);

  const res = getMintFromURL();
  const mint = res?.address || "Unknown";
  resetLoaderProgress();

  const old = document.getElementById('padre-rug-check-ui');
  const wasHiddenBeforeInject = !!(old && window.getComputedStyle(old).display === 'none');
  if (old) {
    old.style.display = 'block';
    // If it exists but we just want to restart/show, don't remove the whole thing
    const results = document.getElementById('prc-results');
    const loading = document.getElementById('prc-loading');
    if (results) results.style.display = 'none';
    if (loading) {
      loading.style.display = 'flex';
      loading.style.backgroundImage = `url('${safeGetURL('loader.png')}')`;
      loading.innerHTML = `
        <span id="prc-loader-label" class="prc-glow-text prc-loader-label">Hermeto is analyzing this token...</span>
        <div class="prc-loader-progress">
          <span id="prc-loader-progress-fill" class="prc-loader-progress-fill"></span>
        </div>
      `;
    }
    const mainUi = document.getElementById('prc-main-ui');
    const miniBtn = document.getElementById('prc-minimized-btn');
    if (mainUi) mainUi.style.display = 'block';
    if (miniBtn) miniBtn.style.display = 'none';

    // We still need to run the analysis part, which is defined below in the original code.
    // For simplicity with the current structure, we'll continue into the DOM creation part 
    // BUT we will change how 'container' is handled.
  }

  // To avoid massive refactor of nested performAnalysis, we still define it inside.
  // But we skip re-creation if same token and already in DOM.
  let container = old;
  if (!container) {
    container = document.createElement('div');
    container.id = 'padre-rug-check-ui';
    document.body.appendChild(container);
  }

  // CRITICAL: Only redraw the template if it's a new container or a new token.
  // This prevents the "infinite loading" loop when the MutationObserver triggers updates.
  if (!old || currentAnalyzingMint !== mint) {
    container.innerHTML = `
      <div id="prc-main-ui">
        <div class="prc-header" style="position: relative; padding: 0; background: none; border-bottom: none;">
        <div style="position: relative; display: inline-block; width: 100%; -webkit-mask-image: linear-gradient(to bottom, black calc(100% - 10px), transparent 100%); mask-image: linear-gradient(to bottom, black calc(100% - 10px), transparent 100%); line-height: 0;">
          <img src="${safeGetURL('logo.png')}" style="width: 100%; height: auto; display: block; border-radius: 16px 16px 0 0;" alt="Header Logo"/>
          <img class="prc-header-hat-img" src="${safeGetURL('hat.png')}" alt="Header Hat"/>
          <img id="prc-header-title-img" class="prc-header-title-img" src="${safeGetURL('hermeto_title.png')}" alt="Hermeto Title"/>
          <div id="prc-header-phrase" class="prc-header-phrase"></div>
          <div class="prc-particles-container">
            <div class="prc-particle"></div>
            <div class="prc-particle"></div>
            <div class="prc-particle"></div>
            <div class="prc-particle"></div>
            <div class="prc-particle"></div>
            <div class="prc-particle"></div>
            <div class="prc-particle"></div>
            <div class="prc-particle"></div>
          </div>
        </div>
        <div id="prc-version-label" style="position: absolute; bottom: 6px; left: 0; right: 0; text-align: center; font-size: 8px; color: rgba(255,255,255,0.4); text-transform: uppercase; font-weight: 400; pointer-events: none; z-index: 10;"></div>
        <div id="prc-header-actions" class="prc-header-actions">
          <button id="prc-settings" class="prc-header-btn" title="Settings">\u2699\ufe0f</button>
          <a href="https://hermeto.fun" target="_blank" class="prc-header-btn" title="hermeto.fun">\ud83c\udf10</a>
          <a href="https://x.com/hermetosol" target="_blank" rel="noopener noreferrer" class="prc-header-btn" title="X @hermetosol" aria-label="X @hermetosol">
            <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor" aria-hidden="true">
              <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"></path>
            </svg>
          </a>
          <button id="prc-contract" class="prc-header-btn" title="Contract">\u2212</button>
          <button id="prc-close" class="prc-header-btn close" title="Close">\u00d7</button>
        </div>
      </div>
      <div id="prc-settings-panel" style="display:none; margin: 10px 16px 14px 16px; padding-bottom: 12px; border-bottom: 1px solid rgba(255, 255, 255, 0.04);">
        <div class="prc-row">
          <span class="prc-label">Auto-open on Padre.gg</span>
          <label class="prc-switch">
            <input type="checkbox" id="prc-auto-open-padre" checked>
            <span class="prc-slider"></span>
          </label>
        </div>
        <div class="prc-row prc-recovery-row">
          <span class="prc-label">Backup Code</span>
          <div class="prc-recovery-wrap">
            <input id="prc-recovery-code" class="prc-recovery-input" type="password" readonly placeholder="Generating...">
            <button id="prc-recovery-toggle" class="prc-recovery-btn" type="button" title="Show code">Show</button>
            <button id="prc-recovery-copy" class="prc-recovery-btn" type="button" title="Copy code">Copy</button>
          </div>
        </div>
        <div class="prc-recovery-note">Save this code. You can restore your stats on a new install with it.</div>
      </div>
      <div class="prc-content">
        <div id="prc-screen-main" class="prc-screen prc-screen-active">
        <div id="prc-scammer-alert">
          <span id="prc-scammer-alert-text">&#128680; SCAMMER ALERT (${ruggerThresholdAuthoritative ? currentRuggerThreshold : '--'}+ FLAGS) &#128680;</span>
        </div>
        <div id="prc-loading" class="prc-loading prc-loading-bg-pulse" style="background-image: url('${safeGetURL('loader.png')}'); background-position: center; background-size: cover; background-repeat: no-repeat; min-height: 220px; border-radius: 0; margin: 8px -16px; width: calc(100% + 32px); position: relative; overflow: hidden; border: none; display: flex;">
          <span id="prc-loader-label" class="prc-glow-text prc-loader-label">Hermeto is analyzing this token...</span>
          <div class="prc-loader-progress">
            <span id="prc-loader-progress-fill" class="prc-loader-progress-fill"></span>
          </div>
        </div>
        <div id="prc-results" style="display:none;">
          <div id="prc-cto-badge" class="prc-risk-badge cto" style="display:none; background: rgba(0, 191, 255, 0.15); color: #00bfff; border: 1px solid rgba(0, 191, 255, 0.3); margin-bottom: 8px;">COMMUNITY TAKE OVER</div>
          <button id="prc-open-stats" class="prc-stats-open-btn">
            <img src="${safeGetURL('button_badges.png')}" alt="Stats">
            <span>Your stats</span>
          </button>
          <div id="prc-risk-badge" class="prc-risk-badge"></div>
          <div id="prc-seal-msg" class="prc-seal-msg"></div>

          <div class="prc-section prc-token-info-section">
            <div id="prc-token-bg"></div>
            <div class="prc-section-title">Token Info <span class="prc-section-arrow">\u25be</span></div>
            <div class="prc-row"><span class="prc-label">Name</span><span id="prc-name" class="prc-value">-</span></div>
            <div class="prc-row"><span class="prc-label">Symbol</span><span id="prc-symbol" class="prc-value">-</span></div>
            <div class="prc-row">
              <span class="prc-label">Mint</span>
              <div style="display: flex; align-items: center; gap: 4px;">
                <span id="prc-mint" class="prc-value mono">-</span>
                <button id="prc-copy-mint" class="prc-copy-btn" title="Copy Mint">\ud83d\udccb</button>
              </div>
            </div>
          </div>

          <div id="prc-insights-section" class="prc-section" style="display:none;">
            <div class="prc-section-title">Safety Insights <span class="prc-section-arrow">\u25be</span></div>
            <div id="prc-insights-list" class="prc-insights-list"></div>
          </div>

          <div id="prc-lp-section" class="prc-section" style="display:none;">
            <div class="prc-section-title">LP Status (Locks/Burns) <span class="prc-section-arrow">\u25be</span></div>
            <div id="prc-lp-list" style="display: flex; flex-direction: column; gap: 4px;"></div>
          </div>

          <div class="prc-section">
            <div class="prc-section-title">Token Risk Analysis <span class="prc-section-arrow">\u25be</span></div>
            <div class="prc-row"><span class="prc-label">Score</span><span id="prc-score" class="prc-value">-</span></div>
            <div class="prc-row"><span class="prc-label">Distribution Risk</span><span id="prc-distribution-risk" class="prc-value">-</span></div>
            <div class="prc-row"><span class="prc-label">Token Maturity</span><span id="prc-token-maturity" class="prc-value">-</span></div>
            <div id="prc-token-lock-msg" class="prc-token-lock-msg" style="display:none;"></div>
            <div id="prc-risks-list" class="prc-risks-list"></div>
          </div>

          <div class="prc-section">
            <div class="prc-section-title">Creator Details <span class="prc-section-arrow">\u25be</span></div>
            <div class="prc-row">
              <span class="prc-label">Address</span>
              <div style="display: flex; align-items: center; gap: 4px;">
                <span id="prc-creator" class="prc-value mono">-</span>
                <button id="prc-copy-creator" class="prc-copy-btn" title="Copy Address">\ud83d\udccb</button>
              </div>
            </div>
            <div class="prc-row"><span class="prc-label">Dev Held %</span><span id="prc-creator-pct" class="prc-value">-</span></div>
            <div id="prc-dev-bought-row" class="prc-row" style="display:none;"><span class="prc-label">Dev Bought</span><span id="prc-dev-bought" class="prc-value">-</span></div>
            <div id="prc-dev-sold-row" class="prc-row" style="display:none;"><span class="prc-label">Dev Sold</span><span id="prc-dev-sold" class="prc-value">-</span></div>
            <div class="prc-row"><span class="prc-label">Dev Tokens</span><span id="prc-creator-amount" class="prc-value">-</span></div>
            <div class="prc-row"><span class="prc-label">Platform</span><span id="prc-platform" class="prc-value">-</span></div>
            <div id="prc-creator-tokencount-msg" class="prc-creator-tokencount-msg" style="display:none;"></div>
            <div id="prc-creator-flags-alert" class="prc-creator-flags-alert" style="display:none;"></div>
            <div id="prc-creator-hunted-msg" class="prc-creator-hunted-msg" style="display:none;">You have hunted this creator</div>
            <div id="prc-seal-vote-slot" class="prc-seal-vote-slot" style="display:none;">
              <div id="prc-seal-note" class="prc-seal-note" style="display:none;"></div>
              <div id="prc-seal-vote-wrap" class="prc-seal-vote-wrap" style="display:none;">
                <button id="prc-seal-vote-btn" class="prc-seal-vote-btn">PROPOSE FOR <span class="prc-seal-btn-white">SEAL OF HERMETO</span></button>
                <span id="prc-seal-vote-count" class="prc-seal-vote-count">0 votes</span>
              </div>
            </div>
          </div>

          <div style="margin: 0 -16px -14px -16px;">
            <div class="prc-footer-section" data-mode="default">
              <div class="prc-footer-bg">
                <div style="width: 100%; height: 100%; -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 20px, black calc(100% - 20px), transparent 100%); mask-image: linear-gradient(to bottom, transparent 0, black 20px, black calc(100% - 20px), transparent 100%);">
                  <img id="prc-footer-bg-img" src="${safeGetURL('footer_back.png')}" alt="Footer Background" style="width:100%; height:100%; object-fit:cover; opacity: 0.8; border-radius: 0 0 16px 16px; display: block;">
                </div>
              </div>
              <div class="prc-state-particles" aria-hidden="true">
                <span class="prc-state-particle"></span>
                <span class="prc-state-particle"></span>
                <span class="prc-state-particle"></span>
                <span class="prc-state-particle"></span>
                <span class="prc-state-particle"></span>
                <span class="prc-state-particle"></span>
                <span class="prc-state-particle"></span>
                <span class="prc-state-particle"></span>
                <span class="prc-state-particle"></span>
                <span class="prc-state-particle"></span>
                <span class="prc-state-particle"></span>
                <span class="prc-state-particle"></span>
              </div>
              <div id="prc-seal-slot" class="prc-seal-slot" style="display:none;">
                <div id="prc-seal-badge-wrap" class="prc-seal-badge-wrap" style="display:none;">
                  <a href="https://hermeto.fun/seal.php" target="_blank" rel="noopener noreferrer" class="prc-seal-badge-link" title="Open Seal Of Hermeto">
                    <img src="${safeGetURL('seal.png')}" alt="Seal Of Hermeto" class="prc-seal-badge-img">
                  </a>
                </div>
              </div>
              <div id="prc-hunt-container" class="prc-hunt-container">
                <div id="prc-vote-overlay" class="prc-vote-overlay" style="display:none;">
                  <div class="prc-vote-overlay-card">
                    <div class="prc-spinner-glow prc-vote-spinner"></div>
                    <div id="prc-vote-overlay-text" class="prc-vote-overlay-text">Recording vote in database...</div>
                  </div>
                </div>
                <div class="prc-fire-container">
                  <div class="prc-fire-particle"></div>
                  <div class="prc-fire-particle"></div>
                  <div class="prc-fire-particle"></div>
                  <div class="prc-fire-particle"></div>
                  <div class="prc-fire-particle"></div>
                  <div class="prc-fire-particle"></div>
                  <div class="prc-fire-particle"></div>
                  <div class="prc-fire-particle"></div>
                </div>
                <div class="prc-skull-wrapper">
                <svg class="prc-progress-ring" width="100" height="100">
                  <circle class="prc-progress-ring-bg" stroke="rgba(255,255,255,0.1)" stroke-width="4" fill="transparent" r="45" cx="50" cy="50"/>
                  <circle id="prc-progress-ring-circle" class="prc-progress-ring-circle" stroke="#ff3838" stroke-width="4" fill="transparent" r="45" cx="50" cy="50"/>
                </svg>
                <img src="${safeGetURL('skull.png')}" alt="Skull" class="prc-skull-img">
              </div>
              <div id="prc-flag-count-text" class="prc-flag-count-text">0/${ruggerThresholdAuthoritative ? currentRuggerThreshold : '--'} Flags</div>
              <button id="prc-flag-btn" class="prc-hunt-btn">HUNT THIS DEPLOYER</button>
              <div id="prc-cooldown-label" style="display:none; color: #ff3838; font-size: 11px; margin-top: 8px; font-weight: 700; text-transform: uppercase; text-align: center; width: 100%;">
                You can hunt again<br>this allegedly rugger in <span id="prc-cooldown-timer">6:00 HS</span>
              </div>
            </div>
            <div id="prc-rugged-verified-msg" class="prc-rugged-verified-msg" style="display:none;">
              <span id="prc-rugged-text-1" style="display:none; color: #ff3838; font-size: 18px; font-weight: 900; text-transform: uppercase;">THIS CREATOR IS A RUGGER</span>
              <span id="prc-rugged-text-2" style="display:none; color: #fff; font-size: 24px; font-weight: 900; text-transform: uppercase;">RUN!</span>
              <button id="prc-appeal-btn" class="prc-appeal-btn">APPEAL THIS STATUS</button>
            </div>
          </div>
          </div>
        </div>
        </div>
        <div id="prc-screen-stats" class="prc-screen" style="display:none;">
          <div class="prc-stats-header">
            <button id="prc-stats-back" class="prc-stats-back-btn" title="Back">
              <svg class="prc-back-icon" viewBox="0 0 24 24" aria-hidden="true">
                <path d="M15 5L8 12L15 19" />
              </svg>
            </button>
            <div class="prc-stats-title">Stats</div>
          </div>
          <div class="prc-stats-metrics">
            <div class="prc-stats-metric" data-hint="Flags applied. Progress bar indicates how much you need to earn the next badge on this category.">
              <span class="label">Flags</span>
              <div class="prc-stats-progress"><span id="prc-stats-flags-progress" class="prc-stats-progress-fill"></span></div>
              <span id="prc-stats-flags" class="value">0</span>
            </div>
            <div class="prc-stats-metric prc-stats-metric-hunted" data-hint="Number of deployers you flagged turned into ruggers. Progress bar indicates how much you need to earn the next badge on this category.">
              <span class="label">Hunted Ruggers</span>
              <div class="prc-stats-progress"><span id="prc-stats-hunts-progress" class="prc-stats-progress-fill"></span></div>
              <span id="prc-stats-hunts" class="value">0</span>
            </div>
            <div class="prc-stats-metric" data-hint="Amount of seal proposals made by this user. Progress bar indicates how much you need to earn the next badge on this category.">
              <span class="label">Seal Proposals</span>
              <div class="prc-stats-progress"><span id="prc-stats-seal-proposals-progress" class="prc-stats-progress-fill"></span></div>
              <span id="prc-stats-seal-proposals" class="value">0</span>
            </div>
            <div class="prc-stats-metric" data-hint="Tokens granted Seal of Hermeto where this user voted before grant. Progress bar indicates how much you need to earn the next badge on this category.">
              <span class="label">Grants</span>
              <div class="prc-stats-progress"><span id="prc-stats-seal-grants-progress" class="prc-stats-progress-fill"></span></div>
              <span id="prc-stats-seal-grants" class="value">0</span>
            </div>
          </div>
          <div class="prc-stats-avatar-wrap">
            <button id="prc-flex-btn" class="prc-flex-btn">Download Flex Card</button>
            <img id="prc-stats-avatar" class="prc-stats-avatar" src="${safeGetURL('logo.png')}" alt="User badge level">
          </div>
          <div class="prc-stats-score-card" data-hint="You receive points for flags, hunted ruggers, seal proposals, seal grants, and earned badges.">
            <div class="prc-stats-score-row">
              <span class="label">SCORE</span>
              <div class="prc-stats-progress"><span id="prc-stats-score-progress" class="prc-stats-progress-fill"></span></div>
              <span id="prc-stats-score" class="value">0</span>
            </div>
            <div id="prc-stats-score-next" class="prc-stats-score-next">0 points to next level</div>
          </div>
          <div id="prc-earned-titles" class="prc-earned-titles"></div>
          <div id="prc-badges-gallery" class="prc-badges-gallery"></div>
          <div id="prc-badge-detail" class="prc-badge-detail" style="display:none;">
            <div id="prc-badge-detail-stage" class="prc-badge-detail-stage">
              <div class="prc-badge-detail-glow"></div>
              <img id="prc-badge-detail-image" class="prc-badge-detail-image" src="" alt="Badge detail">
              <span class="prc-badge-particle"></span>
              <span class="prc-badge-particle"></span>
              <span class="prc-badge-particle"></span>
              <span class="prc-badge-particle"></span>
              <span class="prc-badge-particle"></span>
              <span class="prc-badge-particle"></span>
              <span class="prc-badge-particle"></span>
              <span class="prc-badge-particle"></span>
              <span class="prc-badge-particle"></span>
            </div>
            <div id="prc-badge-detail-state" class="prc-badge-detail-state">Earned</div>
            <div class="prc-badge-detail-label">Required action</div>
            <div id="prc-badge-detail-action" class="prc-badge-detail-action"></div>
            <div class="prc-badge-detail-label">Points Given</div>
            <div id="prc-badge-detail-points" class="prc-badge-detail-action"></div>
            <button id="prc-badge-detail-close" class="prc-badge-detail-close">Close</button>
          </div>
        </div>
      </div>
      <div id="prc-appeal-modal">
        <div class="prc-appeal-content">
          <div class="prc-appeal-title">
            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ff3838" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
            CREATOR APPEAL
          </div>
          <div class="prc-appeal-subtitle">If you believe this flag is a mistake, provide your contact wallet and describe the situation for admin review.</div>
          <div class="prc-appeal-field">
            <label class="prc-appeal-label">Contact Wallet (SOL)</label>
            <input type="text" id="prc-appeal-wallet" class="prc-appeal-input" placeholder="Enter your Solana wallet...">
          </div>
          <div class="prc-appeal-field">
            <label class="prc-appeal-label">Situation Description</label>
            <textarea id="prc-appeal-description" class="prc-appeal-input prc-appeal-textarea" placeholder="Explain why you think you been flagged incorrectly..."></textarea>
          </div>
          <div class="prc-appeal-actions">
            <button id="prc-appeal-cancel-btn" class="prc-appeal-cancel">CANCEL</button>
            <button id="prc-appeal-submit-btn" class="prc-appeal-submit">SUBMIT APPEAL</button>
          </div>
        </div>
      </div>
      <div id="prc-minimized-btn" style="display:none; position:fixed; bottom:20px; right:20px; width:50px; height:50px; background:#111; border:2px solid #00ffa3; border-radius:50%; z-index:99999; cursor:pointer; box-shadow: 0 0 15px rgba(0, 255, 163, 0.4); align-items:center; justify-content:center; overflow:hidden;">
        <img src="${safeGetURL('logo.png')}" style="width:100%; height:100%; object-fit:cover;" alt="Open PRC"/>
      </div>
    `;
  }

  const windowContainer = document.getElementById('padre-rug-check-ui');
  moveFooterUnderRiskBadge(windowContainer || document);
  moveCreatorDetailsUnderAppHeader(windowContainer || document);
  moveTokenRiskAfterCreator(windowContainer || document);
  moveSealVoteUnderAppHeader(windowContainer || document);
  const mainUi = document.getElementById('prc-main-ui');
  const miniBtn = document.getElementById('prc-minimized-btn');
  const header = windowContainer?.querySelector('.prc-header');

  if (header) {
    header.classList.add('prc-drag-handle');
  }

  if (windowContainer && !windowContainer.dataset.prcFailRefreshBound) {
    windowContainer.addEventListener('click', (event) => {
      const btn = event.target?.closest ? event.target.closest('#prc-fail-refresh-btn') : null;
      if (!btn) return;
      event.preventDefault();
      event.stopPropagation();
      const fn = prcRetryAnalysisFn;
      if (typeof fn === 'function') {
        fn();
        return;
      }
      forceRetryCurrentAnalysis();
    });
    windowContainer.dataset.prcFailRefreshBound = '1';
  }

  const triggerOpenAnimation = () => {
    if (!windowContainer) return;
    windowContainer.classList.remove('prc-closing');
    windowContainer.classList.remove('prc-opening');
    void windowContainer.offsetWidth;
    windowContainer.classList.add('prc-opening');
    setTimeout(() => {
      if (!windowContainer) return;
      windowContainer.classList.remove('prc-opening');
    }, 560);
  };

  if (!old || wasHiddenBeforeInject) {
    triggerOpenAnimation();
  }

  const applySavedOrDockedPosition = () => {
    if (!windowContainer) return;
    safeStorage()?.get([PRC_PANEL_POS_KEY], (result) => {
      const saved = result?.[PRC_PANEL_POS_KEY];
      const isCustomSaved = saved && saved.mode === 'custom';
      if (isCustomSaved && Number.isFinite(saved.left) && Number.isFinite(saved.top)) {
        const panelWidth = windowContainer.offsetWidth || 340;
        const panelHeight = windowContainer.offsetHeight || Math.max(320, window.innerHeight - 100);
        const pos = clampPanelPosition(saved.left, saved.top, panelWidth, panelHeight);
        windowContainer.style.left = `${pos.left}px`;
        windowContainer.style.top = `${pos.top}px`;
        windowContainer.style.right = 'auto';
        return;
      }
      applyDockedPosition(windowContainer);
    });
  };

  applySavedOrDockedPosition();

  if (!prcPanelResizeBound) {
    window.addEventListener('resize', () => {
      const panel = document.getElementById('padre-rug-check-ui');
      if (!panel) return;
      safeStorage()?.get([PRC_PANEL_POS_KEY], (result) => {
        const saved = result?.[PRC_PANEL_POS_KEY];
        const isCustomSaved = saved && saved.mode === 'custom';
        if (isCustomSaved && Number.isFinite(saved.left) && Number.isFinite(saved.top)) {
          const panelWidth = panel.offsetWidth || 340;
          const panelHeight = panel.offsetHeight || Math.max(320, window.innerHeight - 100);
          const pos = clampPanelPosition(saved.left, saved.top, panelWidth, panelHeight);
          panel.style.left = `${pos.left}px`;
          panel.style.top = `${pos.top}px`;
          panel.style.right = 'auto';
          return;
        }
        applyDockedPosition(panel);
      });
    });
    prcPanelResizeBound = true;
  }

  if (header && !header.dataset.prcDragBound) {
    let dragging = false;
    let dragOffsetX = 0;
    let dragOffsetY = 0;

    const onDragMove = (e) => {
      if (!dragging || !windowContainer) return;
      const panelWidth = windowContainer.offsetWidth || 340;
      const panelHeight = windowContainer.offsetHeight || Math.max(320, window.innerHeight - 100);
      const pos = clampPanelPosition(e.clientX - dragOffsetX, e.clientY - dragOffsetY, panelWidth, panelHeight);
      windowContainer.style.left = `${pos.left}px`;
      windowContainer.style.top = `${pos.top}px`;
      windowContainer.style.right = 'auto';
    };

    const onDragEnd = () => {
      if (!dragging || !windowContainer) return;
      dragging = false;
      document.body.style.userSelect = '';
      document.removeEventListener('mousemove', onDragMove);
      document.removeEventListener('mouseup', onDragEnd);
      const left = parseFloat(windowContainer.style.left);
      const top = parseFloat(windowContainer.style.top);
      if (Number.isFinite(left) && Number.isFinite(top)) {
        safeStorage()?.set({
          [PRC_PANEL_POS_KEY]: { left, top, mode: 'custom', updatedAt: Date.now() }
        });
      }
    };

    header.onmousedown = (e) => {
      if (e.button !== 0) return;
      if (e.target.closest && e.target.closest('.prc-header-hat-img')) {
        e.preventDefault();
        e.stopPropagation();
        return;
      }
      if (e.target.closest && e.target.closest('#prc-header-actions')) return;
      if (!windowContainer) return;
      const rect = windowContainer.getBoundingClientRect();
      dragging = true;
      dragOffsetX = e.clientX - rect.left;
      dragOffsetY = e.clientY - rect.top;
      document.body.style.userSelect = 'none';
      document.addEventListener('mousemove', onDragMove);
      document.addEventListener('mouseup', onDragEnd);
      e.preventDefault();
    };

    header.ondblclick = () => {
      // Temporarily disabled: double-click auto-position reset.
      return;
    };

    header.dataset.prcDragBound = '1';
  }

  function fullyCloseUI() {
    userDismissedCurrentToken = true;
    if (!windowContainer) return;
    if (windowContainer.classList.contains('prc-closing')) return;
    windowContainer.classList.add('prc-closing');
    windowContainer.style.pointerEvents = 'none';
    setTimeout(() => {
      if (!windowContainer) return;
      windowContainer.style.display = 'none';
      windowContainer.classList.remove('prc-closing');
      windowContainer.style.pointerEvents = '';
    }, 620);
  }

  function minimizeUI() {
    mainUi.style.display = 'none';
    miniBtn.style.display = 'flex';
  }

  function expandUI() {
    miniBtn.style.display = 'none';
    mainUi.style.display = 'block';
    windowContainer.style.display = 'block';
    windowContainer.classList.remove('prc-closing');
    windowContainer.style.pointerEvents = '';
    triggerOpenAnimation();
    queueHeaderLoopRestart(30);
  }

  document.getElementById('prc-close').onclick = fullyCloseUI;
  miniBtn.onclick = expandUI;

  const settingsBtn = document.getElementById('prc-settings');
  const contractBtn = document.getElementById('prc-contract');
  const settingsPanel = document.getElementById('prc-settings-panel');
  const autoOpenToggles = {
    [PRC_SITE_TOGGLE_KEYS.padre]: document.getElementById('prc-auto-open-padre')
  };

  document.getElementById('prc-copy-creator').onclick = () => {
    const creatorText = document.getElementById('prc-creator').getAttribute('data-full');
    if (creatorText && creatorText !== "-") {
      navigator.clipboard.writeText(creatorText);
      const btn = document.getElementById('prc-copy-creator');
      btn.textContent = "\u2705";
      setTimeout(() => btn.textContent = "\ud83d\udccb", 1500);
    }
  };

  const copyMintBtn = document.getElementById('prc-copy-mint');
  if (copyMintBtn) {
    copyMintBtn.onclick = () => {
      const mintEl = document.getElementById('prc-mint');
      const mintText = mintEl?.getAttribute('title') || mintEl?.textContent || '';
      if (mintText && mintText !== '-') {
        navigator.clipboard.writeText(mintText);
        copyMintBtn.textContent = "\u2705";
        setTimeout(() => {
          copyMintBtn.textContent = "\ud83d\udccb";
        }, 1500);
      }
    };
  }

  if (chrome?.storage?.local) {
    const keys = [...Object.values(PRC_SITE_TOGGLE_KEYS), 'prcAutoOpen'];
    safeStorage()?.get(keys, (result) => {
      const legacyGlobalOff = result?.prcAutoOpen === false;
      if (!siteAutoOpenState) siteAutoOpenState = getDefaultSiteAutoOpenState();
      for (const key of Object.values(PRC_SITE_TOGGLE_KEYS)) {
        const enabled = legacyGlobalOff ? false : (result?.[key] !== false);
        siteAutoOpenState[key] = enabled;
        if (autoOpenToggles[key]) autoOpenToggles[key].checked = enabled;
      }
    });
  }

  bindRecoveryCodeFieldActions();
  void updateRecoveryCodeFieldUI();
  void ensureCloudProfileReady();

  settingsBtn.onclick = () => {
    settingsPanel.style.display = settingsPanel.style.display === 'none' ? 'block' : 'none';
  };

  let isContracted = false;
  const applyContractState = () => {
    if (!mainUi || !contractBtn) return;
    mainUi.classList.toggle('prc-contracted', isContracted);
    contractBtn.title = isContracted ? 'Expand' : 'Contract';
    contractBtn.textContent = isContracted ? '\u25a2' : '\u2212';
    if (isContracted && settingsPanel) settingsPanel.style.display = 'none';
  };
  applyContractState();
  if (contractBtn) {
    contractBtn.onclick = () => {
      isContracted = !isContracted;
      applyContractState();
    };
  }

  for (const key of Object.values(PRC_SITE_TOGGLE_KEYS)) {
    const toggle = autoOpenToggles[key];
    if (!toggle) continue;
    toggle.onchange = (e) => {
      const enabled = !!e.target.checked;
      if (!siteAutoOpenState) siteAutoOpenState = getDefaultSiteAutoOpenState();
      siteAutoOpenState[key] = enabled;
      if (chrome?.storage?.local) {
        safeStorage()?.set({ [key]: enabled });
      }
    };
  }

  const openStatsBtn = document.getElementById('prc-open-stats');
  const statsBackBtn = document.getElementById('prc-stats-back');
  const flexBtn = document.getElementById('prc-flex-btn');
  const badgeDetailCloseBtn = document.getElementById('prc-badge-detail-close');
  startHeaderTitlePhraseLoop();
  bindHeaderVisibilityRecovery();
  startHeaderHatEventLoop();

  if (openStatsBtn) {
    openStatsBtn.onclick = async () => {
      lastStatsSnapshot = await fetchUserStatsSnapshot();
      renderStatsUI(lastStatsSnapshot);
      setPanelScreen('stats');
    };
  }

  if (statsBackBtn) {
    statsBackBtn.onclick = () => setPanelScreen('main');
  }

  if (badgeDetailCloseBtn) {
    badgeDetailCloseBtn.onclick = () => {
      const detail = document.getElementById('prc-badge-detail');
      if (detail) detail.style.display = 'none';
    };
  }

  if (flexBtn) {
    flexBtn.onclick = async () => {
      if (flexBtn.dataset.loading === '1') return;
      flexBtn.dataset.loading = '1';
      const oldText = flexBtn.textContent;
      flexBtn.textContent = 'Preparing...';
      try {
        await handleFlexAction();
      } finally {
        flexBtn.textContent = oldText;
        flexBtn.dataset.loading = '0';
      }
    };
  }

  container.querySelectorAll('.prc-section-title').forEach(title => {
    title.onclick = () => {
      const section = title.closest('.prc-section');
      if (section) section.classList.toggle('collapsed');
    };
  });
  setPanelScreen('main');

  document.getElementById('prc-flag-btn').onclick = async () => {
    const creator = document.getElementById('prc-creator').getAttribute('data-full');
    if (creator && creator !== "Unknown") {
      const userId = await getOrCreateUserId();
      const uniqueUserId = getOrCreateUniqueUserIdLocal();
      const btn = document.getElementById('prc-flag-btn');
      const originalText = btn.textContent;
      huntUiSyncInProgress = true;
      setVoteRecordingState(true, "Recording vote in database...");
      btn.textContent = "HUNTING...";
      btn.disabled = true;
      btn.style.opacity = "0.7";
      btn.classList.add('prc-loading-btn');
      const mint = getMintFromURL()?.address;
      const tokenName = (document.getElementById('prc-name')?.textContent || '').trim();
      const tokenSymbol = (document.getElementById('prc-symbol')?.textContent || '').trim();
      safeSendMessage({ action: 'flagCreator', creator, mint, tokenName, tokenSymbol, userId, uniqueUserId, clientFingerprint: getClientFingerprint() }, (response) => {
        const finishSync = () => {
          huntUiSyncInProgress = false;
          setVoteRecordingState(false);
          btn.classList.remove('prc-loading-btn');
        };

        const loadFinalFlagState = (expectedState, rewardGlow = false) => {
          const expectedCount = Number(expectedState?.count);
          const expectedCooldownUntil = Number(expectedState?.cooldownUntil) || 0;
          const expectedThreshold = parseRuggerThreshold(
            expectedState?.ruggerThreshold ?? expectedState?.threshold ?? expectedState?.flagThreshold ?? expectedState?.scammerThreshold,
            currentRuggerThreshold
          );
          let pollAttempt = 0;

          const pollFinalState = () => {
            pollAttempt += 1;
            const syncText = pollAttempt > 1
              ? `Syncing final status... (${pollAttempt})`
              : "Syncing final status...";
            setVoteRecordingState(true, syncText);

            safeSendMessage({ action: 'getFlagState', creator, userId, uniqueUserId, clientFingerprint: getClientFingerprint() }, (state) => {
              if (!state || typeof state !== 'object' || state.count === undefined) {
                setTimeout(pollFinalState, 700);
                return;
              }

              const finalCount = Number(state.count) || 0;
              const finalCooldownUntil = Number(state.cooldownUntil) || 0;
              const finalThreshold = parseRuggerThreshold(
                state.ruggerThreshold ?? state.threshold ?? state.flagThreshold ?? state.scammerThreshold,
                expectedThreshold
              );

              const countReady = Number.isFinite(expectedCount) ? finalCount >= expectedCount : true;
              const cooldownReady = expectedCooldownUntil > 0
                ? finalCooldownUntil >= (expectedCooldownUntil - 1000)
                : true;

              if (!countReady || !cooldownReady) {
                setTimeout(pollFinalState, 700);
                return;
              }

              currentRuggerThreshold = finalThreshold;
              ruggerThresholdAuthoritative = true;
              captureConfirmedHuntState(creator, finalCount, finalCooldownUntil, currentRuggerThreshold);
              updateHuntUI(
                finalCount,
                false,
                false,
                creator,
                finalCooldownUntil,
                false,
                false,
                currentRuggerThreshold,
                false,
                false,
                true
              );
              fetchUserStatsSnapshot().then((snapshot) => {
                lastStatsSnapshot = snapshot;
                const statsScreen = document.getElementById('prc-screen-stats');
                if (statsScreen && statsScreen.style.display !== 'none') {
                  renderStatsUI(snapshot);
                }
                if (rewardGlow) triggerStatsRewardGlow(3);
              });
              finishSync();
            });
          };

          pollFinalState();
        };

        if (response && response.success) {
          const responseCooldownUntil = Number(response.cooldownUntil || response.userCooldownUntil || 0) || 0;
          loadFinalFlagState({
            count: response.count,
            cooldownUntil: responseCooldownUntil,
            ruggerThreshold: response.ruggerThreshold || currentRuggerThreshold
          }, true);
        } else if (response && (response.cooldownUntil || response.userCooldownUntil)) {
          const responseCooldownUntil = Number(response.cooldownUntil || response.userCooldownUntil || 0) || 0;
          loadFinalFlagState({
            count: response.count || 0,
            cooldownUntil: responseCooldownUntil,
            ruggerThreshold: response.ruggerThreshold || currentRuggerThreshold
          }, false);
        } else {
          btn.textContent = originalText;
          btn.disabled = false;
          btn.style.opacity = "1";
          finishSync();
        }
      });
    }
  };

  const sealVoteBtn = document.getElementById('prc-seal-vote-btn');
  if (sealVoteBtn) {
    sealVoteBtn.onclick = async (e) => {
      e.stopPropagation();
      if (sealVoteInProgress) return;
      const ctx = currentSealContext;
      if (!ctx || !ctx.eligible || !ctx.mint || !ctx.creator) return;

      sealVoteInProgress = true;
      setSealFooterVoteUI(
        Number(document.getElementById('prc-seal-vote-count')?.textContent?.replace(/\D/g, '') || 0),
        false,
        "Recording vote in database..."
      );

      const userId = await getOrCreateUserId();
      const response = await requestMessageAsync({
        action: 'voteSealOfHermeto',
        mint: ctx.mint,
        creator: ctx.creator,
        tokenSymbol: ctx.symbol,
        userId,
        clientFingerprint: getClientFingerprint()
      });

      const key = getSealCacheKey(ctx.mint, ctx.creator);
      sealStatusCache.delete(key);

      sealVoteInProgress = false;
      if (response && response.success === true) {
        const proposalRecorded = await recordSealProposalMint(ctx.mint);
        if (proposalRecorded) refreshStatsIfVisible();
      }
      const latestData = {
        mint: ctx.mint,
        creator: ctx.creator,
        symbol: ctx.symbol,
        tokenAgeDays: ctx.tokenAgeDays,
        riskLevel: (document.getElementById('prc-risk-badge')?.textContent || '').toLowerCase() === 'safe' ? 'safe' : 'warning',
        flagCount: Number((document.getElementById('prc-flag-count-text')?.textContent || '0').split('/')[0]) || 0,
        isSurvivor: document.querySelector('.prc-footer-section')?.getAttribute('data-mode') === 'cto'
      };

      if (response && response.success === false && response.message) {
        setSealFooterVoteUI(Number(response.votes) || 0, Boolean(response.canVote), String(response.message));
      } else {
        await refreshSealFooterForToken(latestData, true);
      }
    };
  }

  const appealModal = document.getElementById('prc-appeal-modal');
  const appealBtn = document.getElementById('prc-appeal-btn');
  const appealSubmitBtn = document.getElementById('prc-appeal-submit-btn');
  const appealCancelBtn = document.getElementById('prc-appeal-cancel-btn');

  if (appealBtn) appealBtn.onclick = (e) => { e.stopPropagation(); appealModal.classList.add('active'); };
  function closeAppealModal() {
    appealModal.classList.remove('active');
    document.getElementById('prc-appeal-wallet').value = '';
    document.getElementById('prc-appeal-description').value = '';
  }
  if (appealCancelBtn) appealCancelBtn.onclick = (e) => { e.stopPropagation(); closeAppealModal(); };
  if (appealModal) {
    appealModal.onclick = (e) => {
      if (e.target === appealModal) closeAppealModal();
      e.stopPropagation();
    };
  }

  if (appealSubmitBtn) {
    appealSubmitBtn.onclick = (e) => {
      e.stopPropagation();
      const creator = document.getElementById('prc-creator').getAttribute('data-full');
      const wallet = document.getElementById('prc-appeal-wallet').value.trim();
      const description = document.getElementById('prc-appeal-description').value.trim();
      if (!wallet || !description) { alert("Please fill in both fields."); return; }
      appealSubmitBtn.textContent = "SUBMITTING...";
      appealSubmitBtn.disabled = true;
      safeSendMessage({ action: 'submitAppeal', creator, wallet, description }, (response) => {
        if (response && response.success) { alert("Appeal submitted successfully!"); closeAppealModal(); }
        else alert("Error submitting appeal.");
        appealSubmitBtn.textContent = "SUBMIT APPEAL";
        appealSubmitBtn.disabled = false;
      });
    };
  }

  const da = getPadreDA(res?.node);
  const myGeneration = analysisGeneration;
  const analysisStartedAt = Date.now();
  prcRetryAnalysisFn = () => {
    forceRetryCurrentAnalysis();
  };
  const performAnalysis = (retryCount = 0) => {
    const MAX_MINT_RETRIES = 40;
    const MAX_MINT_RETRIES_PADRE = 24;
    const MAX_ANALYSIS_RETRIES = 4;
    const MAX_ANALYSIS_RETRIES_PADRE = 6;
    const ANALYSIS_TIMEOUT_MS = 28000;
    const ABSOLUTE_FAIL_AFTER_MS = 90000;
    const isPadre = isPadreHostContext();
    const mintRetryLimit = isPadre ? MAX_MINT_RETRIES_PADRE : MAX_MINT_RETRIES;
    const analysisRetryLimit = isPadre ? MAX_ANALYSIS_RETRIES_PADRE : MAX_ANALYSIS_RETRIES;
    if (myGeneration !== analysisGeneration) return;
    if ((Date.now() - analysisStartedAt) > ABSOLUTE_FAIL_AFTER_MS) {
      if (isPadre) {
        renderLocalFallbackUI(buildLocalFallbackAnalysis({
          mint: mint || '',
          da: da,
          devStats: getPadreDevStats(da || null),
          devSoldStrict: getPadreDevSoldStrict(da || null),
          devMintedCount: getPadreDevMintedCount(),
          reason: 'analysis exceeded time limit'
        }), 'analysis exceeded time limit');
      } else {
        showUnreadableCreatorState();
      }
      return;
    }
    setLoaderProgress(Math.min(92, 10 + (retryCount * 6)), "Hermeto is analyzing this token...");
    const liveMint = getMintFromURL()?.address;
    const mintToAnalyze = (liveMint || mint || "").trim();
    const hasStrictMint = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(mintToAnalyze);
    const hasValidMint = hasStrictMint;
    if (!hasValidMint) {
      setLoaderProgress(Math.min(60, 8 + (retryCount * 5)), "Hermeto is analyzing this token...");
      // On Padre, token panels can mount lazily; keep waiting a bit longer instead of hard-failing.
      if (retryCount < mintRetryLimit) {
        const nextRetry = retryCount + 1;
        const delay = retryCount < MAX_MINT_RETRIES ? 280 : 900;
        setTimeout(() => performAnalysis(nextRetry), delay);
      } else {
        if (isPadre) {
          const fallback = buildLocalFallbackAnalysis({
            mint: mintToAnalyze || mint || '',
            da: getPadreDA(res?.node || null) || '',
            devStats: getPadreDevStats(getPadreDA(res?.node || null) || null),
            devSoldStrict: getPadreDevSoldStrict(getPadreDA(res?.node || null) || null),
            devMintedCount: getPadreDevMintedCount(),
            reason: 'token address not detected from UI yet'
          });
          renderLocalFallbackUI(fallback, 'token address not detected from UI yet');
        } else {
          showUnreadableCreatorState();
        }
      }
      return;
    }
    let devStats = getPadreBestDevStats(da || null);
    let devSoldStrict = getPadreDevSoldStrict(da || null);
    const padreDevHoldingPct = getPadreDevHoldingPct();
    const quickDevStats = getPadreDevStatsQuickFallback(da || null);
    if (quickDevStats) {
      const quickSold = Number(quickDevStats.sold) || 0;
      const currentSold = Number(devStats?.sold) || 0;
      const quickConf = Number(quickDevStats?.confidence) || 0;
      const currentConf = Number(devStats?.confidence) || 0;
      const canPromoteQuick = !devStats || (
        quickDevStats.hasSold &&
        quickSold > 15 &&
        quickConf >= (currentConf + 20) &&
        quickSold > currentSold
      );
      if (canPromoteQuick) {
        devStats = quickDevStats;
      }
    }
    const devMintedCount = getPadreDevMintedCount();
    // Blockchain-first: do not block analysis waiting for UI scrape readiness.
    analysisInFlightMint = mintToAnalyze;
    getOrCreateUserId().then((userId) => {
      const uniqueUserId = getOrCreateUniqueUserIdLocal();
      let finished = false;
      const finalizeOnce = (fn) => {
        if (finished) return;
        finished = true;
        fn();
      };
      const requestTimeout = setTimeout(() => {
        finalizeOnce(() => {
          if (analysisInFlightMint === mintToAnalyze) analysisInFlightMint = null;
          if (myGeneration !== analysisGeneration) return;
          if (retryCount < analysisRetryLimit) {
            setLoaderProgress(Math.min(95, 30 + (retryCount * 12)), "Hermeto is analyzing this token...");
            const nextRetry = retryCount + 1;
            const delay = retryCount < MAX_ANALYSIS_RETRIES ? 420 : 1100;
            setTimeout(() => performAnalysis(nextRetry), delay);
          } else {
            if (isPadre) {
              renderLocalFallbackUI(buildLocalFallbackAnalysis({
                mint: mintToAnalyze,
                da: da,
                devStats: devStats,
                devSoldStrict: devSoldStrict,
                devMintedCount: devMintedCount,
                reason: 'analysis request timed out'
              }), 'analysis request timed out');
            } else {
              showUnreadableCreatorState();
            }
          }
        });
      }, ANALYSIS_TIMEOUT_MS);

      safeSendMessage({
        action: 'analyzeToken',
        mint: mintToAnalyze,
        padreDA: da,
        devStats: devStats,
        padreDevMintedCount: devMintedCount,
        padreDevSoldStrict: devSoldStrict,
        padreDevHoldingPct: padreDevHoldingPct,
        userId,
        uniqueUserId,
        clientFingerprint: getClientFingerprint()
      }, (resp) => {
        finalizeOnce(() => {
          clearTimeout(requestTimeout);
          if (analysisInFlightMint === mintToAnalyze) analysisInFlightMint = null;
          if (myGeneration !== analysisGeneration) return;
          if (resp && resp.success) {
            completeLoaderThen(() => {
              if (myGeneration !== analysisGeneration) return;
              updateUI(resp);
            });
          } else {
            if (retryCount < analysisRetryLimit) {
              setLoaderProgress(Math.min(95, 30 + (retryCount * 12)), "Hermeto is analyzing this token...");
              const nextRetry = retryCount + 1;
              const delay = retryCount < MAX_ANALYSIS_RETRIES ? 420 : 1100;
              setTimeout(() => performAnalysis(nextRetry), delay);
            } else {
              if (isPadre) {
                renderLocalFallbackUI(buildLocalFallbackAnalysis({
                  mint: mintToAnalyze,
                  da: da,
                  devStats: devStats,
                  devSoldStrict: devSoldStrict,
                  devMintedCount: devMintedCount,
                  reason: String(resp?.error || resp?.message || 'analysis unavailable')
                }), String(resp?.error || resp?.message || 'analysis unavailable'));
              } else {
                showUnreadableCreatorState();
              }
            }
          }
        });
      });
    });
  };
  performAnalysis();
}

let cooldownInterval = null;
const PRC_LOCAL_COOLDOWN_KEY = PRC_CLOUD_LOCAL_COOLDOWN_STORAGE_KEY;
const PRC_HUNT_COOLDOWN_WINDOW_MS = 6 * 60 * 60 * 1000;

function getStoredCreatorCooldown(creatorAddress) {
  if (!creatorAddress) return 0;
  try {
    const raw = localStorage.getItem(PRC_LOCAL_COOLDOWN_KEY);
    if (!raw) return 0;
    const map = JSON.parse(raw);
    if (!map || typeof map !== 'object') return 0;
    const value = Number(map[creatorAddress]) || 0;
    return value > Date.now() ? value : 0;
  } catch (_) {
    return 0;
  }
}

function setStoredCreatorCooldown(creatorAddress, until) {
  if (!creatorAddress) return;
  try {
    const raw = localStorage.getItem(PRC_LOCAL_COOLDOWN_KEY);
    const map = raw ? JSON.parse(raw) : {};
    const safeMap = (map && typeof map === 'object') ? map : {};
    const normalizedUntil = Number(until) || 0;
    if (normalizedUntil > Date.now()) {
      safeMap[creatorAddress] = normalizedUntil;
    } else {
      delete safeMap[creatorAddress];
    }
    localStorage.setItem(PRC_LOCAL_COOLDOWN_KEY, JSON.stringify(safeMap));
    void markCloudStatsLocalUpdated();
  } catch (_) { }
}

function setVoteRecordingState(isLoading, labelText = "Recording vote in database...") {
  const overlay = document.getElementById('prc-vote-overlay');
  const text = document.getElementById('prc-vote-overlay-text');
  if (text) text.textContent = labelText;
  if (!overlay) return;
  overlay.style.display = isLoading ? 'flex' : 'none';
}

function startCooldownTimer(until, creatorAddress, buttonText = "ON COOLDOWN") {
  const btn = document.getElementById('prc-flag-btn');
  const label = document.getElementById('prc-cooldown-label');
  const timer = document.getElementById('prc-cooldown-timer');

  if (!btn || !label || !timer) return;

  // Verify this timer is for the currently viewed creator
  const currentCreator = document.getElementById('prc-creator')?.getAttribute('data-full');
  if (currentCreator !== creatorAddress) return;
  setStoredCreatorCooldown(creatorAddress, until);

  btn.textContent = buttonText;
  btn.disabled = true;
  btn.style.opacity = "0.5";
  btn.style.display = 'none';
  label.style.display = 'block';

  if (cooldownInterval) clearInterval(cooldownInterval);

  function update() {
    // Stop if the user navigated to a different creator
    const viewingCreator = document.getElementById('prc-creator')?.getAttribute('data-full');
    if (viewingCreator !== creatorAddress) {
      clearInterval(cooldownInterval);
      cooldownInterval = null;
      return;
    }

    const now = Date.now();
    const diff = until - now;

    if (diff <= 0) {
      clearInterval(cooldownInterval);
      cooldownInterval = null;
      btn.textContent = "HUNT THIS DEPLOYER";
      btn.disabled = false;
      btn.style.opacity = "1";
      btn.style.display = '';
      label.style.display = 'none';
      setStoredCreatorCooldown(creatorAddress, 0);
      return;
    }

    const hours = Math.floor(diff / (1000 * 60 * 60));
    const mins = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
    const secs = Math.floor((diff % (1000 * 60)) / 1000);

    // Format: H:MM:SS HS or MM:SS HS
    let timeStr = "";
    if (hours > 0) {
      timeStr = `${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')} HS`;
    } else {
      timeStr = `${mins}:${secs.toString().padStart(2, '0')} HS`;
    }
    timer.textContent = timeStr;
  }

  update();
  cooldownInterval = setInterval(update, 1000);
}

function updateHuntUI(flagsCount, isSafe, isRugged, currentCreator, cooldownUntil, isSurvivor, isProven = false, ruggerThreshold = null, isCTO = false, isDumped = false, userFlagged = false) {
  const threshold = parseRuggerThreshold(ruggerThreshold, currentRuggerThreshold);
  if (hasValidThresholdValue(ruggerThreshold)) {
    currentRuggerThreshold = threshold;
    ruggerThresholdAuthoritative = true;
  }
  const countTag = document.getElementById('prc-flag-count-text');
  const scammerAlertText = document.getElementById('prc-scammer-alert-text');
  const huntContainer = document.getElementById('prc-hunt-container');
  const ruggedMsg = document.getElementById('prc-rugged-verified-msg');
  const circle = document.getElementById('prc-progress-ring-circle');
  const footerBgImg = document.getElementById('prc-footer-bg-img');
  const footerSection = document.querySelector('.prc-footer-section');
  const btn = document.getElementById('prc-flag-btn');
  const cooldownLabel = document.getElementById('prc-cooldown-label');
  const huntedMsgEl = document.getElementById('prc-creator-hunted-msg');

  if (huntedMsgEl) {
    huntedMsgEl.style.display = userFlagged ? 'block' : 'none';
  }

  // Cooldown guard: prefer creator-scoped local/confirmed state.
  // Backend cooldown can be global/noisy on some responses, so we only trust it
  // when we also have creator-local evidence in this browser/session.
  const backendCooldownUntil = Number(cooldownUntil) || 0;
  const localCooldownUntil = currentCreator ? getStoredCreatorCooldown(currentCreator) : 0;
  const now = Date.now();
  const protectedState = (
    lastConfirmedHuntState &&
    currentCreator &&
    lastConfirmedHuntState.creator === currentCreator &&
    Number(lastConfirmedHuntState.expiresAt) > now
  ) ? lastConfirmedHuntState : null;
  const confirmedCooldownUntil = protectedState ? (Number(protectedState.cooldownUntil) || 0) : 0;
  const backendWithinWindow = backendCooldownUntil > now && (backendCooldownUntil - now) <= (PRC_HUNT_COOLDOWN_WINDOW_MS + (10 * 60 * 1000));
  const shouldTrustBackendCooldown = !!(
    backendWithinWindow &&
    (
      localCooldownUntil > now ||
      confirmedCooldownUntil > now
    )
  );
  const effectiveCooldownUntil = Math.max(
    localCooldownUntil,
    confirmedCooldownUntil,
    shouldTrustBackendCooldown ? backendCooldownUntil : 0
  );

  if (userFlagged) {
    if (btn) btn.style.display = 'none';
    if (effectiveCooldownUntil && Date.now() < effectiveCooldownUntil && currentCreator) {
      startCooldownTimer(effectiveCooldownUntil, currentCreator, "ON COOLDOWN");
    } else {
      if (cooldownInterval) {
        clearInterval(cooldownInterval);
        cooldownInterval = null;
      }
      if (cooldownLabel) cooldownLabel.style.display = 'none';
    }
  } else if (effectiveCooldownUntil && Date.now() < effectiveCooldownUntil && currentCreator) {
    startCooldownTimer(effectiveCooldownUntil, currentCreator, "ON COOLDOWN");
  } else {
    if (cooldownInterval) {
      clearInterval(cooldownInterval);
      cooldownInterval = null;
    }
    if (cooldownLabel) cooldownLabel.style.display = 'none';
    if (btn) {
      btn.textContent = "HUNT THIS DEPLOYER";
      btn.disabled = false;
      btn.style.opacity = "1";
      btn.style.display = '';
    }
    if (currentCreator) setStoredCreatorCooldown(currentCreator, 0);
  }

  // Handle the SVG stroke-dashoffset math (circumference = 2 * pi * r = 2 * 3.14159 * 45 ~ 282)
  const radius = 45;
  const circumference = radius * 2 * Math.PI;
  if (circle) {
    circle.style.strokeDasharray = `${circumference} ${circumference}`;

    // Calculate percentage based on 5 max flags
    const percent = Math.min((flagsCount / threshold) * 100, 100);
    const offset = circumference - (percent / 100) * circumference;
    circle.style.strokeDashoffset = offset;

    // Color interpolation: Green (#00ffa3) to Red (#ff3838)
    // #00ffa3 -> RGB(0, 255, 163)
    // #ff3838 -> RGB(255, 56, 56)
    const factor = percent / 100;
    const r = Math.round(0 + (255 - 0) * factor);
    const g = Math.round(255 + (56 - 255) * factor);
    const b = Math.round(163 + (56 - 163) * factor);
    const colorStr = `rgb(${r}, ${g}, ${b})`;
    circle.style.stroke = colorStr;
    if (countTag) countTag.style.color = colorStr;
  }

  if (countTag) {
    const labelValue = ruggerThresholdAuthoritative ? String(currentRuggerThreshold) : '--';
    countTag.textContent = `${flagsCount}/${labelValue} Flags`;
  }
  if (scammerAlertText) {
    const labelValue = ruggerThresholdAuthoritative ? String(currentRuggerThreshold) : '--';
    scammerAlertText.textContent = `\uD83D\uDEA8 SCAMMER ALERT (${labelValue}+ FLAGS) \uD83D\uDEA8`;
  }
  if (footerSection) footerSection.setAttribute('data-mode', 'default');

  const hasBadSigns = !isSafe || isRugged || flagsCount > 0 || isDumped;
  const isClean = isSafe && flagsCount === 0 && !isRugged && !isDumped;

  if (isClean) {
    // COMPLETELY SAFE: Show OK background
    if (huntContainer) huntContainer.style.display = 'none';
    if (ruggedMsg) ruggedMsg.style.display = 'none';
    if (footerBgImg) {
      footerBgImg.src = safeGetURL('ok.png');
      footerBgImg.style.opacity = '1';
    }
    if (footerSection) footerSection.setAttribute('data-mode', 'ok');
  } else if (isSurvivor || isProven || isCTO) {
    // SURVIVOR/PROVEN/CTO (with some bad signs/risks): Show CTO background
    if (huntContainer) huntContainer.style.display = 'none';
    if (ruggedMsg) ruggedMsg.style.display = 'none';
    if (footerBgImg) {
      footerBgImg.src = safeGetURL('cto.png');
      footerBgImg.style.opacity = '1';
    }
    if (footerSection) footerSection.setAttribute('data-mode', 'cto');
  } else if (flagsCount >= threshold) {
    // COMMUNITY RUGGER: Show clean Appeal UI
    if (huntContainer) huntContainer.style.display = 'none';
    if (ruggedMsg) {
      ruggedMsg.style.display = 'flex';
      const t1 = document.getElementById('prc-rugged-text-1');
      const t2 = document.getElementById('prc-rugged-text-2');
      if (t1) t1.style.display = 'none';
      if (t2) t2.style.display = 'none';
    }
    if (footerBgImg) {
      footerBgImg.src = safeGetURL('rugger.png');
      footerBgImg.style.opacity = '1';
    }
    if (footerSection) footerSection.setAttribute('data-mode', 'rugger');
  } else {
    // NORMAL/UNCERTAIN: Show Standard Hunt UI
    if (huntContainer) huntContainer.style.display = 'flex';
    if (ruggedMsg) ruggedMsg.style.display = 'none';
    if (footerBgImg) {
      footerBgImg.src = safeGetURL('footer_back.png');
      footerBgImg.style.opacity = '0.8';
    }
  }
}

function updateUI(data) {
  const results = document.getElementById('prc-results');
  const loading = document.getElementById('prc-loading');
  const isPadreHost = (location.hostname || '').toLowerCase().includes('trade.padre.gg');
  let d = (data && typeof data === 'object') ? { ...data } : {};
  const liveRes = isPadreHost ? getMintFromURL() : null;
  const livePair = isPadreHost ? getPadreActivePairLabel() : '';
  const liveSymbol = (livePair && livePair.includes('/')) ? String(livePair.split('/')[0] || '').trim() : '';
  const liveDA = isPadreHost ? (getPadreDA(liveRes?.node || null) || '') : '';

  if (!String(d.mint || '').trim() && liveRes?.address) {
    d.mint = String(liveRes.address).trim();
  }
  if (isPadreHost && liveDA) {
    const currentCreator = String(d.creator || '').trim();
    const creatorUnknown = !currentCreator || /^unknown$/i.test(currentCreator) || currentCreator === '-';
    const creatorMismatch = currentCreator && String(currentCreator).toLowerCase() !== String(liveDA).toLowerCase();
    if (creatorUnknown || creatorMismatch) {
      d.creator = liveDA;
    }
  }
  if (
    (!String(d.symbol || '').trim() || String(d.symbol || '').trim() === '?' || String(d.symbol || '').trim() === '-') &&
    liveSymbol
  ) {
    d.symbol = liveSymbol;
  }
  if (
    (!String(d.name || '').trim() || /^unknown$/i.test(String(d.name || '').trim()) || String(d.name || '').trim() === '-') &&
    liveSymbol
  ) {
    d.name = liveSymbol;
  }
  if (
    (!String(d.platform || '').trim() || /^unknown$/i.test(String(d.platform || '').trim()) || String(d.platform || '').trim() === '-') &&
    isPadreHost
  ) {
    d.platform = 'Padre';
  }
  if (isPadreHost) {
    const liveDevHoldingPct = getPadreDevHoldingPct();
    if (Number.isFinite(Number(liveDevHoldingPct)) && Number(liveDevHoldingPct) >= 0) {
      const payloadPct = Number(d.devPct);
      if (!Number.isFinite(payloadPct) || payloadPct <= 0.0001 || Math.abs(payloadPct - Number(liveDevHoldingPct)) >= 0.20) {
        d.devPct = Number(liveDevHoldingPct);
      }
    }
  }
  const dexPairUnavailable = isPadreHost && d.dexPairAvailable === false;
  if (dexPairUnavailable) {
    showUnreadableCreatorState();
    return;
  }

  // Padre dead/low-signal tokens can look "safe" with almost no analyzable context.
  // If we only know the deployer wallet but core token intelligence is missing,
  // show explicit "can't analyze" state instead of SAFE.
  if (isPadreHost) {
    const creatorKnown = !!(String(d.creator || '').trim() && !/^unknown$/i.test(String(d.creator || '').trim()) && String(d.creator || '').trim() !== '-');
    const nameUnknown = !String(d.name || '').trim() || /^unknown$/i.test(String(d.name || '').trim()) || String(d.name || '').trim() === '-';
    const symbolUnknown = !String(d.symbol || '').trim() || String(d.symbol || '').trim() === '?' || String(d.symbol || '').trim() === '-';
    const scoreNum = Number(d.score) || 0;
    const risksCount = Array.isArray(d.risks) ? d.risks.length : 0;
    const devPctNum = Number(d.devPct) || 0;
    const holdersNum = Number(d.totalHolders) || 0;
    const lowSignalDeadToken = !!(
      creatorKnown &&
      nameUnknown &&
      symbolUnknown &&
      scoreNum <= 1 &&
      risksCount === 0 &&
      devPctNum <= 0.0001 &&
      holdersNum <= 1
    );
    if (lowSignalDeadToken) {
      showUnreadableCreatorState(`Hermeto can detect deployer ${shortAddr(String(d.creator || 'unknown'))}, but cannot reliably analyze this token (dead/low data).`);
      return;
    }
  }

  const hasRenderableCore = !!(
    String(d.mint || '').trim() ||
    String(d.symbol || '').trim() ||
    String(d.creator || '').trim()
  );
  if (!hasRenderableCore) {
    if (isPadreHost) {
      let localFallback = null;
      try {
        localFallback = buildLocalFallbackAnalysis({
          mint: String(d?.mint || liveRes?.address || '').trim(),
          da: liveDA,
          devStats: null,
          devSoldStrict: null,
          devMintedCount: getPadreDevMintedCount(),
          reason: 'upstream returned incomplete profile'
        });
      } catch (_) {
        localFallback = buildLocalFallbackAnalysis({
          mint: String(d?.mint || '').trim(),
          da: String(d?.creator || '').trim(),
          devStats: null,
          devSoldStrict: null,
          devMintedCount: null,
          reason: 'upstream returned incomplete profile'
        });
      }
      renderLocalFallbackUI(localFallback, 'upstream returned incomplete profile');
      return;
    } else {
      showUnreadableCreatorState();
      return;
    }
  }

  const livePadreDA = isPadreHost ? (liveDA || d.creator || null) : null;
  const soldStrict = isPadreHost ? getPadreDevSoldStrict(livePadreDA) : { found: false, sold: 0, source: 'none' };
  const soldStrictAmount = Number(soldStrict?.sold) || 0;
  const soldStrictSaysSold = !!(soldStrict.found && soldStrictAmount > 0.000001);
  const soldStrictSource = String(soldStrict?.source || '').toLowerCase();
  const soldStrictIsTargetBacked = /target/.test(soldStrictSource);
  const soldStatsFallback = isPadreHost ? getPadreBestDevStats(livePadreDA) : null;
  const soldStatsConfidence = Number(soldStatsFallback?.confidence) || 0;
  const soldStatsBought = Number(soldStatsFallback?.bought) || 0;
  const soldStatsAmount = Number(soldStatsFallback?.sold) || 0;
  const soldStatsMethod = String(soldStatsFallback?.method || '').toUpperCase();
  const soldStatsRatio = soldStatsBought > 0 ? (soldStatsAmount / soldStatsBought) : (soldStatsAmount > 0 ? 9 : 0);
  const soldStatsStructuredMethod = /(ROW-STRICT|TABLE|CELL-|BADGE-)/.test(soldStatsMethod);
  const soldStatsStrongSell = !!(
    soldStatsFallback &&
    soldStatsAmount > 15 &&
    soldStatsBought > 0 &&
    (
      (soldStatsStructuredMethod && soldStatsRatio >= 0.10) ||
      soldStatsConfidence >= 82 ||
      soldStatsRatio >= 0.45 ||
      soldStatsAmount > soldStatsBought
    )
  );
  const soldStatsStructuredNoSell = !!(
    soldStatsFallback &&
    soldStatsBought > 0 &&
    soldStatsAmount <= 0.000001 &&
    soldStatsStructuredMethod
  );
  const soldNoSellOverride = !!(
    soldStatsStructuredNoSell &&
    (
      !soldStrictSaysSold ||
      !soldStrictIsTargetBacked
    )
  );
  const backendSellSignal = false;
  const devSold = isPadreHost
    ? (
      soldNoSellOverride
        ? false
        : soldStrict.found
          ? soldStrictSaysSold
          : (soldStatsStrongSell ? true : (soldStatsStructuredNoSell ? false : backendSellSignal))
    )
    : !!d.devSold;
  const suppressSellNarrative = isPadreHost && !devSold && !d.devSold;
  if (Array.isArray(d.safetyInsights)) {
    const livePct = isPadreHost ? (Number(getPadreDevHoldingPct()) || 0) : 0;
    const pctNow = Math.max(Number(d.devPct) || 0, livePct);
    if (pctNow > (Number(d.devPct) || 0)) {
      d.devPct = pctNow;
    }
    if (pctNow > 0.0001) {
      const pctLabel = pctNow < 1 ? pctNow.toFixed(2) : pctNow.toFixed(1);
      d.safetyInsights = d.safetyInsights.map((msg) => {
        const text = String(msg || '');
        if (/holds\s*0%\s*supply/i.test(text)) {
          return text.replace(/holds\s*0%\s*supply/i, `holds ${pctLabel}% supply`);
        }
        return text;
      });
    }
  }

  const isSellNarrativeText = (text) => {
    const t = String(text || '').toLowerCase();
    return (
      t.includes('developer dump detected') ||
      t.includes('developer appears to have sold') ||
      t.includes('sold more than bought') ||
      t.includes('on-chain flow shows recent developer outflow') ||
      t.includes('developer likely sold') ||
      t.includes('side wallets')
    );
  };

  if (results) results.style.display = 'block';
  if (loading) loading.style.display = 'none';

  // Reset Seal message
  const sealMsgEl = document.getElementById('prc-seal-msg');
  if (sealMsgEl) {
    sealMsgEl.style.display = 'none';
    sealMsgEl.textContent = '';
  }

  // Token Info
  document.getElementById('prc-name').textContent = d.name || '-';
  document.getElementById('prc-symbol').textContent = d.symbol || '-';
  document.getElementById('prc-mint').textContent = shortAddr(d.mint);
  document.getElementById('prc-mint').title = d.mint;
  // Creator
  const creatorEl = document.getElementById('prc-creator');
  creatorEl.textContent = shortAddr(d.creator);
  creatorEl.setAttribute('data-full', d.creator);
  creatorEl.title = d.creator;

  const actPct = d.devPct || 0;
  const devPctEl = document.getElementById('prc-creator-pct');
  devPctEl.textContent = actPct.toFixed(2) + '%';
  devPctEl.className = 'prc-value ' + (actPct > 10 ? 'danger' : actPct > 5 ? 'warning' : 'safe');

  const localPadreMintedCount = isPadreHost ? getPadreDevMintedCount() : null;
  const hasLocalPadreMintedCount = Number.isFinite(Number(localPadreMintedCount)) && Number(localPadreMintedCount) > 0;
  const mergedDevMintedCount = hasLocalPadreMintedCount ? Number(localPadreMintedCount) : d.devMintedCount;
  const effectiveDevMintedCount = Number(mergedDevMintedCount);

  const devAmountEl = document.getElementById('prc-creator-amount');
  if (devAmountEl) {
    const hasMintedCount = mergedDevMintedCount !== null && mergedDevMintedCount !== undefined && mergedDevMintedCount !== '';
    const mintedCount = hasMintedCount ? Number(mergedDevMintedCount) : NaN;
    if (Number.isFinite(mintedCount) && mintedCount > 0) {
      devAmountEl.textContent = mintedCount.toLocaleString();
      devAmountEl.title = String(mintedCount);
    } else {
      devAmountEl.textContent = '-';
      devAmountEl.title = '';
    }
  }

  document.getElementById('prc-platform').textContent = d.platform || '-';
  const creatorTokenCountMsgEl = document.getElementById('prc-creator-tokencount-msg');
  if (creatorTokenCountMsgEl) {
    const hasMintedCount = mergedDevMintedCount !== null && mergedDevMintedCount !== undefined && mergedDevMintedCount !== '';
    const mintedCount = hasMintedCount ? Number(mergedDevMintedCount) : NaN;
    if (Number.isFinite(mintedCount) && mintedCount > 0) {
      if (mintedCount > 10) {
        creatorTokenCountMsgEl.textContent = 'The deployer has more than 10 tokens created, be very careful.';
        creatorTokenCountMsgEl.className = 'prc-creator-tokencount-msg danger-strong';
      } else if (mintedCount > 5) {
        creatorTokenCountMsgEl.textContent = 'The deployer has more than 5 tokens created.';
        creatorTokenCountMsgEl.className = 'prc-creator-tokencount-msg danger';
      } else if (mintedCount > 1) {
        creatorTokenCountMsgEl.textContent = 'The deployer has more than 1 token created.';
        creatorTokenCountMsgEl.className = 'prc-creator-tokencount-msg warning';
      } else {
        creatorTokenCountMsgEl.textContent = 'The deployer has only 1 token.';
        creatorTokenCountMsgEl.className = 'prc-creator-tokencount-msg positive';
      }
      creatorTokenCountMsgEl.style.display = 'block';
    } else {
      creatorTokenCountMsgEl.style.display = 'none';
      creatorTokenCountMsgEl.textContent = '';
      creatorTokenCountMsgEl.className = 'prc-creator-tokencount-msg';
    }
  }

  // Dev Activity (Bought/Sold)
  const devBoughtRow = document.getElementById('prc-dev-bought-row');
  const devSoldRow = document.getElementById('prc-dev-sold-row');
  const devBoughtEl = document.getElementById('prc-dev-bought');
  const devSoldEl = document.getElementById('prc-dev-sold');

  const boughtVal = isPadreHost ? (soldStatsBought || 0) : 0;
  const soldVal = isPadreHost ? Math.max(soldStrictAmount, soldStatsAmount) : (d.devSold ? (Number(d.devSoldAmount) || 1) : 0);

  if (isPadreHost && (boughtVal > 0 || soldVal > 0)) {
    if (devBoughtRow) devBoughtRow.style.display = 'flex';
    if (devSoldRow) devSoldRow.style.display = 'flex';
    if (devBoughtEl) devBoughtEl.textContent = boughtVal > 0 ? ('$' + formatNum(boughtVal)) : '-';
    if (devSoldEl) {
      devSoldEl.textContent = soldVal > 0 ? ('$' + formatNum(soldVal)) : '-';
      devSoldEl.className = 'prc-value ' + (soldVal > 0 ? 'danger' : 'safe');
    }
  } else {
    if (devBoughtRow) devBoughtRow.style.display = 'none';
    if (devSoldRow) devSoldRow.style.display = 'none';
  }

  const creatorFlagsAlertEl = document.getElementById('prc-creator-flags-alert');
  const communityFlagCount = Math.max(0, Number(d.flagCount) || 0);
  const applyCreatorFlagsAlert = (count) => {
    if (!creatorFlagsAlertEl) return;
    const safeCount = Math.max(0, Number(count) || 0);
    if (safeCount > 0) {
      creatorFlagsAlertEl.textContent = `Community flags detected on this deployer: ${safeCount}`;
      creatorFlagsAlertEl.style.display = 'block';
    } else {
      creatorFlagsAlertEl.style.display = 'none';
      creatorFlagsAlertEl.textContent = '';
    }
  };
  if (creatorFlagsAlertEl) {
    applyCreatorFlagsAlert(communityFlagCount);
  }

  // Locks and Burns
  const lpSection = document.getElementById('prc-lp-section');
  const lpList = document.getElementById('prc-lp-list');
  let lpItemsHtml = '';

  if (d.burns && d.burns.length > 0) {
    d.burns.forEach(b => {
      lpItemsHtml += `
        <div class="prc-row" style="padding:0">
          <span class="prc-label">\ud83d\udd25 Burned</span>
          <span class="prc-value safe" title="${b.tx}">
            ${b.pct.toFixed(2)}% ${b.tx ? `<a href="https://solscan.io/tx/${b.tx}" target="_blank" style="color:#00ffa3;text-decoration:none;">[TX]</a>` : ''}
          </span>
        </div>`;
    });
  }

  if (d.locks && d.locks.length > 0) {
    d.locks.forEach(l => {
      lpItemsHtml += `
        <div class="prc-row" style="padding:0">
          <span class="prc-label">\ud83d\udd12 Locked</span>
          <span class="prc-value safe">
            ${l.pct.toFixed(2)}% <a href="${l.url}" target="_blank" style="color:#00ffa3;text-decoration:none;">[Link]</a>
          </span>
        </div>`;
    });
  }

  if (lpItemsHtml) {
    lpSection.style.display = 'block';
    lpList.innerHTML = lpItemsHtml;
  } else {
    lpSection.style.display = 'none';
  }


  // Safety Insights (render positive ones smoothly)
  const insightsEl = document.getElementById('prc-insights-section');
  const insightsListEl = document.getElementById('prc-insights-list');

  const allSafetyInsights = (d.safetyInsights || []).map((i) => String(i || ''));
  const provenInsight = allSafetyInsights.find((i) => /\bPROVEN:\s*/i.test(i));

  const survivorDetailInsight = allSafetyInsights.find((i) =>
    /\bSURVIVOR:\s*/i.test(i) && /older than 2 months|market cap|liquidity/i.test(i)
  );
  const provenOrSurvivorInsight = provenInsight || survivorDetailInsight;

  if (insightsEl && insightsListEl) {
    let insights = allSafetyInsights.filter((i) =>
      !/community flags/i.test(i) &&
      !/\bSURVIVOR:\s*/i.test(i) &&
      !/\bPROVEN:\s*/i.test(i)
    );
    if (suppressSellNarrative) {
      insights = insights.filter((i) => !isSellNarrativeText(i));
    }

    if (insights.length > 0) {
      insightsEl.style.display = 'block';
      insightsListEl.innerHTML = insights.map(i => {
        const isPos = i.includes('\u2705');
        return `<div class="prc-insight-item ${isPos ? 'positive' : ''}">${isPos ? '' : '\u26a0\ufe0f'} ${i}</div>`;
      }).join('');
    } else {
      insightsEl.style.display = 'none';
    }
  }

  // CTO Badge
  const ctoBadge = document.getElementById('prc-cto-badge');
  if (ctoBadge) ctoBadge.style.display = d.isCTO ? 'block' : 'none';

  // Risk Badge logic
  const resolvedThreshold = parseRuggerThreshold(
    d.ruggerThreshold ?? d.threshold ?? d.flagThreshold ?? d.scammerThreshold,
    currentRuggerThreshold
  );
  const protectedHuntState = getProtectedHuntState(d.creator, {
    flagsCount: d.flagCount,
    cooldownUntil: d.cooldownUntil,
    ruggerThreshold: resolvedThreshold
  });
  const ruggerFlagged = (protectedHuntState.flagsCount >= resolvedThreshold);

  let displayRating = String(d.integratedRating || 'SAFE').toUpperCase();
  let displayRiskLevel = String(d.riskLevel || 'safe').toLowerCase();

  // Enforce DANGER if the deployer is a rugger or the token is rugged
  if (d.rugged || ruggerFlagged) {
    displayRating = 'DANGER';
    displayRiskLevel = 'danger';
  } else {
    if (suppressSellNarrative && displayRating === 'DANGER' && (Number(d.score) || 0) <= 2000) {
      displayRating = 'WARNING';
      displayRiskLevel = 'warning';
    }
    if (Number.isFinite(effectiveDevMintedCount) && effectiveDevMintedCount > 5 && displayRating === 'SAFE' && !d.isCTO && !d.isSurvivor) {
      displayRating = 'WARNING';
      displayRiskLevel = 'warning';
    }
  }

  const badge = document.getElementById('prc-risk-badge');
  badge.textContent = displayRating;
  badge.className = `prc-risk-badge ${displayRiskLevel || 'safe'}`;

  // Risk Analysis
  const scoreEl = document.getElementById('prc-score');
  scoreEl.textContent = `${d.score} (RugCheck)`;
  const scoreLevel = d.score > 2000 ? 'danger' : (d.score > 500 ? 'warning' : 'safe');
  scoreEl.className = 'prc-value ' + scoreLevel;

  const distributionRiskMeta = getDistributionRiskMeta(d);
  const distributionRiskEl = document.getElementById('prc-distribution-risk');
  if (distributionRiskEl) {
    distributionRiskEl.textContent = distributionRiskMeta.text;
    distributionRiskEl.className = `prc-value ${distributionRiskMeta.className}`;
  }

  const maturityMeta = getTokenMaturityMeta(d.tokenAgeDays);
  const maturityEl = document.getElementById('prc-token-maturity');
  if (maturityEl) {
    maturityEl.textContent = maturityMeta.text;
    maturityEl.className = `prc-value ${maturityMeta.className}`;
  }

  // Version Update
  const versionEl = document.getElementById('prc-version-label');
  if (versionEl && d.appVersion) {
    versionEl.textContent = d.appVersion;
  }

  // Hunt Rugger Footer Update
  if (ruggerThresholdAuthoritative) {
    currentRuggerThreshold = protectedHuntState.ruggerThreshold;
    safeStorage()?.set({ [PRC_RUGGER_THRESHOLD_KEY]: currentRuggerThreshold });
  }
  if (!huntUiSyncInProgress) {
    updateHuntUI(
      protectedHuntState.flagsCount,
      displayRiskLevel === 'safe',
      d.rugged,
      d.creator,
      protectedHuntState.cooldownUntil,
      d.isSurvivor,
      Boolean(provenOrSurvivorInsight),
      ruggerThresholdAuthoritative ? currentRuggerThreshold : null,
      d.isCTO,
      devSold || d.devSold || soldVal > 15,
      d.userFlagged
    );
  }

  // Live reconcile for creator flags. The initial analysis payload can be stale;
  // fetch current creator state and refresh only if this creator is still on screen.
  const creatorForLiveFlags = String(d.creator || '').trim();
  if (creatorForLiveFlags && creatorForLiveFlags !== 'Unknown') {
    getOrCreateUserId().then((userId) => {
      const uniqueUserId = getOrCreateUniqueUserIdLocal();
      safeSendMessage({
        action: 'getFlagState',
        creator: creatorForLiveFlags,
        userId,
        uniqueUserId,
        clientFingerprint: getClientFingerprint()
      }, (state) => {
        if (!state || typeof state !== 'object') return;
        const activeCreator = document.getElementById('prc-creator')?.getAttribute('data-full');
        if (!activeCreator || activeCreator !== creatorForLiveFlags) return;

        const liveCount = Math.max(0, Number(state.count) || 0);
        const liveCooldown = Number(state.cooldownUntil) || 0;
        const liveThreshold = parseRuggerThreshold(
          state.ruggerThreshold ?? state.threshold ?? state.flagThreshold ?? state.scammerThreshold,
          currentRuggerThreshold
        );

        const hasDiff = (
          liveCount !== Number(protectedHuntState.flagsCount || 0) ||
          liveCooldown !== Number(protectedHuntState.cooldownUntil || 0) ||
          liveThreshold !== Number(protectedHuntState.ruggerThreshold || currentRuggerThreshold)
        );
        if (!hasDiff) return;

        captureConfirmedHuntState(creatorForLiveFlags, liveCount, liveCooldown, liveThreshold);
        if (!huntUiSyncInProgress) {
          updateHuntUI(
            liveCount,
            displayRiskLevel === 'safe',
            d.rugged,
            creatorForLiveFlags,
            liveCooldown,
            d.isSurvivor,
            Boolean(provenOrSurvivorInsight),
            liveThreshold,
            d.isCTO,
            devSold || d.devSold || soldVal > 15,
            state.userFlagged
          );
        }
        applyCreatorFlagsAlert(liveCount);

        const badgeEl = document.getElementById('prc-risk-badge');
        if (badgeEl && (liveCount > 0 || d.rugged) && String(badgeEl.textContent || '').trim().toUpperCase() === 'SAFE') {
          const liveRuggerFlagged = (liveCount >= liveThreshold);
          if (liveRuggerFlagged || d.rugged) {
            badgeEl.textContent = 'DANGER';
            badgeEl.className = 'prc-risk-badge danger';
          } else {
            badgeEl.textContent = 'WARNING';
            badgeEl.className = 'prc-risk-badge warning';
          }
        }
      });
    });
  }
  d.isDumped = devSold || d.devSold || soldVal > 15;
  refreshSealFooterForToken(d, true);

  // Risks List
  const risksList = document.getElementById('prc-risks-list');
  const risks = (d.risks || [])
    .filter((r) => {
      const text = `${String(r?.name || '')} ${String(r?.description || '')}`.toUpperCase();
      if (text.includes('SURVIVOR:')) return false;
      if (text.includes('COMMUNITY/DEV PROVED STRONGER THAN DEPLOYER')) return false;
      if (text.includes('PROVEN:')) return false;
      if (suppressSellNarrative && isSellNarrativeText(text)) return false;
      return true;
    })
    .map(r => `
    <div class="prc-risk-item ${r.level}">
      <div class="prc-risk-name">${r.name}</div>
      <div class="prc-risk-desc">${r.description}</div>
    </div>
  `);

  if (devSold || d.devSold || soldVal > 15) {
    risks.unshift(`
      <div class="prc-creator-tokencount-msg danger" style="margin: 4px 0; text-align: left;">
        Dev dump detected
      </div>
    `);
  }

  const finalRisksHtml = risks.join('');

  const provenRiskHtml = provenOrSurvivorInsight
    ? `<div class="prc-risk-item positive">
         <div class="prc-risk-name">&#128142; PROVEN</div>
         <div class="prc-risk-desc">${provenOrSurvivorInsight.replace(/^.*?(PROVEN|SURVIVOR):\s*/i, '')}</div>
       </div>`
    : '';

  risksList.innerHTML = `${finalRisksHtml}${provenRiskHtml}`;

  // Update background header image if available
  const bg = document.getElementById('prc-token-bg');
  if (bg) {
    if (d.headerURL) {
      bg.style.backgroundImage = `url('${d.headerURL}')`;
      bg.style.display = 'block';
    } else {
      bg.style.display = 'none';
      bg.style.backgroundImage = 'none';
    }
  }
}

function formatNum(n) {
  if (!n) return '0';
  if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M';
  if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
  return n.toFixed(2);
}

function getPadreTop10HolderPct() {
  try {
    if (!isPadreHostContext()) return null;

    const normalize = (v) => String(v || '')
      .replace(/[\u00A0\u2007\u202F]/g, ' ')
      .replace(/\s+/g, ' ')
      .trim();

    const parsePctValue = (raw) => {
      if (raw === null || raw === undefined) return null;
      const cleaned = String(raw).trim().replace(',', '.');
      const val = Number(cleaned);
      if (!Number.isFinite(val) || val < 0 || val > 100) return null;
      return val;
    };

    const parseFirstPct = (txt) => {
      const m = normalize(txt).match(/([+-]?[0-9]+(?:[.,][0-9]+)?)\s*%/i);
      return m?.[1] ? parsePctValue(m[1]) : null;
    };

    const parseTop10Pct = (txt) => {
      const t = normalize(txt);
      if (!t) return null;
      const lower = t.toLowerCase();
      if (
        /\bavg\.?\s*top\s*10\s*holders?\b/i.test(lower) ||
        /\btop\s*10\s*holders?\s*(entry|exit)\b/i.test(lower)
      ) {
        return null;
      }

      const labels = [];
      const labelRe = /top\s*10\s*h(?:olders?)?\.?/gi;
      let lm;
      while ((lm = labelRe.exec(t)) !== null) {
        labels.push(lm.index);
      }
      if (!labels.length) return null;

      const pcts = [];
      const pctRe = /([+-]?[0-9]+(?:[.,][0-9]+)?)\s*%/gi;
      let pm;
      while ((pm = pctRe.exec(t)) !== null) {
        const v = parsePctValue(pm[1]);
        if (v !== null) pcts.push({ value: v, idx: pm.index });
      }
      if (!pcts.length) return null;

      let best = null;
      for (const labelIdx of labels) {
        for (const pct of pcts) {
          const dist = Math.abs(pct.idx - labelIdx);
          if (dist > 90) continue;
          const beforeLabel = pct.idx <= labelIdx;
          if (
            !best ||
            dist < best.dist ||
            (dist === best.dist && beforeLabel && !best.beforeLabel)
          ) {
            best = { value: pct.value, dist, beforeLabel };
          }
        }
      }
      return best ? best.value : null;
    };

    // Fast-path: extract directly from Token Data & Security visible text.
    const bodyTextFast = normalize(document.body?.innerText || '');
    if (bodyTextFast) {
      const secIdx = bodyTextFast.toLowerCase().indexOf('token data & security');
      const region = secIdx >= 0
        ? bodyTextFast.slice(secIdx, Math.min(bodyTextFast.length, secIdx + 3800))
        : bodyTextFast;
      let bestFast = null;
      const pushFast = (rawValue, contextText = '') => {
        const ctx = String(contextText || '').toLowerCase();
        if (/\bavg\b/.test(ctx) || /\bentry\b/.test(ctx) || /\bexit\b/.test(ctx)) return;
        const v = parsePctValue(rawValue);
        if (v === null) return;
        if (bestFast === null || v > bestFast) bestFast = v;
      };
      const reBefore = /([0-9]+(?:[.,][0-9]+)?)\s*%?\s*top\s*10\s*h(?:olders?)?\.?/gi;
      const reAfter = /top\s*10\s*h(?:olders?)?\.?\s*([0-9]+(?:[.,][0-9]+)?)\s*%?/gi;
      let m;
      while ((m = reBefore.exec(region)) !== null) {
        const ctx = region.slice(Math.max(0, m.index - 36), Math.min(region.length, m.index + 72));
        pushFast(m[1], ctx);
      }
      while ((m = reAfter.exec(region)) !== null) {
        const ctx = region.slice(Math.max(0, m.index - 36), Math.min(region.length, m.index + 72));
        pushFast(m[1], ctx);
      }
      if (bestFast !== null) return bestFast;
    }

    const isVisible = (el) => {
      if (!el || !(el instanceof Element)) return false;
      if (el.closest('#padre-rug-check-ui')) return false;
      if (el.closest('[aria-hidden="true"], [hidden], [style*="display: none"]')) return false;
      const style = window.getComputedStyle(el);
      if (!style || style.display === 'none' || style.visibility === 'hidden') return false;
      const rect = el.getBoundingClientRect();
      return rect.width > 0 && rect.height > 0;
    };

    const scoreNode = (el) => {
      if (!el || !(el instanceof Element)) return 0;
      const rect = el.getBoundingClientRect();
      let score = 0;
      if (rect.left >= (window.innerWidth * 0.55)) score += 30;
      if (rect.left >= (window.innerWidth * 0.72)) score += 20;
      if (rect.width > 0 && rect.height > 0) score += 5;
      const txt = normalize(el.textContent || '');
      if (/token\s*data\s*&\s*security/i.test(txt)) score += 20;
      if (/top\s*10\s*h/i.test(txt)) score += 25;
      // Prefer nodes inside the Token Data & Security panel.
      let scope = el;
      for (let i = 0; i < 6 && scope; i += 1) {
        const scopeText = normalize(scope.textContent || '');
        if (/token\s*data\s*&\s*security/i.test(scopeText)) {
          score += 120;
          break;
        }
        if (/\bavg\.?\s*top\s*10\s*holders?\b/i.test(scopeText)) {
          score -= 90;
        }
        scope = scope.parentElement;
      }
      return score;
    };

    const candidates = [];
    const addCandidate = (value, node, bonus = 0) => {
      if (!Number.isFinite(Number(value))) return;
      const v = Number(value);
      if (v < 0 || v > 100) return;
      candidates.push({ value: v, score: scoreNode(node) + (Number(bonus) || 0) });
    };

    const labels = Array.from(document.querySelectorAll('div, span, p, li, button, a')).filter((el) => {
      if (!isVisible(el)) return false;
      const txt = normalize(el.textContent || '');
      return /top\s*10\s*h(?:olders?)?\.?/i.test(txt);
    });

    // Strict path: exact "Top 10 H." metric label card in Token Data & Security.
    for (const labelEl of labels) {
      const labelText = normalize(labelEl.textContent || '');
      if (!/^top\s*10\s*h(?:olders?)?\.?$/i.test(labelText)) continue;

      const rect = labelEl.getBoundingClientRect();
      if (rect.left < (window.innerWidth * 0.55)) continue;

      const prevPct = parseFirstPct(labelEl.previousElementSibling?.textContent || '');
      if (prevPct !== null) addCandidate(prevPct, labelEl.previousElementSibling || labelEl, 280);

      const nextPct = parseFirstPct(labelEl.nextElementSibling?.textContent || '');
      if (nextPct !== null) addCandidate(nextPct, labelEl.nextElementSibling || labelEl, 250);

      const parent = labelEl.parentElement;
      if (parent) {
        const siblings = parent.querySelectorAll(':scope > *');
        for (const sib of siblings) {
          if (sib === labelEl || !isVisible(sib)) continue;
          const pct = parseFirstPct(normalize(sib.textContent || ''));
          if (pct !== null) addCandidate(pct, sib, 220);
        }
      }

      let scope = labelEl;
      for (let i = 0; i < 6 && scope; i += 1) {
        const parsed = parseTop10Pct(normalize(scope.textContent || ''));
        if (parsed !== null) addCandidate(parsed, scope, 180 - (i * 10));
        scope = scope.parentElement;
      }
    }

    for (const labelEl of labels) {
      const labelText = normalize(labelEl.textContent || '');
      const direct = parseTop10Pct(labelText);
      if (direct !== null) addCandidate(direct, labelEl, 40);

      let scope = labelEl;
      for (let i = 0; i < 4 && scope; i += 1) {
        const scopeText = normalize(scope.textContent || '');
        const parsed = parseTop10Pct(scopeText);
        if (parsed !== null) addCandidate(parsed, scope, 30 - (i * 4));
        scope = scope.parentElement;
      }

      const parent = labelEl.parentElement;
      if (parent) {
        const siblings = parent.querySelectorAll(':scope > *');
        for (const sib of siblings) {
          if (!isVisible(sib)) continue;
          const parsed = parseTop10Pct(normalize(sib.textContent || ''));
          if (parsed !== null) addCandidate(parsed, sib, 18);
        }
      }
    }

    const bodyText = normalize(document.body?.innerText || '');
    if (bodyText) {
      const secIdx = bodyText.toLowerCase().indexOf('token data & security');
      if (secIdx >= 0) {
        const chunk = bodyText.slice(secIdx, Math.min(bodyText.length, secIdx + 4200));
        const parsedChunk = parseTop10Pct(chunk);
        if (parsedChunk !== null) addCandidate(parsedChunk, document.body, 8);
      }
      const parsedGlobal = parseTop10Pct(bodyText);
      if (parsedGlobal !== null) addCandidate(parsedGlobal, document.body, 2);
    }

    if (!candidates.length) return null;
    candidates.sort((a, b) => b.score - a.score);
    return candidates[0].value;
  } catch (_) { }
  return null;
}

function getDistributionRiskMeta(data = {}) {
  let top10 = null;

  const liveTop10 = isPadreHostContext() ? getPadreTop10HolderPct() : null;
  if (Number.isFinite(Number(liveTop10))) {
    top10 = Number(liveTop10);
  }

  if (!Number.isFinite(Number(top10))) {
    const holders = Array.isArray(data?.topHolders) ? data.topHolders : [];
    if (!holders.length) {
      return { text: 'UNKNOWN', className: 'warning' };
    }
    const top = holders
      .map((h) => Number(h?.pct) || 0)
      .filter((pct) => Number.isFinite(pct) && pct >= 0)
      .sort((a, b) => b - a);
    if (!top.length) {
      return { text: 'UNKNOWN', className: 'warning' };
    }
    top10 = top.slice(0, 10).reduce((acc, pct) => acc + (Number(pct) || 0), 0);
  }

  if (top10 < 24) return { text: 'MEDIUM', className: 'warning' };
  return { text: 'HIGH', className: 'danger' };
}

function getTokenMaturityMeta(tokenAgeDays) {
  const age = Number(tokenAgeDays);
  if (!Number.isFinite(age) || age < 0) {
    return { text: 'UNKNOWN', className: 'warning' };
  }
  if (age < 1) return { text: 'VERY NEW (<1D)', className: 'danger' };
  if (age < 7) return { text: 'NEW (<7D)', className: 'danger' };
  if (age < 30) return { text: 'EARLY (<30D)', className: 'warning' };
  if (age < 90) return { text: 'ESTABLISHED (30D+)', className: 'safe' };
  return { text: 'MATURE (90D+)', className: 'safe' };
}

function buildLocalFallbackAnalysis({ mint, da, devStats, devSoldStrict, devMintedCount, reason = '' } = {}) {
  const pair = String(getPadreActivePairLabel() || '').trim();
  const symbol = pair.includes('/') ? pair.split('/')[0].trim() : (pair || 'Unknown');
  const fallbackMint = String(mint || '').trim() || 'Unavailable';
  let fallbackCreator = String(da || '').trim() || 'Unavailable';
  let liveDevStats = devStats;
  let liveDevSoldStrict = devSoldStrict;

  if (isPadreHostContext()) {
    try {
      const liveRes = getMintFromURL();
      const liveDA = getPadreDA(liveRes?.node || null) || '';
      if (liveDA) fallbackCreator = liveDA;
      if (!liveDevStats || !Number.isFinite(Number(liveDevStats?.sold))) {
        liveDevStats = getPadreBestDevStats(fallbackCreator || null) || liveDevStats;
      }
      if (!liveDevSoldStrict || typeof liveDevSoldStrict !== 'object' || liveDevSoldStrict.found !== true) {
        liveDevSoldStrict = getPadreDevSoldStrict(fallbackCreator || null) || liveDevSoldStrict;
      }
    } catch (_) { }
  }

  const strictFound = !!(liveDevSoldStrict && typeof liveDevSoldStrict === 'object' && liveDevSoldStrict.found === true);
  const strictSold = strictFound ? (Number(liveDevSoldStrict.sold) || 0) : 0;
  const statsSold = Number(liveDevStats?.sold) || 0;
  const statsBought = Number(liveDevStats?.bought) || 0;
  const statsConfidence = Number(liveDevStats?.confidence) || 0;
  const statsMethod = String(liveDevStats?.method || '').toUpperCase();
  const statsStructuredMethod = /(ROW-STRICT|TABLE|CELL-|BADGE-)/.test(statsMethod);
  const statsRatio = statsBought > 0 ? (statsSold / statsBought) : (statsSold > 0 ? 9 : 0);
  const soldByStrict = strictFound ? (strictSold > 0.000001) : null;
  const soldByStatsStrong = !!(
    liveDevStats &&
    statsSold > 15 &&
    statsBought > 0 &&
    (
      (statsStructuredMethod && statsRatio >= 0.10) ||
      statsConfidence >= 82 ||
      statsRatio >= 0.45 ||
      statsSold > statsBought
    )
  );
  const soldByStatsStructuredNoSell = !!(
    liveDevStats &&
    statsBought > 0 &&
    statsSold <= 0.000001 &&
    statsStructuredMethod &&
    statsConfidence >= 65
  );
  const devSold = soldByStrict !== null
    ? soldByStrict
    : (soldByStatsStrong ? true : (soldByStatsStructuredNoSell ? false : false));
  const mintedCount = Number.isFinite(Number(devMintedCount)) && Number(devMintedCount) > 0 ? Number(devMintedCount) : null;
  const detail = String(reason || '').trim();
  const summary = detail
    ? `Limited mode: ${detail}`
    : "Limited mode: full chain profile unavailable right now.";

  return {
    success: true,
    mint: fallbackMint,
    name: symbol || 'Unknown',
    symbol: symbol || '?',
    creator: fallbackCreator,
    devPct: 0,
    devAmount: mintedCount ? String(mintedCount) : '0',
    devMintedCount: mintedCount,
    devLockedPct: 0,
    locks: [],
    burns: [],
    creatorBalance: 0,
    platform: 'Padre',
    score: 0,
    integratedRating: devSold ? 'WARNING' : 'SAFE',
    safetyInsights: [summary],
    riskLevel: devSold ? 'warning' : 'safe',
    rugged: false,
    devSold: devSold,
    isCTO: false,
    mintAuthority: false,
    freezeAuthority: false,
    risks: [],
    totalLiquidity: 0,
    lpLockedPct: 0,
    totalHolders: 0,
    topHolders: [],
    flagCount: 0,
    ruggerThreshold: ruggerThresholdAuthoritative ? currentRuggerThreshold : null,
    survivorMinMarketCap: 70000,
    cooldownUntil: 0,
    appVersion: 'Alpha Testing',
    headerURL: null,
    isSurvivor: false,
    tokenAgeDays: 0
  };
}

function renderLocalFallbackUI(data = {}, message = '') {
  const safe = data && typeof data === 'object' ? data : {};
  const results = document.getElementById('prc-results');
  const loading = document.getElementById('prc-loading');
  if (results) results.style.display = 'block';
  if (loading) loading.style.display = 'none';

  const setText = (id, text, title = '') => {
    const el = document.getElementById(id);
    if (!el) return;
    el.textContent = String(text ?? '-');
    if (title) el.title = String(title);
  };
  const setClass = (id, className) => {
    const el = document.getElementById(id);
    if (!el) return;
    el.className = className;
  };

  const mint = String(safe.mint || '').trim() || 'Unavailable';
  let creator = String(safe.creator || '').trim() || 'Unavailable';
  const symbol = String(safe.symbol || safe.name || 'Unknown').trim() || 'Unknown';
  const riskLevel = String(safe.riskLevel || 'warning').toLowerCase();
  let devSold = !!safe.devSold;
  const note = String(message || safe.safetyInsights?.[0] || 'Limited mode active').trim();

  if (isPadreHostContext()) {
    try {
      const liveRes = getMintFromURL();
      const liveDA = getPadreDA(liveRes?.node || null) || '';
      if (liveDA) creator = liveDA;
      const liveStats = getPadreBestDevStats(creator || null);
      const liveStrict = getPadreDevSoldStrict(creator || null);
      const liveSold = Number(liveStats?.sold) || 0;
      const liveBought = Number(liveStats?.bought) || 0;
      const liveConfidence = Number(liveStats?.confidence) || 0;
      const liveMethod = String(liveStats?.method || '').toUpperCase();
      const liveStructuredMethod = /(ROW-STRICT|TABLE|CELL-|BADGE-)/.test(liveMethod);
      const liveRatio = liveBought > 0 ? (liveSold / liveBought) : (liveSold > 0 ? 9 : 0);
      const soldByLiveStatsStrong = !!(
        liveStats &&
        liveSold > 15 &&
        liveBought > 0 &&
        (
          (liveStructuredMethod && liveRatio >= 0.10) ||
          liveConfidence >= 82 ||
          liveRatio >= 0.45 ||
          liveSold > liveBought
        )
      );
      const soldByLiveStatsStructuredNoSell = !!(
        liveStats &&
        liveBought > 0 &&
        liveSold <= 0.000001 &&
        liveStructuredMethod &&
        liveConfidence >= 65
      );
      const liveStrictFound = !!(liveStrict?.found);
      if (liveStrictFound) {
        devSold = Number(liveStrict?.sold) > 0.000001;
      } else if (soldByLiveStatsStrong) {
        devSold = true;
      } else if (soldByLiveStatsStructuredNoSell) {
        devSold = false;
      }
    } catch (_) { }
  }

  setText('prc-name', String(safe.name || symbol || '-'));
  setText('prc-symbol', symbol);
  setText('prc-mint', shortAddr(mint), mint);
  const tokenLockMsgEl = document.getElementById('prc-token-lock-msg');
  if (tokenLockMsgEl) {
    tokenLockMsgEl.style.display = 'none';
    tokenLockMsgEl.textContent = '';
    tokenLockMsgEl.className = 'prc-token-lock-msg';
  }

  const creatorEl = document.getElementById('prc-creator');
  if (creatorEl) {
    creatorEl.textContent = shortAddr(creator);
    creatorEl.setAttribute('data-full', creator);
    creatorEl.title = creator;
  }

  setText('prc-creator-pct', `${Number(safe.devPct || 0).toFixed(2)}%`);
  setClass('prc-creator-pct', `prc-value ${devSold ? 'warning' : 'safe'}`);

  const mintedCount = Number(safe.devMintedCount);
  if (Number.isFinite(mintedCount) && mintedCount > 0) {
    setText('prc-creator-amount', mintedCount.toLocaleString(), String(mintedCount));
  } else {
    setText('prc-creator-amount', '-');
  }
  setText('prc-platform', String(safe.platform || 'Padre'));
  setText('prc-score', `${Number(safe.score || 0)} (Limited)`);
  setClass('prc-score', `prc-value ${riskLevel === 'danger' ? 'danger' : (riskLevel === 'warning' ? 'warning' : 'safe')}`);
  const fallbackDistributionRisk = getDistributionRiskMeta(safe);
  setText('prc-distribution-risk', fallbackDistributionRisk.text);
  setClass('prc-distribution-risk', `prc-value ${fallbackDistributionRisk.className}`);
  const fallbackMaturity = getTokenMaturityMeta(safe.tokenAgeDays);
  setText('prc-token-maturity', fallbackMaturity.text);
  setClass('prc-token-maturity', `prc-value ${fallbackMaturity.className}`);

  const badge = document.getElementById('prc-risk-badge');
  if (badge) {
    const fallbackMintedCount = Number(safe.devMintedCount);
    const safeByTokensRule = Number.isFinite(fallbackMintedCount) && fallbackMintedCount > 5 && !safe.isCTO && !safe.isSurvivor;
    const badgeText = String(safe.integratedRating || (devSold ? 'WARNING' : 'SAFE')).toUpperCase();
    const forcedBadgeText = (safeByTokensRule && badgeText === 'SAFE') ? 'WARNING' : badgeText;
    const forcedRiskLevel = (safeByTokensRule && (riskLevel === 'safe' || !riskLevel)) ? 'warning' : riskLevel;
    badge.textContent = forcedBadgeText;
    badge.className = `prc-risk-badge ${forcedRiskLevel || (devSold ? 'warning' : 'safe')}`;
  }

  const insightsSection = document.getElementById('prc-insights-section');
  const insightsList = document.getElementById('prc-insights-list');
  if (insightsSection && insightsList) {
    insightsSection.style.display = 'block';
    insightsList.innerHTML = `<div class="prc-insight-item">&#9888;&#65039; ${note}</div>`;
  }

  const lpSection = document.getElementById('prc-lp-section');
  if (lpSection) lpSection.style.display = 'none';

  const risksList = document.getElementById('prc-risks-list');
  if (risksList) {
    risksList.innerHTML = `<div class="prc-risk-item warning"><div class="prc-risk-name">Limited Mode</div><div class="prc-risk-desc">${note}</div></div>`;
  }

  const bg = document.getElementById('prc-token-bg');
  if (bg) {
    bg.style.display = 'none';
    bg.style.backgroundImage = 'none';
  }

  try {
    const fallbackThresholdRaw = safe.ruggerThreshold;
    const fallbackThreshold = hasValidThresholdValue(fallbackThresholdRaw)
      ? parseRuggerThreshold(fallbackThresholdRaw, currentRuggerThreshold)
      : (ruggerThresholdAuthoritative ? currentRuggerThreshold : null);
    updateHuntUI(
      Math.max(0, Number(safe.flagCount) || 0),
      !devSold,
      !!safe.rugged,
      creator,
      Number(safe.cooldownUntil) || 0,
      false,
      false,
      fallbackThreshold,
      false,
      false,
      !!safe.userFlagged
    );
  } catch (_) { }
}

// Initial boot or SPA nav check helper
let currentAnalyzingMint = "";
let userDismissedCurrentToken = false;
let analysisInFlightMint = null;
let observerDebounceTimer = null;
let prcRetryAnalysisFn = null;

function forceRetryCurrentAnalysis() {
  if (!isContextValid()) return;
  userDismissedCurrentToken = false;
  analysisInFlightMint = null;
  lastConfirmedHuntState = null;
  const liveMint = String(getMintFromURL()?.address || '').trim();
  if (liveMint) currentAnalyzingMint = liveMint;
  injectUI();
}

function refreshForDetectedTokenChange(reason, nextMint = '') {
  if (!isContextValid()) return;
  userDismissedCurrentToken = false;
  analysisInFlightMint = null;
  lastConfirmedHuntState = null;
  currentAnalyzingMint = '';
  if (nextMint) {
    console.log("PRC: refreshing analysis for token change", {
      reason,
      next: nextMint.slice(0, 8)
    });
  } else {
    console.log("PRC: refreshing analysis for token change", { reason });
  }
  triggerAutoOpenIfEnabled(true);
}

function showUnreadableCreatorState(message = "Hermeto can't look into this creator, please refresh the app and try again.") {
  const results = document.getElementById('prc-results');
  const loading = document.getElementById('prc-loading');
  if (results) results.style.display = 'none';
  if (!loading) return;
  loading.style.display = 'flex';
  loading.style.backgroundImage = 'none';
  loading.innerHTML = `
    <div class="prc-fail-state">
      <div class="prc-fail-message">${message}</div>
      <button id="prc-fail-refresh-btn" class="prc-fail-refresh-btn">REFRESH</button>
    </div>
  `;
  const refreshBtn = document.getElementById('prc-fail-refresh-btn');
  if (refreshBtn) {
    refreshBtn.type = 'button';
    refreshBtn.onclick = () => {
      const fn = prcRetryAnalysisFn;
      if (typeof fn === 'function') {
        fn();
        return;
      }
      forceRetryCurrentAnalysis();
    };
  }
}

function triggerAutoOpenIfEnabled(force = false) {
  if (!isContextValid()) return;
  if (!isPadreHostContext()) return;
  if (!force && !isAutoOpenEnabledForCurrentSite()) return;
  // Only auto-open on explicit token URLs inside Padre.
  if (!getPadreTokenAddressFromUrl()) return;
  const res = getMintFromURL();
  if (!res || !res.address) return;

  // If token changed, reset the dismissal flag
  if (res.address !== currentAnalyzingMint) {
    userDismissedCurrentToken = false;
    lastConfirmedHuntState = null;
  }

  // If already analyzed AND user closed it, respect their choice UNLESS it's a force scan
  if (!force && userDismissedCurrentToken) return;
  if (!force && analysisInFlightMint === res.address) return;

  // Do not re-analyze the same mint on DOM mutations.
  if (!force && res.address === currentAnalyzingMint) return;

  currentAnalyzingMint = res.address;
  injectUI();
}

// Watch for DOM changes to catch late-loading stats or tab clicks
let pObserver = null;
function setupObserver() {
  if (pObserver) pObserver.disconnect();
  pObserver = new MutationObserver(() => {
    if (observerDebounceTimer) clearTimeout(observerDebounceTimer);
    observerDebounceTimer = setTimeout(() => {
      const res = getMintFromURL();
      if (!res || !res.address) return;
      // Only react if we don't have an active token yet or URL token diverged.
      if (!currentAnalyzingMint || res.address !== currentAnalyzingMint) {
        triggerAutoOpenIfEnabled();
      }
    }, 300);
  });
  pObserver.observe(document.body, { childList: true, subtree: true });
}

setupObserver();
hydrateSiteAutoOpenState();
hydrateRuggerThresholdState();

function getPadreActivePairLabel() {
  if (!isPadreHostContext()) return '';
  try {
    const textBlob = String(document.body?.innerText || '');
    const direct = textBlob.match(/\b([A-Za-z0-9._$-]{2,20}\/SOL)\s*(?:[·•\-]\s*)?trade\.padre\.gg\b/i);
    if (direct?.[1]) return String(direct[1]).toUpperCase().trim();

    const isElementVisible = (el) => {
      if (!el || !(el instanceof Element)) return false;
      const style = window.getComputedStyle(el);
      if (!style || style.display === 'none' || style.visibility === 'hidden') return false;
      const rect = el.getBoundingClientRect();
      return rect.width > 0 && rect.height > 0;
    };

    const nodes = document.querySelectorAll('div, span, p');
    for (const el of nodes) {
      if (!isElementVisible(el)) continue;
      const t = String(el.textContent || '').trim();
      if (!t || !/trade\.padre\.gg/i.test(t)) continue;
      const m = t.match(/\b([A-Za-z0-9._$-]{2,20}\/SOL)\b/i);
      if (m?.[1]) return String(m[1]).toUpperCase().trim();
    }
  } catch (_) { }
  return '';
}

// Watch for SPA navigation
let lastUrl = location.href;
let lastPadreObservedMint = '';
let lastPadreObservedPair = '';
const spaInterval = setInterval(() => {
  if (!isContextValid()) {
    clearInterval(spaInterval);
    return;
  }

  if (isPadreHostContext()) {
    const liveRes = getPadreMint();
    const liveMint = String(liveRes?.address || '').trim();
    const livePair = getPadreActivePairLabel();
    if (livePair && livePair !== lastPadreObservedPair) {
      const prevPair = lastPadreObservedPair;
      lastPadreObservedPair = livePair;
      if (prevPair && prevPair !== livePair) {
        const inFlightDifferentMint = !!(analysisInFlightMint && liveMint && analysisInFlightMint !== liveMint);
        if (!liveMint || liveMint === currentAnalyzingMint || inFlightDifferentMint) {
          console.log("PRC: Padre pair changed", {
            from: prevPair,
            to: livePair,
            current: currentAnalyzingMint ? currentAnalyzingMint.slice(0, 8) : ''
          });
          refreshForDetectedTokenChange('padre-live-pair-change', liveMint);
        }
      }
    }

    if (liveMint && liveMint !== lastPadreObservedMint) {
      const prevMint = lastPadreObservedMint;
      lastPadreObservedMint = liveMint;
      const inFlightDifferentMint = !!(analysisInFlightMint && analysisInFlightMint !== liveMint);
      if (liveMint !== currentAnalyzingMint || inFlightDifferentMint) {
        console.log("PRC: Padre token changed without URL change", {
          from: prevMint ? prevMint.slice(0, 8) : '',
          to: liveMint.slice(0, 8),
          current: currentAnalyzingMint ? currentAnalyzingMint.slice(0, 8) : ''
        });
        refreshForDetectedTokenChange('padre-live-mint-change', liveMint);
      }
    }

  }

  const url = location.href;
  if (url !== lastUrl) {
    const oldUrl = lastUrl;
    lastUrl = url;
    if (url.match(/(?:\/solana\/|\/token\/|\/lp\/|\/board\/|\/coin\/|\/trade\/solana\/|\/terminal\?.*address=)/)) {
      const newAddr = getAddressFromUrl(url);
      const oldAddr = getAddressFromUrl(oldUrl);

      console.log("PRC: SPA navigation detected", {
        from: oldUrl.slice(-30),
        to: url.slice(-30),
        oldAddr: oldAddr?.slice(0, 8),
        newAddr: newAddr?.slice(0, 8)
      });

      if (newAddr && newAddr !== oldAddr) {
        // Force immediate refresh when mint changes.
        refreshForDetectedTokenChange('spa-url-mint-change', newAddr);
      }

      // Fallback refresh after DOM settles.
      setTimeout(() => triggerAutoOpenIfEnabled(), 2000);
    }
  }
}, 1000);


// Listen for manual activation
chrome.runtime.onMessage.addListener((message) => {
  if (message.action === 'toggleUI') {
    if (!isPadreHostContext()) return;
    currentAnalyzingMint = ""; // Force re-scan
    analysisInFlightMint = null;
    injectUI();
  }
});

// Initial boot
setTimeout(() => triggerAutoOpenIfEnabled(), 1000);


