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

V6.4.1: 修复域名提取bug,确保屏蔽功能100%生效

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         功能增强型必应美化脚本 (Enhanced Bing Beautifier) - V6.4.1
// @namespace    http://tampermonkey.net/
// @version      6.4.1
// @description  V6.4.1: 修复域名提取bug,确保屏蔽功能100%生效
// @author       Gemini & Enhanced by Assistant
// @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',
      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_algo.b_ad', // 广告搜索结果
        '.b_ad', '.b_ads', '.sb_adsWv2', '.b_adTop',
        'li.b_ans.b_ad', '.ad_sect_line', '.msan_ads_container', '#df_ad',
        '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_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',
      ],
      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', icon: '🎓' },
        { name: '哔哩哔哩', domain: 'bilibili.com', icon: '📺' },
        { name: 'GitHub', domain: 'github.com', icon: '💻' },
        { name: '维基百科', domain: 'wikipedia.org', icon: '📚' },
        { name: '豆瓣', domain: 'douban.com', icon: '🎬' },
        { name: '微博', domain: 'weibo.com', icon: '📱' },
        { name: '少数派', domain: 'sspai.com', icon: '⚡' },
        { name: 'CSDN', domain: 'csdn.net', icon: '💾' },
        { name: 'V2EX', domain: 'v2ex.com', icon: '🔧' },
        { name: 'Stack Overflow', domain: 'stackoverflow.com', icon: '📖' },
        { name: 'Reddit', domain: 'reddit.com', icon: '🌐' },
      ],
      FILETYPES: [
        { name: 'PDF', ext: 'pdf', icon: '📄' },
        { name: 'DOCX', ext: 'docx', icon: '📝' },
        { name: 'PPTX', ext: 'pptx', icon: '📊' },
        { name: 'XLSX', ext: 'xlsx', icon: '📈' }
      ],
    },
    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.75);
        --glass-bg-dark: rgba(25, 25, 40, 0.8);
        --glass-border: rgba(255, 255, 255, 0.4);
        --glass-border-dark: rgba(255, 255, 255, 0.2);
        --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
        --glass-shadow-hover: 0 12px 48px rgba(0, 0, 0, 0.15);
        --card-radius: 20px;
        --panel-radius: 16px;
        --accent: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        --accent-hover: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
        --success: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
        --danger: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
      }

      /* 背景美化 */
      body {
        background: url('https://raw.githubusercontent.com/WJH-makers/markdown_photos/main/images/sea.png') center/cover fixed no-repeat !important;
        position: relative;
      }
      body::before {
        content: '';
        position: fixed;
        inset: 0;
        background: linear-gradient(180deg, rgba(103,126,234,0.05) 0%, rgba(118,75,162,0.05) 100%);
        pointer-events: none;
        z-index: -1;
      }

      /* 全局透明 */
      #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: 24px !important;
        border-radius: var(--card-radius);
        background: var(--glass-bg);
        backdrop-filter: blur(20px) saturate(180%);
        -webkit-backdrop-filter: blur(20px) saturate(180%);
        border: 1.5px solid var(--glass-border);
        box-shadow: var(--glass-shadow),
                    inset 0 1px 0 rgba(255,255,255,0.5) !important;
        transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
      }
      #gemini_centered_container > li[data-centered="true"]:hover {
        transform: translateY(-6px) scale(1.01);
        box-shadow: var(--glass-shadow-hover),
                    inset 0 1px 0 rgba(255,255,255,0.6) !important;
      }
      body.b_dark #gemini_centered_container > li[data-centered="true"] {
        background: var(--glass-bg-dark);
        border-color: var(--glass-border-dark);
        box-shadow: var(--glass-shadow),
                    inset 0 1px 0 rgba(255,255,255,0.1) !important;
      }

      /* 底部卡片 */
      ${S.MAIN_CONTENT} > li[data-relocated-bottom="true"] {
        width: 90%; max-width: 1200px; margin: 40px auto 20px auto !important; padding: 24px !important;
        border-radius: var(--card-radius);
        background: var(--glass-bg);
        backdrop-filter: blur(20px) saturate(180%);
        -webkit-backdrop-filter: blur(20px) saturate(180%);
        border: 1.5px solid var(--glass-border);
        box-shadow: var(--glass-shadow),
                    inset 0 1px 0 rgba(255,255,255,0.5) !important;
        transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
      }
      ${S.MAIN_CONTENT} > li[data-relocated-bottom="true"]:hover {
        transform: translateY(-4px);
        box-shadow: var(--glass-shadow-hover),
                    inset 0 1px 0 rgba(255,255,255,0.6) !important;
      }
      body.b_dark ${S.MAIN_CONTENT} > li[data-relocated-bottom="true"] {
        background: var(--glass-bg-dark);
        border-color: var(--glass-border-dark);
      }

      /* 右栏卡片 */
      ${CFG.SELECTORS.RIGHT_RAIL} > li[data-relocated="true"] {
        display: block !important; width: 100%; box-sizing: border-box;
        margin: 0 0 20px 0; padding: 18px !important;
        border-radius: 14px;
        background: var(--glass-bg);
        backdrop-filter: blur(16px) saturate(160%);
        -webkit-backdrop-filter: blur(16px) saturate(160%);
        border: 1px solid var(--glass-border);
        box-shadow: 0 6px 24px rgba(0,0,0,0.08),
                    inset 0 1px 0 rgba(255,255,255,0.4) !important;
        transition: all 0.3s ease;
      }
      ${CFG.SELECTORS.RIGHT_RAIL} > li[data-relocated="true"]:hover {
        transform: translateX(-4px);
        box-shadow: 0 8px 32px rgba(0,0,0,0.12),
                    inset 0 1px 0 rgba(255,255,255,0.5) !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):not(.b_ad),
      ${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: var(--glass-bg) !important;
        backdrop-filter: blur(24px) saturate(200%) !important;
        -webkit-backdrop-filter: blur(24px) saturate(200%) !important;
        border: 1.5px solid var(--glass-border) !important;
        border-radius: var(--card-radius) !important;
        padding: 22px !important;
        margin-bottom: 24px !important;
        box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15),
                    inset 0 1px 1px rgba(255,255,255,0.5),
                    0 1px 0 rgba(255,255,255,0.25) !important;
        transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
        position: relative;
        overflow: hidden;
      }

      /* 卡片高光效果 */
      ${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):not(.b_ad)::before,
      ${S.RESULTS_LIST} > li.b_ans:not([data-relocated="true"]):not([data-centered="true"])::before {
        content: '';
        position: absolute;
        top: 0;
        left: -100%;
        width: 100%;
        height: 100%;
        background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
        transition: left 0.5s;
        pointer-events: none;
      }
      ${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):not(.b_ad):hover::before,
      ${S.RESULTS_LIST} > li.b_ans:not([data-relocated="true"]):not([data-centered="true"]):hover::before {
        left: 100%;
      }

      /* 卡片悬停效果 */
      ${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):not(.b_ad):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(-6px) scale(1.01);
        box-shadow: 0 16px 48px rgba(31, 38, 135, 0.2),
                    inset 0 2px 4px rgba(255,255,255,0.6),
                    0 2px 0 rgba(255,255,255,0.4) !important;
        background: rgba(255,255,255,0.85) !important;
        border-color: rgba(255,255,255,0.6) !important;
      }

      /* 暗色模式 */
      body.b_dark ${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):not(.b_ad),
      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;
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3),
                    inset 0 1px 1px rgba(255,255,255,0.1) !important;
      }
      body.b_dark ${S.RESULTS_LIST} > li.b_algo:not(.b_nwsAns):not(.b_ad):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.9) !important;
        box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4),
                    inset 0 2px 4px rgba(255,255,255,0.15) !important;
      }

      /* 文字美化 */
      .b_algo h2 a {
        color: #1a1a2e !important;
        font-weight: 600;
        transition: color 0.3s ease;
      }
      .b_algo h2 a:hover {
        color: #667eea !important;
      }
      body.b_dark .b_algo h2 a { color: #e0e0ff !important; }
      body.b_dark .b_algo h2 a:hover { color: #a5b4fc !important; }

      .b_caption p, .b_caption .b_lineclamp3 {
        color: #4a4a4a !important;
        line-height: 1.6;
      }
      body.b_dark .b_caption p,
      body.b_dark .b_caption .b_lineclamp3 { color: #d0d0d0 !important; }

      cite {
        color: #16a34a !important;
        font-style: normal;
        font-weight: 500;
        transition: color 0.3s ease;
      }
      cite:hover { color: #15803d !important; }
      body.b_dark cite { color: #86efac !important; }
      body.b_dark cite:hover { color: #4ade80 !important; }

      /* FAB 按钮 - 呼吸动画 */
      #search-enhancer-fab {
        position: fixed; top: 120px; right: 20px; z-index: 9999;
        width: 56px; height: 56px;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: #fff; border-radius: 50%;
        display: flex; align-items: center; justify-content: center;
        font-size: 24px; cursor: pointer;
        backdrop-filter: blur(10px);
        box-shadow: 0 8px 24px rgba(102, 126, 234, 0.4),
                    inset 0 1px 0 rgba(255,255,255,0.3);
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        animation: fab-breathe 3s ease-in-out infinite;
      }
      @keyframes fab-breathe {
        0%, 100% { transform: scale(1); box-shadow: 0 8px 24px rgba(102, 126, 234, 0.4); }
        50% { transform: scale(1.05); box-shadow: 0 12px 32px rgba(102, 126, 234, 0.6); }
      }
      #search-enhancer-fab:hover {
        transform: scale(1.12) rotate(90deg);
        box-shadow: 0 12px 36px rgba(102, 126, 234, 0.6),
                    inset 0 2px 0 rgba(255,255,255,0.4);
        animation: none;
      }
      #search-enhancer-fab:active {
        transform: scale(1.05) rotate(90deg);
      }

      /* 面板 - 现代卡片设计 */
      #search-enhancer-panel {
        position: fixed; top: 190px; right: 20px; z-index: 9998;
        width: 360px; padding: 0;
        background: var(--glass-bg);
        backdrop-filter: blur(30px) saturate(180%);
        -webkit-backdrop-filter: blur(30px) saturate(180%);
        border-radius: var(--panel-radius);
        box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2),
                    inset 0 1px 0 rgba(255,255,255,0.5);
        border: 1.5px solid var(--glass-border);
        display: none;
        overflow: hidden;
        animation: panel-slide-in 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      @keyframes panel-slide-in {
        from { opacity: 0; transform: translateY(-10px); }
        to { opacity: 1; transform: translateY(0); }
      }
      body.b_dark #search-enhancer-panel {
        background: var(--glass-bg-dark);
        border-color: var(--glass-border-dark);
      }

      /* 面板组 */
      .enhancer-group {
        padding: 16px 18px;
        border-bottom: 1px solid rgba(0,0,0,0.06);
        transition: background 0.2s ease;
      }
      .enhancer-group:last-child {
        border-bottom: none;
      }
      .enhancer-group:hover {
        background: rgba(255,255,255,0.3);
      }
      body.b_dark .enhancer-group {
        border-bottom-color: rgba(255,255,255,0.08);
      }
      body.b_dark .enhancer-group:hover {
        background: rgba(255,255,255,0.05);
      }

      .enhancer-group h3 {
        font-size: 13px;
        font-weight: 700;
        margin: 0 0 12px 0;
        color: #374151;
        text-transform: uppercase;
        letter-spacing: 0.5px;
        display: flex;
        align-items: center;
        gap: 6px;
      }
      body.b_dark .enhancer-group h3 { color: #e5e7eb; }

      /* 按钮网格 */
      .enhancer-btn-grid {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
      }

      /* 按钮 - 渐变 + 动画 */
      .enhancer-btn {
        padding: 8px 14px;
        border-radius: 10px;
        border: none;
        cursor: pointer;
        background: var(--accent);
        color: #fff;
        font-size: 13px;
        font-weight: 600;
        transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
        box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
        position: relative;
        overflow: hidden;
      }
      .enhancer-btn::before {
        content: '';
        position: absolute;
        top: 50%;
        left: 50%;
        width: 0;
        height: 0;
        border-radius: 50%;
        background: rgba(255,255,255,0.3);
        transform: translate(-50%, -50%);
        transition: width 0.6s, height 0.6s;
      }
      .enhancer-btn:hover::before {
        width: 300px;
        height: 300px;
      }
      .enhancer-btn:hover {
        transform: translateY(-2px);
        box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4);
        background: var(--accent-hover);
      }
      .enhancer-btn:active {
        transform: translateY(0) scale(0.98);
      }

      /* 自定义输入组 */
      .custom-input-group {
        display: flex;
        gap: 8px;
        margin-top: 10px;
      }
      .custom-input-group input {
        flex-grow: 1;
        padding: 8px 12px;
        border-radius: 10px;
        border: 1.5px solid rgba(102, 126, 234, 0.3);
        background: rgba(255,255,255,0.5);
        backdrop-filter: blur(10px);
        font-size: 13px;
        transition: all 0.3s ease;
        color: #1a1a2e;
      }
      .custom-input-group input:focus {
        outline: none;
        border-color: #667eea;
        background: rgba(255,255,255,0.8);
        box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
      }
      body.b_dark .custom-input-group input {
        background: rgba(255,255,255,0.1);
        border-color: rgba(255,255,255,0.2);
        color: #e5e7eb;
      }
      body.b_dark .custom-input-group input:focus {
        background: rgba(255,255,255,0.15);
        border-color: #a5b4fc;
      }

      /* 屏蔽按钮 */
      .block-site-btn {
        margin-left: 8px;
        cursor: pointer;
        color: #ef4444;
        font-weight: 600;
        font-size: 12px;
        padding: 2px 8px;
        border-radius: 6px;
        transition: all 0.2s ease;
        display: inline-block;
      }
      .block-site-btn:hover {
        background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
        color: white;
        transform: scale(1.05);
      }
      .block-site-btn.blocked {
        background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
        color: white !important;
        cursor: default;
        font-weight: 500;
      }
      .block-site-btn.blocked:hover {
        transform: none;
      }

      /* 模态框 */
      #blocklist-modal {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        z-index: 10001;
        width: 540px;
        max-width: 90vw;
        padding: 28px;
        background: var(--glass-bg);
        backdrop-filter: blur(40px) saturate(200%);
        -webkit-backdrop-filter: blur(40px) saturate(200%);
        border-radius: 20px;
        box-shadow: 0 24px 64px rgba(0, 0, 0, 0.3),
                    inset 0 1px 0 rgba(255,255,255,0.6);
        border: 2px solid var(--glass-border);
        display: none;
        animation: modal-zoom-in 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      @keyframes modal-zoom-in {
        from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
        to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
      }
      body.b_dark #blocklist-modal {
        background: var(--glass-bg-dark);
        border-color: var(--glass-border-dark);
      }

      #blocklist-modal h2 {
        color: #1a1a2e;
        margin: 0 0 8px 0;
        font-size: 22px;
        font-weight: 700;
      }
      #blocklist-modal p {
        color: #6b7280;
        margin: 0 0 16px 0;
        font-size: 14px;
      }
      body.b_dark #blocklist-modal h2 { color: #f3f4f6; }
      body.b_dark #blocklist-modal p { color: #9ca3af; }

      #blocklist-textarea {
        width: calc(100% - 24px);
        height: 240px;
        padding: 12px;
        border-radius: 12px;
        border: 2px solid rgba(102, 126, 234, 0.3);
        background: rgba(255,255,255,0.6);
        backdrop-filter: blur(10px);
        font-family: 'Consolas', 'Monaco', monospace;
        font-size: 13px;
        color: #1a1a2e;
        resize: vertical;
        transition: all 0.3s ease;
      }
      #blocklist-textarea:focus {
        outline: none;
        border-color: #667eea;
        background: rgba(255,255,255,0.8);
        box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
      }
      body.b_dark #blocklist-textarea {
        background: rgba(255,255,255,0.08);
        border-color: rgba(255,255,255,0.2);
        color: #e5e7eb;
      }
      body.b_dark #blocklist-textarea:focus {
        background: rgba(255,255,255,0.12);
        border-color: #a5b4fc;
      }

      .blocklist-buttons {
        margin-top: 18px;
        display: flex;
        gap: 10px;
        justify-content: flex-end;
      }
      .blocklist-buttons .enhancer-btn {
        min-width: 80px;
      }
      #blocklist-save-btn {
        background: var(--success);
      }
      #blocklist-save-btn:hover {
        background: linear-gradient(135deg, #16a34a 0%, #22c55e 100%);
      }
      #blocklist-cancel-btn {
        background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%);
      }
      #blocklist-cancel-btn:hover {
        background: linear-gradient(135deg, #4b5563 0%, #6b7280 100%);
      }

      /* [V6.4.1] 强制隐藏已屏蔽的结果 */
      li.b_algo[data-blocked="true"],
      li.b_ans[data-blocked="true"] {
        display: none !important;
      }
    `);
  }

  // ---------- 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 = [];

  /**
   * [V6.4.1 修复] 从cite元素提取域名
   * 正确处理特殊字符(›、»、空格等)
   */
  function domainFromCite(citeEl) {
    if (!citeEl || !citeEl.textContent) return null;
    const t = citeEl.textContent.trim();

    // 处理特殊字符分隔符,只取URL部分
    // \u203A = ›, \u00BB = », 还有中文空格等
    const urlPart = t.split(/[\s\u203A\u00BB›»\u3000]+/)[0];

    // 只匹配合法的域名字符(RFC标准)
    const m = urlPart.match(/^(?:https?:\/\/)?(?:www\.)?([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*)/i);

    const domain = m ? m[1].toLowerCase() : null;

    // 调试日志(可选)
    if (domain) {
      console.debug(`[域名提取] "${t}" => "${domain}"`);
    }

    return domain;
  }

  /**
   * [V6.4.1] 应用静态屏蔽
   */
  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 domain = domainFromCite(cite);

        if (domain && blockedSites.includes(domain)) {
          result.dataset.blocked = 'true';
          result.style.display = 'none';
          console.debug(`[屏蔽] ${domain}`);
        }
      });
  }

  /**
   * [V6.4.1] 添加屏蔽按钮
   */
  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 domain = domainFromCite(cite);

        if (!domain || !cite || !cite.parentNode) return;

        const btn = document.createElement('span');
        btn.className = 'block-site-btn';

        if (blockedSites.includes(domain)) {
          btn.textContent = '✓ 已屏蔽';
          btn.classList.add('blocked');
        } else {
          btn.textContent = '🚫 屏蔽';
          btn.title = `屏蔽 ${domain}`;
          btn.addEventListener('click', async (e) => {
            e.preventDefault();
            e.stopPropagation();

            // 更新按钮状态
            btn.textContent = '✓ 已屏蔽';
            btn.classList.add('blocked');
            btn.title = '';

            // 粒子效果
            Effects.burst(e.clientX, e.clientY, {
              particleCount: 80,
              spread: 65,
              colors: ['#22c55e', '#16a34a', '#4ade80', '#86efac']
            });

            // 动画隐藏
            result.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
            result.style.opacity = '0';
            result.style.transform = 'scale(0.95)';
            setTimeout(() => {
              result.dataset.blocked = 'true';
              result.style.display = 'none';
            }, 300);

            // 保存到屏蔽列表
            if (!blockedSites.includes(domain)) {
              blockedSites.push(domain);
              await store.set(CFG.STORAGE_KEYS.BLOCKLIST, [...new Set(blockedSites)]);
              console.log(`[已添加屏蔽] ${domain}`);
            }
          }, { 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();
  }

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

  function createUI() {
    if (document.getElementById(CFG.UI.FAB_ID)) return;

    // 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">
          <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" style="width: 100%;">管理屏蔽列表</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.innerHTML = `${site.icon} ${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.innerHTML = `${type.icon} ${type.name}`;
      b.dataset.op = 'filetype';
      b.dataset.value = type.ext;
      fileGrid.appendChild(b);
    });

    // 模态框
    const modal = document.createElement('div');
    modal.id = CFG.UI.BLOCKLIST_MODAL_ID;
    modal.innerHTML = `
      <h2>🛡️ 网站屏蔽列表</h2>
      <p>每行输入一个域名(例如:qiniu.com),保存后自动刷新</p>
      <textarea id="${CFG.UI.BLOCKLIST_TEXTAREA_ID}" placeholder="qiniu.com&#10;zhihu.com&#10;csdn.net"></textarea>
      <div class="blocklist-buttons">
        <button id="blocklist-cancel-btn" class="enhancer-btn">取消</button>
        <button id="blocklist-save-btn" class="enhancer-btn">保存并刷新</button>
      </div>
    `;
    document.body.appendChild(modal);

    // 交互:FAB
    fab.addEventListener('click', (e) => {
      const isVisible = panel.style.display === 'block';
      panel.style.display = isVisible ? 'none' : 'block';
      Effects.burst(e.clientX, e.clientY, {
        particleCount: isVisible ? 40 : 100,
        spread: isVisible ? 45 : 75,
        colors: isVisible ? ['#6b7280'] : ['#667eea', '#764ba2', '#a5b4fc']
      });
    }, { 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);

      Effects.burst(x, y, {
        particleCount: 50,
        spread: 55,
        colors: ['#667eea', '#764ba2', '#a5b4fc', '#c4b5fd']
      });

      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) {
        const params = new URLSearchParams(window.location.search);
        params.set('q', nq);
        params.set(filterParam, filterValue);
        window.location.search = params.toString();
      } else if (nq !== base || op === 'broaden') {
        doSearch(nq);
      }
    }, { 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: 60,
        spread: 50,
        colors: ['#ef4444', '#dc2626', '#f87171']
      });
    }, { 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().toLowerCase())
        .filter(Boolean);

      await store.set(CFG.STORAGE_KEYS.BLOCKLIST, [...new Set(lines)]);

      Effects.burst(e.clientX, e.clientY, {
        particleCount: 120,
        spread: 80,
        colors: ['#22c55e', '#16a34a', '#4ade80', '#86efac']
      });

      setTimeout(() => {
        window.location.reload();
      }, 600);
    }, { 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);
  }

  // ---------- 初始化与观察 ----------
  let globalObserver = null;

  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() {
    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 }));
    }
  }

  function mainInit() {
    createUI();
    initialRun();
    setupObserver();
  }

  // ---------- PJAX/SPA 导航处理 ----------
  function onPageChange() {
    setTimeout(() => {
      mainInit();
    }, 300);
  }

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

    ['pushState', 'replaceState'].forEach(fn => {
      const orig = history[fn];
      if (orig.patched) return;

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

    window.addEventListener('popstate', onPageChange);
    window.addEventListener('locationchange_internal', onPageChange);

    setInterval(() => {
      if (window.location.href !== lastHref) {
        lastHref = window.location.href;
        onPageChange();
      }
    }, 250);
  })();

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

  (async () => {
    blockedSites = await store.get(CFG.STORAGE_KEYS.BLOCKLIST, '[]');
    console.log('[屏蔽列表]', blockedSites);

    domReady(() => {
      mainInit();
    });
  })();

})();