功能增强型必应美化脚本 (Enhanced Bing Beautifier) - Refactored V6.3.1

V6.3.1: 修正时间筛选未读取最新查询词的问题 (Fix time filter not reading latest query)。V6.3: 修正 PJAX/SPA 翻页导致脚本失效的问题。功能:结构化重构、性能优化、专业粒子库点击动效、卡片重排、强力搜索增强、站点屏蔽持久化。

// ==UserScript==
// @name         功能增强型必应美化脚本 (Enhanced Bing Beautifier) - Refactored V6.3.1
// @namespace    http://tampermonkey.net/
// @version      6.3.1
// @description  V6.3.1: 修正时间筛选未读取最新查询词的问题 (Fix time filter not reading latest query)。V6.3: 修正 PJAX/SPA 翻页导致脚本失效的问题。功能:结构化重构、性能优化、专业粒子库点击动效、卡片重排、强力搜索增强、站点屏蔽持久化。
// @author       Gemini & WJH
// @match        https://www.bing.com/search*
// @match        https://cn.bing.com/search*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-start
// ==/UserScript==

(() => {
  'use strict';

  // ---------- 常量与配置 ----------
  const CFG = {
    STORAGE_KEYS: {
      BLOCKLIST: 'bing_blocked_sites_v3',
    },
    UI: {
      FAB_ID: 'search-enhancer-fab',
      PANEL_ID: 'search-enhancer-panel',
      BLOCKLIST_MODAL_ID: 'blocklist-modal',
      BLOCKLIST_TEXTAREA_ID: 'blocklist-textarea',
    },
    SELECTORS: {
      MAIN_CONTENT: '#b_content',
      RIGHT_RAIL: '#b_context',
      RESULTS_LIST: '#b_results',
      // 原始定义(含 :has),并提供降级选择器
      CENTER_HAS: 'li.b_ans:has(#TechHelpInfoACFCard)',
      CENTER_FALLBACK_PARENT: 'li.b_ans',
      CENTER_FALLBACK_CHILD: '#TechHelpInfoACFCard',
      MOVE_BOTTOM: 'li.b_vidAns',
      SIDEBAR_HAS: 'li.b_ans:has(.df_alaskcarousel)',
      SIDEBAR_FALLBACK_PARENT: 'li.b_ans',
      SIDEBAR_FALLBACK_CHILD: '.df_alaskcarousel',
      REMOVE: [
        'li.b_ans:has(.b_rrsr)', '.b_msg.b_canvas', 'li.b_ans:has(#inline_rs)', 'li.b_ans:has(#brsv3)',
        'li.b_ans:has(div.richrswrapper)', '#b_topw', '.b_ad', '.b_ads', '.sb_adsWv2', '.b_adTop',
        'li.b_ans.b_ad', '.ad_sect_line', '.msan_ads_container', '#df_ad', '#b_pole', '.b_expandableAnswer',
        '#brsv3', '#b_footer', '.b_inline_ajax_rs', '#inline_rs', '.richrswrapper', '.b_mrs',
        '#monica-content-root', '#monica-search-enhance', '#doubao-ai-assistant', '#ciciai-shadow-container',
        'doubao-ai-csui', 'kimi-web-extension', '#__infoflow_commercial', '#MAXAI_SEARCH_WITH_AI_ROOT_ID',
        'cici-ai-csui', 'max-ai-minimum-app', 'use-chat-gpt-ai-content-menu', 'use-chat-gpt-ai',
        'li.b_ans:has(.rqnaacfacc)',
        '#adstop_gradiant_separator', // [V6.1 新增] 屏蔽广告分隔线
      ],
      RESULT_BLOCKS: '#b_results li.b_algo, #b_context li.b_ans',
      CITE: 'cite',
      ENHANCER_BTN: '.enhancer-btn',
      QUERY_INPUT: '#sb_form_q',
    },
    PRESETS: {
      SITES: [
        { name: '知乎', domain: 'zhihu.com' },
        { name: '哔哩哔哩', domain: 'bilibili.com' },
        { name: 'GitHub', domain: 'github.com' },
        { name: '维基百科', domain: 'wikipedia.org' },
        { name: '豆瓣', domain: 'douban.com' },
        { name: '微博', domain: 'weibo.com' },
        { name: '少数派', domain: 'sspai.com' },
        { name: 'CSDN', domain: 'csdn.net' },
        { name: 'V2EX', domain: 'v2ex.com' },
        { name: 'Stack Overflow', domain: 'stackoverflow.com' },
        { name: 'Reddit', domain: 'reddit.com' },
      ],
      FILETYPES: ['PDF', 'DOCX', 'PPTX', 'XLSX'],
    },
    ADS_ICON_MARKERS: [
      '/9j/4AAQSkZJRgABAQ',
      'iVBORw0KGgoAAAANSUhEUgAAABsAAAALCAYAAACOAvbO'
    ],
  };

  // ---------- 工具函数 ----------
  const CSS_HAS_SUPPORTED = typeof CSS !== 'undefined' && CSS.supports && CSS.supports('selector(:has(*))');

  const debounce = (fn, wait = 150) => {
    let t;
    return (...args) => {
      clearTimeout(t);
      t = setTimeout(() => fn.apply(null, args), wait);
    };
  };

  const once = (fn) => {
    let called = false;
    return (...args) => {
      if (called) return;
      called = true;
      fn(...args);
    };
  };

  const qs = (root, sel) => root.querySelector(sel);
  const qsa = (root, sel) => root.querySelectorAll(sel);

  const domReady = (cb) => {
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', cb, { once: true });
    } else {
      cb();
    }
  };

  // ---------- 存储封装(异步) ----------
  const store = {
    async get(key, fallback = '[]') {
      try {
        const v = await GM_getValue(key, fallback);
        return typeof v === 'string' ? JSON.parse(v) : v;
      } catch {
        return JSON.parse(fallback);
      }
    },
    async set(key, value) {
      await GM_setValue(key, JSON.stringify(value));
    },
  };

  // ---------- 动效抽象层(自动检测专业粒子库,提供回退) ----------
  const Effects = (() => {
    const injectRippleCSS = once(() => {
      GM_addStyle(`
        .click-ripple {
          position: absolute; border-radius: 50%;
          transform: translate(-50%, -50%); pointer-events: none;
          width: 8px; height: 8px;
          background: radial-gradient(circle, rgba(255,255,255,0.9) 0%, rgba(0,120,212,0.2) 70%, rgba(0,120,212,0) 100%);
          animation: ripple-anim 600ms ease-out forwards;
          z-index: 10000;
        }
        @keyframes ripple-anim {
          to { opacity: 0; transform: translate(-50%, -50%) scale(16); }
        }
      `);
    });

    function rippleAt(x, y) {
      injectRippleCSS();
      const el = document.createElement('div');
      el.className = 'click-ripple';
      el.style.left = x + 'px';
      el.style.top = y + 'px';
      document.body.appendChild(el);
      setTimeout(() => el.remove(), 620);
    }

    function confettiAt(x, y, opts = {}) {
      const g = window;
      const hasCanvasConfetti = typeof g.confetti === 'function' && !!g.confetti;
      if (hasCanvasConfetti) {
        const canvas = document.createElement('canvas');
        canvas.style.cssText = 'position:fixed;pointer-events:none;inset:0;z-index:10001;';
        document.body.appendChild(canvas);
        const my = g.confetti.create(canvas, { resize: true, useWorker: true });
        my({
          particleCount: opts.particleCount ?? 120,
          spread: opts.spread ?? 75,
          startVelocity: opts.startVelocity ?? 55,
          gravity: opts.gravity ?? 0.9,
          ticks: opts.ticks ?? 200,
          origin: {
            x: x / window.innerWidth,
            y: y / window.innerHeight
          },
          scalar: opts.scalar ?? 1.0,
          shapes: opts.shapes,
          colors: opts.colors,
        });
        setTimeout(() => canvas.remove(), 1200);
      } else {
        rippleAt(x, y);
      }
    }

    return {
      burst: confettiAt,
      ripple: rippleAt,
    };
  })();

  // ---------- 样式注入 ----------
  function injectStyles() {
    const S = CFG.SELECTORS;
    GM_addStyle(`
      :root {
        --glass-bg: rgba(255, 255, 255, 0.6);
        --glass-bg-dark: rgba(25, 25, 40, 0.7);
        --glass-border: rgba(255, 255, 255, 0.25);
        --glass-border-dark: rgba(255, 255, 255, 0.15);
        --glass-shadow: 0 6px 15px rgba(0,0,0,0.15);
        --card-radius: 16px;
        --panel-radius: 12px;
        --accent: #0078d4;
      }
      body {
        background: url('https://raw.githubusercontent.com/WJH-makers/markdown_photos/main/images/sea.png') center/cover fixed no-repeat !important;
      }
      #b_header, .b_scopebar, ${S.MAIN_CONTENT} { background: transparent !important; }
      .b_header_bg, #b_header_bg { display: none !important; }
      ${CFG.SELECTORS.REMOVE.join(',')} { display: none !important; }
      #gemini_centered_container {
        width: 100%; display: flex; flex-direction: column; align-items: center; margin-bottom: 20px;
      }
      #gemini_centered_container > li[data-centered="true"] {
        width: 70%; max-width: 800px; margin: 0 0 20px 0 !important; padding: 20px !important;
        border-radius: var(--card-radius);
        background: var(--glass-bg); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
        border: 1px solid var(--glass-border); box-shadow: var(--glass-shadow) !important;
      }
      body.b_dark #gemini_centered_container > li[data-centered="true"] {
        background: var(--glass-bg-dark); border-color: var(--glass-border-dark);
      }
      #gemini_centered_container .thml_cnt, #gemini_centered_container #TechHelpInfoACFCard {
        background: transparent !important; border: none !important; box-shadow: none !important; padding: 0 !important;
      }
      ${S.MAIN_CONTENT} > li[data-relocated-bottom="true"] {
        width: 90%; max-width: 1200px; margin: 40px auto 20px auto !important; padding: 20px !important;
        border-radius: var(--card-radius);
        background: var(--glass-bg); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
        border: 1px solid var(--glass-border); box-shadow: var(--glass-shadow) !important;
      }
      body.b_dark ${S.MAIN_CONTENT} > li[data-relocated-bottom="true"] {
        background: var(--glass-bg-dark); border-color: var(--glass-border-dark);
      }
      ${S.MAIN_CONTENT} > li.b_vidAns .mc_vtvc { background: transparent !important; box-shadow: none !important; }
      ${CFG.SELECTORS.RIGHT_RAIL} > li[data-relocated="true"] {
        display: block !important; width: 100%; box-sizing: border-box; margin: 0 0 20px 0; padding: 16px !important;
        border-radius: 8px; background: var(--glass-bg);
        backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
        border: 1px solid var(--glass-border); box-shadow: 0 4px 12px rgba(0,0,0,0.1) !important;
      }
      body.b_dark ${CFG.SELECTORS.RIGHT_RAIL} > li[data-relocated="true"] {
        background: var(--glass-bg-dark); border-color: var(--glass-border-dark);
      }
      ${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns),
      ${S.RESULTS_LIST} > li.b_ans:not([data-relocated="true"]):not([data-centered="true"]),
      ${CFG.SELECTORS.RIGHT_RAIL} .b_ans:not([data-relocated="true"]) {
        background-color: rgba(255,255,255,0.55) !important;
        backdrop-filter: blur(16px) !important; -webkit-backdrop-filter: blur(16px) !important;
        border: 1px solid var(--glass-border) !important; border-radius: var(--card-radius) !important;
        padding: 20px !important; margin-bottom: 20px !important; box-shadow: 0 4px 12px rgba(0,0,0,0.1) !important;
        transition: transform .25s ease, box-shadow .25s ease, background-color .25s ease;
      }
      ${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):hover,
      ${S.RESULTS_LIST} > li.b_ans:not([data-relocated="true"]):not([data-centered="true"]):hover,
      ${CFG.SELECTORS.RIGHT_RAIL} .b_ans:not([data-relocated="true"]):hover {
        transform: translateY(-4px);
        box-shadow: 0 10px 20px rgba(0,0,0,0.15) !important;
        background-color: rgba(255,255,255,0.7) !important;
      }
      body.b_dark ${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns),
      body.b_dark ${S.RESULTS_LIST} > li.b_ans:not([data-relocated="true"]):not([data-centered="true"]),
      body.b_dark ${CFG.SELECTORS.RIGHT_RAIL} .b_ans:not([data-relocated="true"]) {
        background: var(--glass-bg-dark) !important; border-color: var(--glass-border-dark) !important;
      }
      body.b_dark ${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):hover,
      body.b_dark ${S.RESULTS_LIST} > li.b_ans:not([data-relocated="true"]):not([data-centered="true"]):hover,
      body.b_dark ${CFG.SELECTORS.RIGHT_RAIL} .b_ans:not([data-relocated="true"]):hover {
        background: rgba(25,25,40,0.75) !important;
      }
      ${S.RESULTS_LIST} > li, ${CFG.SELECTORS.RIGHT_RAIL} > li { border: none !important; box-shadow: none !important; }
      .b_algo h2 a { color: #002D62 !important; }
      body.b_dark .b_algo h2 a { color: #99ccff !important; }
      .b_caption p, .b_caption .b_lineclamp3 { color: #333 !important; }
      body.b_dark .b_caption p, body.b_dark .b_caption .b_lineclamp3 { color: #ccc !important; }
      cite { color: #006400 !important; font-style: normal; }
      body.b_dark cite { color: #8fbc8f !important; }
      #search-enhancer-fab {
        position: fixed; top: 120px; right: 20px; z-index: 9999; width: 50px; height: 50px;
        background: rgba(0,120,212,0.85); color: #fff; border-radius: 50%;
        text-align: center; line-height: 50px; font-size: 22px; cursor: pointer;
        backdrop-filter: blur(10px); box-shadow: 0 4px 10px rgba(0,0,0,0.2); transition: transform .15s ease, box-shadow .2s ease;
      }
      #search-enhancer-fab:hover { transform: scale(1.08); box-shadow: 0 8px 24px rgba(0,0,0,0.25); }
      #search-enhancer-panel {
        position: fixed; top: 180px; right: 20px; z-index: 9998; width: 320px; padding: 14px;
        background: rgba(240,240,240,0.7); backdrop-filter: blur(15px); border-radius: var(--panel-radius);
        box-shadow: 0 4px 20px rgba(0,0,0,0.2); display: none; border: 1px solid rgba(255,255,255,0.4);
      }
      body.b_dark #search-enhancer-panel { background: rgba(30,30,50,0.7); border: 1px solid rgba(255,255,255,0.1); }
      .enhancer-group { margin-bottom: 14px; }
      .enhancer-group h3 {
        font-size: 14px; margin: 0 0 8px 0; border-bottom: 1px solid #ccc; padding-bottom: 4px; color: #333;
      }
      body.b_dark .enhancer-group h3 { color: #ddd; border-bottom-color: #555; }
      .enhancer-btn-grid { display: flex; flex-wrap: wrap; gap: 8px; }
      .enhancer-btn {
        padding: 6px 10px; border-radius: 8px; border: none; cursor: pointer; background: var(--accent);
        color: #fff; transition: transform .06s ease, filter .2s ease;
      }
      .enhancer-btn:hover { filter: brightness(1.05); }
      .enhancer-btn:active { transform: scale(0.97); }
      .enhancer-group .custom-input-group { display: flex; gap: 8px; }
      .enhancer-group .custom-input-group input {
        flex-grow: 1; padding: 5px; border-radius: 6px; border: 1px solid #ccc;
      }
      .block-site-btn {
        margin-left: 8px; cursor: pointer; color: #d9534f; font-weight: bold; font-size: 12px; transition: color .2s;
      }
      .block-site-btn:hover { text-decoration: underline; color: #c9302c; }
      .block-site-btn.blocked { color: #28a745 !important; cursor: default; font-weight: normal; }
      .block-site-btn.blocked:hover { text-decoration: none; }
      #blocklist-modal {
        position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10001; width: 500px; padding: 20px;
        background: rgba(255,255,255,0.8); backdrop-filter: blur(20px); border-radius: 16px;
        box-shadow: 0 8px 30px rgba(0,0,0,0.2); display: none; border: 1px solid rgba(255,255,255,0.5);
      }
      body.b_dark #blocklist-modal { background: rgba(40,40,60,0.8); border-color: rgba(255,255,255,0.2); }
      #blocklist-modal h2, #blocklist-modal p { color: #333; margin-top: 0; }
      body.b_dark #blocklist-modal h2, body.b_dark #blocklist-modal p { color: #eee; }
      #blocklist-textarea { width: 95%; height: 200px; padding: 10px; border-radius: 8px; border: 1px solid #ccc; }
      .blocklist-buttons { margin-top: 15px; text-align: right; }
    `);
  }

  // ---------- DOM 操作:居中 / 底部 / 右栏 ----------
  const Containers = {
    ensureCenteredHost() {
      if (!document.getElementById('gemini_centered_container')) {
        const c = document.createElement('div');
        c.id = 'gemini_centered_container';
        const main = qs(document, CFG.SELECTORS.MAIN_CONTENT);
        if (main) main.before(c);
      }
      return document.getElementById('gemini_centered_container');
    },
    relocateToCenter(root) {
      const host = this.ensureCenteredHost();
      if (!host) return;
      if (CSS_HAS_SUPPORTED) {
        root.querySelectorAll(CFG.SELECTORS.CENTER_HAS).forEach(el => {
          if (!el.dataset.centered) {
            el.dataset.centered = 'true';
            host.appendChild(el);
          }
        });
      } else {
        root.querySelectorAll(CFG.SELECTORS.CENTER_FALLBACK_PARENT).forEach(li => {
          if (li.dataset.centered) return;
          if (li.querySelector(CFG.SELECTORS.CENTER_FALLBACK_CHILD)) {
            li.dataset.centered = 'true';
            host.appendChild(li);
          }
        });
      }
    },
    relocateToEnd(root) {
      const main = qs(document, CFG.SELECTORS.MAIN_CONTENT);
      if (!main) return;
      root.querySelectorAll(CFG.SELECTORS.MOVE_BOTTOM).forEach(el => {
        if (!el.dataset.relocatedBottom) {
          el.dataset.relocatedBottom = 'true';
          main.appendChild(el);
        }
      });
    },
    relocateToSidebar(root) {
      const rail = qs(document, CFG.SELECTORS.RIGHT_RAIL);
      if (!rail) return;
      rail.style.display = 'inline-block';
      rail.style.verticalAlign = 'top';

      if (CSS_HAS_SUPPORTED) {
        root.querySelectorAll(CFG.SELECTORS.SIDEBAR_HAS).forEach(el => {
          if (!el.dataset.relocated) {
            el.dataset.relocated = 'true';
            rail.prepend(el);
          }
        });
      } else {
        root.querySelectorAll(CFG.SELECTORS.SIDEBAR_FALLBACK_PARENT).forEach(li => {
          if (li.dataset.relocated) return;
          if (li.querySelector(CFG.SELECTORS.SIDEBAR_FALLBACK_CHILD)) {
            li.dataset.relocated = 'true';
            rail.prepend(li);
          }
        });
      }
    },
  };

  // ---------- 动态广告选择器探测 ----------
  let cachedAdSelectors = [];
  let scannedSheets = false;
  function scanStylesheetsForAdMarkers() {
    if (scannedSheets || CFG.ADS_ICON_MARKERS.length === 0) return;
    const set = new Set();
    for (const sheet of document.styleSheets) {
      try {
        const rules = sheet.cssRules;
        if (!rules) continue;
        for (const rule of rules) {
          if (rule.style && rule.style.content) {
            const content = rule.style.content;
            for (const marker of CFG.ADS_ICON_MARKERS) {
              if (content.includes(marker)) {
                const sels = rule.selectorText.split(',').map(s => s.replace(/::(before|after)/gi, '').trim());
                sels.forEach(s => set.add(s));
              }
            }
          }
        }
      } catch { /* ignore CORS */ }
    }
    cachedAdSelectors = Array.from(set);
    scannedSheets = true;
  }

  function applyDynamicAdBlocking(root) {
    if (!cachedAdSelectors.length) return;
    cachedAdSelectors.forEach(sel => {
      try {
        root.querySelectorAll(sel).forEach(el => {
          const block = el.closest('li.b_algo, .b_ans');
          if (block && block.style.display !== 'none') block.style.display = 'none';
        });
      } catch { /* ignore invalid selectors */ }
    });
  }

  // ---------- 站点屏蔽 ----------
  let blockedSites = [];
  function domainFromCite(citeEl) {
    if (!citeEl || !citeEl.textContent) return null;
    const t = citeEl.textContent.trim();
    const m = t.match(/^(?:https?:\/\/)?(?:www\.)?([^:\/\n?]+)/i);
    return m ? m[1].toLowerCase() : null;
  }

  // [V6.2 修正]
  function applyStaticBlocking(root) {
    const resultItemSelector = 'li.b_algo:not(.b_nwsAns), li.b_ans:not(.b_nwsAns)';
    const itemsInRoot = Array.from(root.querySelectorAll(resultItemSelector));
    let allItems = itemsInRoot;
    if (root.nodeType === 1 && root.matches && root.matches(resultItemSelector)) {
      allItems.push(root);
    }
    [...new Set(allItems)]
      .filter(item => item.closest('#b_results, #b_context'))
      .forEach(result => {
        const cite = result.querySelector(CFG.SELECTORS.CITE);
        const d = domainFromCite(cite);
        if (d && blockedSites.includes(d)) {
          result.style.display = 'none';
        }
      });
  }

  function addBlockButtons(root) {
    const resultItemSelector = 'li.b_algo:not(.b_nwsAns), li.b_ans:not(.b_nwsAns)';
    const itemsInRoot = Array.from(root.querySelectorAll(resultItemSelector));
    let allItems = itemsInRoot;
    if (root.nodeType === 1 && root.matches && root.matches(resultItemSelector)) {
      allItems.push(root);
    }
    [...new Set(allItems)]
      .filter(item => item.closest('#b_results, #b_context'))
      .forEach(result => {
        if (result.querySelector('.block-site-btn')) return;
        const cite = result.querySelector(CFG.SELECTORS.CITE);
        const d = domainFromCite(cite);
        if (!d || !cite || !cite.parentNode) return;

        const btn = document.createElement('span');
        btn.className = 'block-site-btn';
        if (blockedSites.includes(d)) {
          btn.textContent = '[已屏蔽]';
          btn.classList.add('blocked');
        } else {
          btn.textContent = '[屏蔽]';
          btn.title = `屏蔽 ${d}`;
          btn.addEventListener('click', async (e) => {
            e.preventDefault();
            e.stopPropagation();
            btn.textContent = '[已屏蔽]';
            btn.classList.add('blocked');
            btn.title = '';
            Effects.burst(e.clientX, e.clientY, { particleCount: 60, spread: 55, colors: ['#22c55e', '#16a34a', '#4ade80'] });
            if (!blockedSites.includes(d)) {
              blockedSites.push(d);
            }
            await store.set(CFG.STORAGE_KEYS.BLOCKLIST, [...new Set(blockedSites)]);
          }, { once: true });
        }
        cite.parentNode.appendChild(btn);
      });
  }

  // ---------- 搜索增强 UI ----------
  function currentQuery() {
    const q = qs(document, CFG.SELECTORS.QUERY_INPUT);
    let s = q ? q.value : '';
    return s.replace(/"/g, '').replace(/-\S+/g, '').replace(/site:\S+/g, '').replace(/filetype:\S+/g, '').trim();
  }

  // [V6.3.1 修正] 移除 gotoWithParam 函数

  function doSearch(str) {
    const params = new URLSearchParams(window.location.search);
    params.set('q', str.trim());
    window.location.search = params.toString();
  }

  function createUI() {
    // [V6.3 Idempotency Check]
    if (document.getElementById(CFG.UI.FAB_ID)) {
      return; // UI already exists, do not recreate
    }

    // FAB
    const fab = document.createElement('div');
    fab.id = CFG.UI.FAB_ID;
    fab.innerHTML = '🛠️';
    fab.title = '打开/关闭搜索增强工具';
    document.body.appendChild(fab);

    // 面板
    const panel = document.createElement('div');
    panel.id = CFG.UI.PANEL_ID;
    panel.innerHTML = `
      <div class="enhancer-group">
        <h3>搜索模式</h3>
        <div class="enhancer-btn-grid">
          <button class="enhancer-btn" data-op="quotes">"精确匹配"</button>
          <button class="enhancer-btn" data-op="broaden">扩大范围</button>
        </div>
      </div>
      <div class="enhancer-group">
        <h3>时间范围</h3>
        <div class="enhancer-btn-grid">
          <button class="enhancer-btn" data-filter-param="qft" data-filter-value="+filterui:age-lt1440">最近24小时</button>
          <button class="enhancer-btn" data-filter-param="qft" data-filter-value="+filterui:age-lt10080">最近一周</button>
          <button class="enhancer-btn" data-filter-param="qft" data-filter-value="+filterui:age-lt43200">最近一月</button>
          <button class="enhancer-btn" data-filter-param="qft" data-filter-value="+filterui:age-lt525600">最近一年</button>
        </div>
      </div>
      <div class="enhancer-group">
        <h3>站内搜索</h3>
        <div class="enhancer-btn-grid" id="site-preset-grid"></div>
        <div class="custom-input-group" style="margin-top: 8px;">
          <input id="custom-site-input" type="text" placeholder="自定义域名...">
          <button id="custom-site-btn" class="enhancer-btn" data-op="custom-site">搜索</button>
        </div>
      </div>
      <div class="enhancer-group">
        <h3>文件类型</h3>
        <div class="enhancer-btn-grid" id="filetype-preset-grid"></div>
      </div>
      <div class="enhancer-group">
        <h3>内容屏蔽</h3>
        <button id="enhancer-blocklist-btn" class="enhancer-btn">🚫 管理屏蔽列表</button>
      </div>
    `;
    document.body.appendChild(panel);

    // 预置按钮填充
    const siteGrid = document.getElementById('site-preset-grid');
    CFG.PRESETS.SITES.forEach(site => {
      const b = document.createElement('button');
      b.className = 'enhancer-btn';
      b.textContent = site.name;
      b.dataset.op = 'site';
      b.dataset.value = site.domain;
      siteGrid.appendChild(b);
    });
    const fileGrid = document.getElementById('filetype-preset-grid');
    CFG.PRESETS.FILETYPES.forEach(type => {
      const b = document.createElement('button');
      b.className = 'enhancer-btn';
      b.textContent = type;
      b.dataset.op = 'filetype';
      b.dataset.value = type.toLowerCase();
      fileGrid.appendChild(b);
    });

    // 屏蔽列表弹窗
    const modal = document.createElement('div');
    modal.id = CFG.UI.BLOCKLIST_MODAL_ID;
    modal.innerHTML = `
      <h2>管理网站屏蔽列表</h2>
      <p>每行输入一个要屏蔽的域名 (例如 zhihu.com)</p>
      <textarea id="${CFG.UI.BLOCKLIST_TEXTAREA_ID}"></textarea>
      <div class="blocklist-buttons">
        <button id="blocklist-save-btn" class="enhancer-btn">保存</button>
        <button id="blocklist-cancel-btn" class="enhancer-btn" style="background-color:#777;">取消</button>
      </div>
    `;
    document.body.appendChild(modal);

    // 交互:FAB
    fab.addEventListener('click', (e) => {
      panel.style.display = panel.style.display === 'block' ? 'none' : 'block';
      Effects.burst(e.clientX, e.clientY, { particleCount: 80, spread: 65 });
    }, { passive: true });

    // 交互:面板按钮(事件委托)
    panel.addEventListener('click', (ev) => {
      const target = ev.target.closest('.enhancer-btn');
      if (!target) return;
      const { op, value, filterParam, filterValue } = target.dataset;
      const base = currentQuery();
      const x = ev.clientX || (target.getBoundingClientRect().left + 16);
      const y = ev.clientY || (target.getBoundingClientRect().top + 8);

      // ---------- [V6.3.1 修正] 开始 ----------
      Effects.burst(x, y, { particleCount: 36, spread: 45 });

      let nq = base;
      switch (op) {
        case 'quotes': nq = `"${base}"`; break;
        case 'broaden': nq = base; break;
        case 'site': nq = `${base} site:${value}`; break;
        case 'custom-site':
          const domain = document.getElementById('custom-site-input').value.trim();
          if (domain) nq = `${base} site:${domain}`;
          break;
        case 'filetype': nq = `${base} filetype:${value}`; break;
      }

      if (filterParam && filterValue) {
        // 时间筛选:使用当前搜索框的查询(nq)并应用时间参数
        const params = new URLSearchParams(window.location.search);
        params.set('q', nq); // 关键:使用从输入框获取的 nq (即 base)
        params.set(filterParam, filterValue);
        window.location.search = params.toString();
      } else if (nq !== base || op === 'broaden') {
        // 站点/精确/扩大 搜索:
        // (op === 'broaden' 修正了 "扩大范围" 按钮在 nq === base 时不触发的问题)
        doSearch(nq);
      }
      // ---------- [V6.3.1 修正] 结束 ----------

    }, { passive: true });

    // 交互:屏蔽列表
    document.getElementById('enhancer-blocklist-btn').addEventListener('click', async (e) => {
      const list = await store.get(CFG.STORAGE_KEYS.BLOCKLIST, '[]');
      document.getElementById(CFG.UI.BLOCKLIST_TEXTAREA_ID).value = list.join('\n');
      modal.style.display = 'block';
      Effects.burst(e.clientX, e.clientY, { particleCount: 24, spread: 35 });
    }, { passive: true });

    document.getElementById('blocklist-save-btn').addEventListener('click', async (e) => {
      const lines = document.getElementById(CFG.UI.BLOCKLIST_TEXTAREA_ID).value
        .split('\n').map(s => s.trim()).filter(Boolean);
      await store.set(CFG.STORAGE_KEYS.BLOCKLIST, [...new Set(lines)]);
      blockedSites = await store.get(CFG.STORAGE_KEYS.BLOCKLIST, '[]');
      modal.style.display = 'none';
      Effects.burst(e.clientX, e.clientY, { particleCount: 40, spread: 55, colors: ['#22c55e', '#16a34a'] });
      applyStaticBlocking(document);
    }, { passive: true });

    document.getElementById('blocklist-cancel-btn').addEventListener('click', (e) => {
      modal.style.display = 'none';
      Effects.ripple(e.clientX, e.clientY);
    }, { passive: true });
  }

  // ---------- 统一节点处理 ----------
  function processNode(root) {
    Containers.relocateToCenter(root);
    Containers.relocateToEnd(root);
    Containers.relocateToSidebar(root);
    applyDynamicAdBlocking(root);
    applyStaticBlocking(root);
    addBlockButtons(root);
  }

  // ---------- [V6.3] 初始化与观察 ----------
  let globalObserver = null; // [V6.3] Store observer globally

  const mutationHandler = debounce((mutations) => {
    for (const m of mutations) {
      if (m.type === 'childList' && m.addedNodes.length) {
        m.addedNodes.forEach(n => {
          if (n.nodeType === Node.ELEMENT_NODE) {
            processNode(n);
          }
        });
      }
    }
  }, 120);

  function initialRun() {
    Containers.ensureCenteredHost();
    scanStylesheetsForAdMarkers();
    processNode(document.body);
  }

  function setupObserver() {
    // [V6.3] Disconnect old observer if it exists
    if (globalObserver) {
      globalObserver.disconnect();
    }

    const targets = [
      qs(document, CFG.SELECTORS.MAIN_CONTENT),
      qs(document, CFG.SELECTORS.RIGHT_RAIL),
      qs(document, CFG.SELECTORS.RESULTS_LIST),
    ].filter(Boolean);

    if (targets.length > 0) {
      globalObserver = new MutationObserver(mutationHandler);
      targets.forEach(t => globalObserver.observe(t, { childList: true, subtree: true }));
    }
  }

  // [V6.3] This function is called on initial load AND subsequent page navigations
  function mainInit() {
      createUI();      // Ensure UI exists (idempotent)
      initialRun();    // Re-process the entire document body
      setupObserver(); // (Re-)attach observer to new containers
  }

  // ---------- [V6.3] PJAX/SPA 导航处理 ----------

  function onPageChange() {
    // Wait for PJAX content to settle
    setTimeout(() => {
      mainInit();
    }, 300); // Delay to allow new DOM to be inserted
  }

  (function setupNavListener() {
    let lastHref = window.location.href;

    // Monkey-patch history methods to fire a custom event
    ['pushState', 'replaceState'].forEach(fn => {
      const orig = history[fn];
      if (orig.patched) return; // Prevent double-patching

      history[fn] = function() {
        const ret = orig.apply(this, arguments);
        window.dispatchEvent(new Event('locationchange_internal'));
        return ret;
      };
      history[fn].patched = true; // Mark as patched
    });

    // Listen for browser back/forward
    window.addEventListener('popstate', onPageChange);

    // Listen for patched history changes
    window.addEventListener('locationchange_internal', onPageChange);

    // Fallback detector for cases where history patching fails
    setInterval(() => {
        if (window.location.href !== lastHref) {
            lastHref = window.location.href;
            onPageChange();
        }
    }, 250);
  })();

  // ---------- 启动 ----------
  injectStyles();

  // [V6.3] Modified initial startup
  (async () => {
    blockedSites = await store.get(CFG.STORAGE_KEYS.BLOCKLIST, '[]');

    domReady(() => {
      // Run the main init logic for the first time
      mainInit();
    });
  })();

})();