Clean up your search results! Highlights preferred websites and fades or completely hides sites you dislike on Google, Bing, DuckDuckGo, Brave, and Yandex.
// ==UserScript==
// @name Better Search ⭐
// @namespace https://github.com/quantavil/userscript/better-search
// @version 3.4
// @author quantavil
// @description Clean up your search results! Highlights preferred websites and fades or completely hides sites you dislike on Google, Bing, DuckDuckGo, Brave, and Yandex.
// @license MIT
// @homepage https://github.com/quantavil/userscript
// @homepageURL https://github.com/quantavil/userscript
// @include /^https?:\/\/(?:www\.)?google\.[a-z]{2,6}(?:\.[a-z]{2,3})?\/search/
// @include *://www.bing.com/search*
// @include *://*.bing.com/search*
// @include *://duckduckgo.com/*
// @include *://*.duckduckgo.com/*
// @include *://search.brave.com/search*
// @include /^https?:\/\/(?:[a-z]+\.)?yandex\.[a-z]{2,}\/search/
// @include /^https?:\/\/(?:[a-z]+\.)?ya\.ru\/search/
// @connect gist.githubusercontent.com
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_setClipboard
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
const SVF_CONFIG = {
/** Prefix for all localStorage / GM storage keys */
STORAGE_PREFIX: "svf_",
/** data-attribute written to result items for identification */
ITEM_ATTR: "data-svf-id",
/** How long (ms) to debounce the scanner after DOM mutations */
SCAN_DEBOUNCE_MS: 120,
/** How long (ms) to debounce settings saves */
SAVE_DEBOUNCE_MS: 800,
/** Default liked domains prepopulated on first install */
DEFAULT_LIKED: [],
/** Default disliked domains prepopulated on first install */
DEFAULT_DISLIKED: []
};
function gmGet(key, fallback) {
try {
if (typeof GM_getValue === "function") {
const v2 = GM_getValue(SVF_CONFIG.STORAGE_PREFIX + key, void 0);
return v2 !== void 0 && v2 !== null ? v2 : fallback;
}
const raw = localStorage.getItem(SVF_CONFIG.STORAGE_PREFIX + key);
return raw ? JSON.parse(raw) : fallback;
} catch (err) {
console.error("[SVF] Failed to read from storage:", err);
return fallback;
}
}
function gmSet(key, value) {
try {
if (typeof GM_setValue === "function") {
GM_setValue(SVF_CONFIG.STORAGE_PREFIX + key, value);
return;
}
localStorage.setItem(SVF_CONFIG.STORAGE_PREFIX + key, JSON.stringify(value));
} catch (err) {
console.error("[SVF] Failed to write to storage:", err);
}
}
class Store {
constructor() {
this._saveTimers = {};
this._listeners = /* @__PURE__ */ new Set();
this._settings = {
liked: gmGet("liked", SVF_CONFIG.DEFAULT_LIKED),
disliked: gmGet("disliked", SVF_CONFIG.DEFAULT_DISLIKED),
dislikeMode: gmGet("dislikeMode", "fade"),
showTrigger: gmGet("showTrigger", true),
accent: gmGet("accent", "emerald")
};
this._pagehideHandler = () => {
for (const key of Object.keys(this._saveTimers)) {
const timer = this._saveTimers[key];
if (timer) {
clearTimeout(timer);
gmSet(key, this._settings[key]);
}
}
};
window.addEventListener("pagehide", this._pagehideHandler);
}
// ── Accessors ─────────────────────────────────────────────────────────────
get liked() {
return this._settings.liked;
}
get disliked() {
return this._settings.disliked;
}
get dislikeMode() {
return this._settings.dislikeMode;
}
get showTrigger() {
return this._settings.showTrigger;
}
get accent() {
return this._settings.accent;
}
/** Returns 'liked' | 'disliked' | 'normal' for a given bare domain. Supports wildcards. */
matchDomain(domain) {
const d2 = domain.toLowerCase();
const hit = (list) => list.some((rule) => {
if (rule.startsWith("*.")) {
const r2 = rule.slice(2);
return d2 === r2 || d2.endsWith("." + r2);
}
return d2 === rule;
});
if (hit(this._settings.liked)) return "liked";
if (hit(this._settings.disliked)) return "disliked";
return "normal";
}
// ── Mutations ─────────────────────────────────────────────────────────────
addDomain(list, domain) {
const d2 = normalizeDomain(domain);
const otherList = list === "liked" ? "disliked" : "liked";
if (!d2 || this._settings[list].includes(d2)) return;
this._settings[otherList] = this._settings[otherList].filter((x2) => x2 !== d2);
this._settings[list] = [...this._settings[list], d2].sort();
this._persist("liked");
this._persist("disliked");
this._notify();
}
removeDomain(list, domain) {
const d2 = normalizeDomain(domain);
this._settings[list] = this._settings[list].filter((x2) => x2 !== d2);
this._persist(list);
this._notify();
}
setDomains(list, domains) {
const normalized = domains.map((x2) => normalizeDomain(x2)).filter(Boolean);
const otherList = list === "liked" ? "disliked" : "liked";
this._settings[list] = [...new Set(normalized)];
this._settings[otherList] = this._settings[otherList].filter((x2) => !this._settings[list].includes(x2));
this._persist("liked");
this._persist("disliked");
this._notify();
}
setDislikeMode(mode) {
this._settings.dislikeMode = mode;
this._persist("dislikeMode");
this._notify();
}
setShowTrigger(show) {
this._settings.showTrigger = show;
this._persist("showTrigger");
this._notify();
}
setAccent(accent) {
this._settings.accent = accent;
this._persist("accent");
this._notify();
}
/** Export current domain lists as a plain object. */
exportDomains() {
return {
liked: [...this._settings.liked],
disliked: [...this._settings.disliked]
};
}
/** Merge imported domains into current lists (additive, deduplicated). */
importDomains(data) {
let added = 0;
if (Array.isArray(data.liked)) {
for (const raw of data.liked) {
const d2 = normalizeDomain(raw);
if (d2 && !this._settings.liked.includes(d2)) {
this._settings.disliked = this._settings.disliked.filter((x2) => x2 !== d2);
this._settings.liked = [...this._settings.liked, d2];
added++;
}
}
}
if (Array.isArray(data.disliked)) {
for (const raw of data.disliked) {
const d2 = normalizeDomain(raw);
if (d2 && !this._settings.disliked.includes(d2)) {
this._settings.liked = this._settings.liked.filter((x2) => x2 !== d2);
this._settings.disliked = [...this._settings.disliked, d2];
added++;
}
}
}
if (added > 0) {
this._persist("liked");
this._persist("disliked");
this._notify();
}
return added;
}
// ── Subscriptions ─────────────────────────────────────────────────────────
/** Subscribe to any settings change. Returns an unsubscribe fn. */
subscribe(fn) {
this._listeners.add(fn);
return () => this._listeners.delete(fn);
}
destroy() {
window.removeEventListener("pagehide", this._pagehideHandler);
for (const key of Object.keys(this._saveTimers)) {
const timer = this._saveTimers[key];
if (timer) {
clearTimeout(timer);
gmSet(key, this._settings[key]);
}
}
this._saveTimers = {};
this._listeners.clear();
}
// ── Internal ──────────────────────────────────────────────────────────────
_notify() {
this._listeners.forEach((fn) => {
try {
fn();
} catch (err) {
console.error("[SVF] Subscriber error during notify:", err);
}
});
}
_persist(key) {
clearTimeout(this._saveTimers[key]);
this._saveTimers[key] = setTimeout(() => {
gmSet(key, this._settings[key]);
}, SVF_CONFIG.SAVE_DEBOUNCE_MS);
}
}
function normalizeDomain(input) {
let s2 = input.trim().toLowerCase();
if (!s2) return "";
const hasWildcard = s2.startsWith("*.");
if (hasWildcard) {
s2 = s2.slice(2);
}
try {
if (!s2.includes("://")) {
s2 = "http://" + s2;
}
s2 = new URL(s2).hostname;
} catch {
return "";
}
s2 = s2.startsWith("www.") ? s2.slice(4) : s2;
return hasWildcard ? "*." + s2 : s2;
}
const ACCENT_RAW = {
emerald: { hex: "#10b981", rgb: "16, 185, 129" },
indigo: { hex: "#6366f1", rgb: "99, 102, 241" },
blue: { hex: "#3b82f6", rgb: "59, 130, 246" },
teal: { hex: "#06b6d4", rgb: "6, 182, 212" },
rose: { hex: "#f43f5e", rgb: "244, 63, 94" },
amber: { hex: "#f59e0b", rgb: "245, 158, 11" }
};
const ACCENT_COLORS = Object.keys(ACCENT_RAW).reduce((acc, key) => {
const { hex, rgb } = ACCENT_RAW[key];
acc[key] = {
primary: hex,
glow: `rgba(${rgb}, 0.15)`,
bg: `rgba(${rgb}, 0.04)`,
bgHover: `rgba(${rgb}, 0.08)`,
border: `rgba(${rgb}, 0.25)`,
statusBg: `rgba(${rgb}, 0.1)`,
dotGlow: `rgba(${rgb}, 0.6)`
};
return acc;
}, {});
const ACCENT_VARS = [
"--svf-primary",
"--svf-primary-glow",
"--svf-primary-bg",
"--svf-primary-bg-hover",
"--svf-primary-border",
"--svf-primary-status-bg"
];
function applyAccentVariables(el, accent) {
const current = ACCENT_COLORS[accent] || ACCENT_COLORS.emerald;
el.style.setProperty("--svf-primary", current.primary);
el.style.setProperty("--svf-primary-glow", current.glow);
el.style.setProperty("--svf-primary-bg", current.bg);
el.style.setProperty("--svf-primary-bg-hover", current.bgHover);
el.style.setProperty("--svf-primary-border", current.border);
el.style.setProperty("--svf-primary-status-bg", current.statusBg);
}
function removeAccentVariables(el) {
for (const key of ACCENT_VARS) {
el.style.removeProperty(key);
}
}
const BASE_CSS = '@layer svf-filters{[data-svf-id].svf-liked{position:relative!important;border-left:4px solid var(--svf-primary, #10b981)!important;border-radius:0 6px 6px 0!important;padding-left:12px!important;background:var(--svf-primary-bg, rgba(16, 185, 129, .04))!important;transition:border-color .2s,background .2s!important}[data-svf-id].svf-liked:after{content:"✦";position:absolute;top:6px;right:10px;font-size:10px;font-weight:700;letter-spacing:.4px;color:var(--svf-primary, #10b981);background:var(--svf-primary-glow, rgba(16, 185, 129, .12));padding:2px 8px;border-radius:10px;pointer-events:none;z-index:1;font-family:system-ui,sans-serif}[data-svf-id].svf-fade{opacity:.14!important;filter:grayscale(.8)!important;transition:opacity .25s,filter .25s!important}[data-svf-id].svf-fade:hover{opacity:.9!important;filter:none!important}[data-svf-id].svf-hide{position:relative!important;max-height:36px!important;overflow:hidden!important;opacity:1!important;cursor:pointer!important;border:1px dashed rgba(239,68,68,.25)!important;border-radius:6px!important;background:#ef444408!important;transition:max-height .3s ease,background .2s!important;margin:4px 0!important}[data-svf-id].svf-hide:before{content:"⊘ " attr(data-svf-id-domain) " — blocked (click to reveal)";display:block!important;padding:9px 14px!important;font-size:11.5px!important;font-weight:500!important;color:#ef4444b3!important;font-family:system-ui,sans-serif!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;pointer-events:none!important}[data-svf-id].svf-hide>*{display:none!important}[data-svf-id].svf-hide:hover{background:#ef44440f!important}[data-svf-id].svf-revealed{max-height:none!important;cursor:auto!important;border:none!important;background:transparent!important}[data-svf-id].svf-revealed:before{display:none!important}[data-svf-id].svf-revealed>*{pointer-events:auto!important;user-select:auto!important}}';
class FilterStyleSheet {
constructor(store) {
this._store = store;
this._el = document.createElement("style");
this._el.id = "svf-styles";
this._el.textContent = BASE_CSS;
document.head.appendChild(this._el);
this._updateVariables();
this._unsub = this._store.subscribe(() => this._updateVariables());
}
_updateVariables() {
const accent = this._store.accent;
applyAccentVariables(document.documentElement, accent);
}
destroy() {
this._unsub();
this._el.remove();
removeAccentVariables(document.documentElement);
}
}
const ATTR$1 = SVF_CONFIG.ITEM_ATTR;
const DOMAIN_ATTR$1 = `${ATTR$1}-domain`;
let _counter = 0;
const nextId = () => `${++_counter}`;
class Scanner {
constructor(store, engine, abortSignal) {
this._container = null;
this._observer = null;
this._debounce = null;
this._revealedElements = /* @__PURE__ */ new WeakSet();
this._store = store;
this._engine = engine;
this._abortSignal = abortSignal;
}
/** Find the container and start observing. Returns false if not found yet. */
attach() {
const container = document.querySelector(this._engine.containerSelector);
if (!container) return false;
if (this._container === container) return true;
this._disconnect();
this._container = container;
this._observer = new MutationObserver((mutations) => {
let shouldScan = false;
for (const m2 of mutations) {
if (m2.addedNodes.length > 0) {
shouldScan = true;
break;
}
if (m2.type === "attributes" && m2.attributeName === "href") {
shouldScan = true;
break;
}
}
if (!shouldScan) return;
this._scheduleScan();
});
this._observer.observe(container, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["href"]
});
this.scanAll();
return true;
}
/** Full re-scan of all items. Called on init and on settings change. */
scanAll() {
if (!this._container) return;
const items = this._container.querySelectorAll(this._engine.itemSelector);
items.forEach((item) => this._processItem(item));
}
/** Re-apply classes to all already-scanned items (e.g. after settings change). */
reapply() {
if (!this._container) return;
const items = this._container.querySelectorAll(`[${ATTR$1}]`);
items.forEach((item) => {
const domain = item.getAttribute(DOMAIN_ATTR$1) ?? "";
this._applyClass(item, domain);
});
}
destroy() {
this._disconnect();
if (this._debounce) clearTimeout(this._debounce);
}
// ── Private ───────────────────────────────────────────────────────────────
_scheduleScan() {
if (this._debounce) clearTimeout(this._debounce);
this._debounce = setTimeout(() => this.scanAll(), SVF_CONFIG.SCAN_DEBOUNCE_MS);
}
_processItem(item) {
if (!(item instanceof HTMLElement)) return;
if (item.parentElement && item.parentElement.closest(this._engine.itemSelector)) {
return;
}
if (item.hasAttribute(ATTR$1)) {
const domain2 = item.getAttribute(DOMAIN_ATTR$1) ?? "";
this._applyClass(item, domain2);
return;
}
const href = this._engine.extractUrl(item);
if (!href) return;
const domain = normalizeDomain(href);
if (!domain) return;
const id = nextId();
item.setAttribute(ATTR$1, id);
item.setAttribute(DOMAIN_ATTR$1, domain);
this._applyClass(item, domain);
this._bindReveal(item);
}
_applyClass(item, domain) {
item.getAttribute(ATTR$1) ?? "";
const match = this._store.matchDomain(domain);
const mode = this._store.dislikeMode;
item.classList.remove(
`svf-liked`,
`svf-fade`,
`svf-hide`,
`svf-revealed`
);
if (match === "liked") {
item.classList.add(`svf-liked`);
} else if (match === "disliked") {
if (mode === "hide") {
if (this._revealedElements.has(item)) {
item.classList.add(`svf-revealed`);
} else {
item.classList.add(`svf-hide`);
}
} else {
item.classList.add(`svf-fade`);
}
}
}
_bindReveal(item) {
item.addEventListener("click", (e2) => {
if (!item.classList.contains(`svf-hide`)) return;
e2.preventDefault();
e2.stopPropagation();
this._revealedElements.add(item);
item.classList.remove(`svf-hide`);
item.classList.add(`svf-revealed`);
}, { capture: true, signal: this._abortSignal });
}
_disconnect() {
this._observer?.disconnect();
this._observer = null;
this._container = null;
}
}
const t$3 = globalThis, e$6 = t$3.ShadowRoot && (void 0 === t$3.ShadyCSS || t$3.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, s$3 = /* @__PURE__ */ Symbol(), o$7 = /* @__PURE__ */ new WeakMap();
let n$5 = class n {
constructor(t2, e2, o2) {
if (this._$cssResult$ = true, o2 !== s$3) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");
this.cssText = t2, this.t = e2;
}
get styleSheet() {
let t2 = this.o;
const s2 = this.t;
if (e$6 && void 0 === t2) {
const e2 = void 0 !== s2 && 1 === s2.length;
e2 && (t2 = o$7.get(s2)), void 0 === t2 && ((this.o = t2 = new CSSStyleSheet()).replaceSync(this.cssText), e2 && o$7.set(s2, t2));
}
return t2;
}
toString() {
return this.cssText;
}
};
const r$6 = (t2) => new n$5("string" == typeof t2 ? t2 : t2 + "", void 0, s$3), S$1 = (s2, o2) => {
if (e$6) s2.adoptedStyleSheets = o2.map((t2) => t2 instanceof CSSStyleSheet ? t2 : t2.styleSheet);
else for (const e2 of o2) {
const o3 = document.createElement("style"), n3 = t$3.litNonce;
void 0 !== n3 && o3.setAttribute("nonce", n3), o3.textContent = e2.cssText, s2.appendChild(o3);
}
}, c$3 = e$6 ? (t2) => t2 : (t2) => t2 instanceof CSSStyleSheet ? ((t3) => {
let e2 = "";
for (const s2 of t3.cssRules) e2 += s2.cssText;
return r$6(e2);
})(t2) : t2;
const { is: i$3, defineProperty: e$5, getOwnPropertyDescriptor: h$2, getOwnPropertyNames: r$5, getOwnPropertySymbols: o$6, getPrototypeOf: n$4 } = Object, a$1 = globalThis, c$2 = a$1.trustedTypes, l$1 = c$2 ? c$2.emptyScript : "", p$1 = a$1.reactiveElementPolyfillSupport, d$1 = (t2, s2) => t2, u$1 = { toAttribute(t2, s2) {
switch (s2) {
case Boolean:
t2 = t2 ? l$1 : null;
break;
case Object:
case Array:
t2 = null == t2 ? t2 : JSON.stringify(t2);
}
return t2;
}, fromAttribute(t2, s2) {
let i3 = t2;
switch (s2) {
case Boolean:
i3 = null !== t2;
break;
case Number:
i3 = null === t2 ? null : Number(t2);
break;
case Object:
case Array:
try {
i3 = JSON.parse(t2);
} catch (t3) {
i3 = null;
}
}
return i3;
} }, f$2 = (t2, s2) => !i$3(t2, s2), b$1 = { attribute: true, type: String, converter: u$1, reflect: false, useDefault: false, hasChanged: f$2 };
Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), a$1.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap();
let y$1 = class y extends HTMLElement {
static addInitializer(t2) {
this._$Ei(), (this.l ??= []).push(t2);
}
static get observedAttributes() {
return this.finalize(), this._$Eh && [...this._$Eh.keys()];
}
static createProperty(t2, s2 = b$1) {
if (s2.state && (s2.attribute = false), this._$Ei(), this.prototype.hasOwnProperty(t2) && ((s2 = Object.create(s2)).wrapped = true), this.elementProperties.set(t2, s2), !s2.noAccessor) {
const i3 = /* @__PURE__ */ Symbol(), h2 = this.getPropertyDescriptor(t2, i3, s2);
void 0 !== h2 && e$5(this.prototype, t2, h2);
}
}
static getPropertyDescriptor(t2, s2, i3) {
const { get: e2, set: r2 } = h$2(this.prototype, t2) ?? { get() {
return this[s2];
}, set(t3) {
this[s2] = t3;
} };
return { get: e2, set(s3) {
const h2 = e2?.call(this);
r2?.call(this, s3), this.requestUpdate(t2, h2, i3);
}, configurable: true, enumerable: true };
}
static getPropertyOptions(t2) {
return this.elementProperties.get(t2) ?? b$1;
}
static _$Ei() {
if (this.hasOwnProperty(d$1("elementProperties"))) return;
const t2 = n$4(this);
t2.finalize(), void 0 !== t2.l && (this.l = [...t2.l]), this.elementProperties = new Map(t2.elementProperties);
}
static finalize() {
if (this.hasOwnProperty(d$1("finalized"))) return;
if (this.finalized = true, this._$Ei(), this.hasOwnProperty(d$1("properties"))) {
const t3 = this.properties, s2 = [...r$5(t3), ...o$6(t3)];
for (const i3 of s2) this.createProperty(i3, t3[i3]);
}
const t2 = this[Symbol.metadata];
if (null !== t2) {
const s2 = litPropertyMetadata.get(t2);
if (void 0 !== s2) for (const [t3, i3] of s2) this.elementProperties.set(t3, i3);
}
this._$Eh = /* @__PURE__ */ new Map();
for (const [t3, s2] of this.elementProperties) {
const i3 = this._$Eu(t3, s2);
void 0 !== i3 && this._$Eh.set(i3, t3);
}
this.elementStyles = this.finalizeStyles(this.styles);
}
static finalizeStyles(s2) {
const i3 = [];
if (Array.isArray(s2)) {
const e2 = new Set(s2.flat(1 / 0).reverse());
for (const s3 of e2) i3.unshift(c$3(s3));
} else void 0 !== s2 && i3.push(c$3(s2));
return i3;
}
static _$Eu(t2, s2) {
const i3 = s2.attribute;
return false === i3 ? void 0 : "string" == typeof i3 ? i3 : "string" == typeof t2 ? t2.toLowerCase() : void 0;
}
constructor() {
super(), this._$Ep = void 0, this.isUpdatePending = false, this.hasUpdated = false, this._$Em = null, this._$Ev();
}
_$Ev() {
this._$ES = new Promise((t2) => this.enableUpdating = t2), this._$AL = /* @__PURE__ */ new Map(), this._$E_(), this.requestUpdate(), this.constructor.l?.forEach((t2) => t2(this));
}
addController(t2) {
(this._$EO ??= /* @__PURE__ */ new Set()).add(t2), void 0 !== this.renderRoot && this.isConnected && t2.hostConnected?.();
}
removeController(t2) {
this._$EO?.delete(t2);
}
_$E_() {
const t2 = /* @__PURE__ */ new Map(), s2 = this.constructor.elementProperties;
for (const i3 of s2.keys()) this.hasOwnProperty(i3) && (t2.set(i3, this[i3]), delete this[i3]);
t2.size > 0 && (this._$Ep = t2);
}
createRenderRoot() {
const t2 = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions);
return S$1(t2, this.constructor.elementStyles), t2;
}
connectedCallback() {
this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(true), this._$EO?.forEach((t2) => t2.hostConnected?.());
}
enableUpdating(t2) {
}
disconnectedCallback() {
this._$EO?.forEach((t2) => t2.hostDisconnected?.());
}
attributeChangedCallback(t2, s2, i3) {
this._$AK(t2, i3);
}
_$ET(t2, s2) {
const i3 = this.constructor.elementProperties.get(t2), e2 = this.constructor._$Eu(t2, i3);
if (void 0 !== e2 && true === i3.reflect) {
const h2 = (void 0 !== i3.converter?.toAttribute ? i3.converter : u$1).toAttribute(s2, i3.type);
this._$Em = t2, null == h2 ? this.removeAttribute(e2) : this.setAttribute(e2, h2), this._$Em = null;
}
}
_$AK(t2, s2) {
const i3 = this.constructor, e2 = i3._$Eh.get(t2);
if (void 0 !== e2 && this._$Em !== e2) {
const t3 = i3.getPropertyOptions(e2), h2 = "function" == typeof t3.converter ? { fromAttribute: t3.converter } : void 0 !== t3.converter?.fromAttribute ? t3.converter : u$1;
this._$Em = e2;
const r2 = h2.fromAttribute(s2, t3.type);
this[e2] = r2 ?? this._$Ej?.get(e2) ?? r2, this._$Em = null;
}
}
requestUpdate(t2, s2, i3, e2 = false, h2) {
if (void 0 !== t2) {
const r2 = this.constructor;
if (false === e2 && (h2 = this[t2]), i3 ??= r2.getPropertyOptions(t2), !((i3.hasChanged ?? f$2)(h2, s2) || i3.useDefault && i3.reflect && h2 === this._$Ej?.get(t2) && !this.hasAttribute(r2._$Eu(t2, i3)))) return;
this.C(t2, s2, i3);
}
false === this.isUpdatePending && (this._$ES = this._$EP());
}
C(t2, s2, { useDefault: i3, reflect: e2, wrapped: h2 }, r2) {
i3 && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t2) && (this._$Ej.set(t2, r2 ?? s2 ?? this[t2]), true !== h2 || void 0 !== r2) || (this._$AL.has(t2) || (this.hasUpdated || i3 || (s2 = void 0), this._$AL.set(t2, s2)), true === e2 && this._$Em !== t2 && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t2));
}
async _$EP() {
this.isUpdatePending = true;
try {
await this._$ES;
} catch (t3) {
Promise.reject(t3);
}
const t2 = this.scheduleUpdate();
return null != t2 && await t2, !this.isUpdatePending;
}
scheduleUpdate() {
return this.performUpdate();
}
performUpdate() {
if (!this.isUpdatePending) return;
if (!this.hasUpdated) {
if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) {
for (const [t4, s3] of this._$Ep) this[t4] = s3;
this._$Ep = void 0;
}
const t3 = this.constructor.elementProperties;
if (t3.size > 0) for (const [s3, i3] of t3) {
const { wrapped: t4 } = i3, e2 = this[s3];
true !== t4 || this._$AL.has(s3) || void 0 === e2 || this.C(s3, void 0, i3, e2);
}
}
let t2 = false;
const s2 = this._$AL;
try {
t2 = this.shouldUpdate(s2), t2 ? (this.willUpdate(s2), this._$EO?.forEach((t3) => t3.hostUpdate?.()), this.update(s2)) : this._$EM();
} catch (s3) {
throw t2 = false, this._$EM(), s3;
}
t2 && this._$AE(s2);
}
willUpdate(t2) {
}
_$AE(t2) {
this._$EO?.forEach((t3) => t3.hostUpdated?.()), this.hasUpdated || (this.hasUpdated = true, this.firstUpdated(t2)), this.updated(t2);
}
_$EM() {
this._$AL = /* @__PURE__ */ new Map(), this.isUpdatePending = false;
}
get updateComplete() {
return this.getUpdateComplete();
}
getUpdateComplete() {
return this._$ES;
}
shouldUpdate(t2) {
return true;
}
update(t2) {
this._$Eq &&= this._$Eq.forEach((t3) => this._$ET(t3, this[t3])), this._$EM();
}
updated(t2) {
}
firstUpdated(t2) {
}
};
y$1.elementStyles = [], y$1.shadowRootOptions = { mode: "open" }, y$1[d$1("elementProperties")] = /* @__PURE__ */ new Map(), y$1[d$1("finalized")] = /* @__PURE__ */ new Map(), p$1?.({ ReactiveElement: y$1 }), (a$1.reactiveElementVersions ??= []).push("2.1.2");
const t$2 = globalThis, i$2 = (t2) => t2, s$2 = t$2.trustedTypes, e$4 = s$2 ? s$2.createPolicy("lit-html", { createHTML: (t2) => t2 }) : void 0, h$1 = "$lit$", o$5 = `lit$${Math.random().toFixed(9).slice(2)}$`, n$3 = "?" + o$5, r$4 = `<${n$3}>`, l = document, c$1 = () => l.createComment(""), a = (t2) => null === t2 || "object" != typeof t2 && "function" != typeof t2, u = Array.isArray, d = (t2) => u(t2) || "function" == typeof t2?.[Symbol.iterator], f$1 = "[ \n\f\r]", v = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, _ = /-->/g, m = />/g, p = RegExp(`>|${f$1}(?:([^\\s"'>=/]+)(${f$1}*=${f$1}*(?:[^
\f\r"'\`<>=]|("|')|))|$)`, "g"), g = /'/g, $ = /"/g, y2 = /^(?:script|style|textarea|title)$/i, x = (t2) => (i3, ...s2) => ({ _$litType$: t2, strings: i3, values: s2 }), b = x(1), E = /* @__PURE__ */ Symbol.for("lit-noChange"), A = /* @__PURE__ */ Symbol.for("lit-nothing"), C = /* @__PURE__ */ new WeakMap(), P = l.createTreeWalker(l, 129);
function V(t2, i3) {
if (!u(t2) || !t2.hasOwnProperty("raw")) throw Error("invalid template strings array");
return void 0 !== e$4 ? e$4.createHTML(i3) : i3;
}
const N = (t2, i3) => {
const s2 = t2.length - 1, e2 = [];
let n3, l2 = 2 === i3 ? "<svg>" : 3 === i3 ? "<math>" : "", c2 = v;
for (let i4 = 0; i4 < s2; i4++) {
const s3 = t2[i4];
let a2, u2, d2 = -1, f2 = 0;
for (; f2 < s3.length && (c2.lastIndex = f2, u2 = c2.exec(s3), null !== u2); ) f2 = c2.lastIndex, c2 === v ? "!--" === u2[1] ? c2 = _ : void 0 !== u2[1] ? c2 = m : void 0 !== u2[2] ? (y2.test(u2[2]) && (n3 = RegExp("</" + u2[2], "g")), c2 = p) : void 0 !== u2[3] && (c2 = p) : c2 === p ? ">" === u2[0] ? (c2 = n3 ?? v, d2 = -1) : void 0 === u2[1] ? d2 = -2 : (d2 = c2.lastIndex - u2[2].length, a2 = u2[1], c2 = void 0 === u2[3] ? p : '"' === u2[3] ? $ : g) : c2 === $ || c2 === g ? c2 = p : c2 === _ || c2 === m ? c2 = v : (c2 = p, n3 = void 0);
const x2 = c2 === p && t2[i4 + 1].startsWith("/>") ? " " : "";
l2 += c2 === v ? s3 + r$4 : d2 >= 0 ? (e2.push(a2), s3.slice(0, d2) + h$1 + s3.slice(d2) + o$5 + x2) : s3 + o$5 + (-2 === d2 ? i4 : x2);
}
return [V(t2, l2 + (t2[s2] || "<?>") + (2 === i3 ? "</svg>" : 3 === i3 ? "</math>" : "")), e2];
};
class S {
constructor({ strings: t2, _$litType$: i3 }, e2) {
let r2;
this.parts = [];
let l2 = 0, a2 = 0;
const u2 = t2.length - 1, d2 = this.parts, [f2, v2] = N(t2, i3);
if (this.el = S.createElement(f2, e2), P.currentNode = this.el.content, 2 === i3 || 3 === i3) {
const t3 = this.el.content.firstChild;
t3.replaceWith(...t3.childNodes);
}
for (; null !== (r2 = P.nextNode()) && d2.length < u2; ) {
if (1 === r2.nodeType) {
if (r2.hasAttributes()) for (const t3 of r2.getAttributeNames()) if (t3.endsWith(h$1)) {
const i4 = v2[a2++], s2 = r2.getAttribute(t3).split(o$5), e3 = /([.?@])?(.*)/.exec(i4);
d2.push({ type: 1, index: l2, name: e3[2], strings: s2, ctor: "." === e3[1] ? I : "?" === e3[1] ? L : "@" === e3[1] ? z : H }), r2.removeAttribute(t3);
} else t3.startsWith(o$5) && (d2.push({ type: 6, index: l2 }), r2.removeAttribute(t3));
if (y2.test(r2.tagName)) {
const t3 = r2.textContent.split(o$5), i4 = t3.length - 1;
if (i4 > 0) {
r2.textContent = s$2 ? s$2.emptyScript : "";
for (let s2 = 0; s2 < i4; s2++) r2.append(t3[s2], c$1()), P.nextNode(), d2.push({ type: 2, index: ++l2 });
r2.append(t3[i4], c$1());
}
}
} else if (8 === r2.nodeType) if (r2.data === n$3) d2.push({ type: 2, index: l2 });
else {
let t3 = -1;
for (; -1 !== (t3 = r2.data.indexOf(o$5, t3 + 1)); ) d2.push({ type: 7, index: l2 }), t3 += o$5.length - 1;
}
l2++;
}
}
static createElement(t2, i3) {
const s2 = l.createElement("template");
return s2.innerHTML = t2, s2;
}
}
function M(t2, i3, s2 = t2, e2) {
if (i3 === E) return i3;
let h2 = void 0 !== e2 ? s2._$Co?.[e2] : s2._$Cl;
const o2 = a(i3) ? void 0 : i3._$litDirective$;
return h2?.constructor !== o2 && (h2?._$AO?.(false), void 0 === o2 ? h2 = void 0 : (h2 = new o2(t2), h2._$AT(t2, s2, e2)), void 0 !== e2 ? (s2._$Co ??= [])[e2] = h2 : s2._$Cl = h2), void 0 !== h2 && (i3 = M(t2, h2._$AS(t2, i3.values), h2, e2)), i3;
}
class R {
constructor(t2, i3) {
this._$AV = [], this._$AN = void 0, this._$AD = t2, this._$AM = i3;
}
get parentNode() {
return this._$AM.parentNode;
}
get _$AU() {
return this._$AM._$AU;
}
u(t2) {
const { el: { content: i3 }, parts: s2 } = this._$AD, e2 = (t2?.creationScope ?? l).importNode(i3, true);
P.currentNode = e2;
let h2 = P.nextNode(), o2 = 0, n3 = 0, r2 = s2[0];
for (; void 0 !== r2; ) {
if (o2 === r2.index) {
let i4;
2 === r2.type ? i4 = new k(h2, h2.nextSibling, this, t2) : 1 === r2.type ? i4 = new r2.ctor(h2, r2.name, r2.strings, this, t2) : 6 === r2.type && (i4 = new Z(h2, this, t2)), this._$AV.push(i4), r2 = s2[++n3];
}
o2 !== r2?.index && (h2 = P.nextNode(), o2++);
}
return P.currentNode = l, e2;
}
p(t2) {
let i3 = 0;
for (const s2 of this._$AV) void 0 !== s2 && (void 0 !== s2.strings ? (s2._$AI(t2, s2, i3), i3 += s2.strings.length - 2) : s2._$AI(t2[i3])), i3++;
}
}
class k {
get _$AU() {
return this._$AM?._$AU ?? this._$Cv;
}
constructor(t2, i3, s2, e2) {
this.type = 2, this._$AH = A, this._$AN = void 0, this._$AA = t2, this._$AB = i3, this._$AM = s2, this.options = e2, this._$Cv = e2?.isConnected ?? true;
}
get parentNode() {
let t2 = this._$AA.parentNode;
const i3 = this._$AM;
return void 0 !== i3 && 11 === t2?.nodeType && (t2 = i3.parentNode), t2;
}
get startNode() {
return this._$AA;
}
get endNode() {
return this._$AB;
}
_$AI(t2, i3 = this) {
t2 = M(this, t2, i3), a(t2) ? t2 === A || null == t2 || "" === t2 ? (this._$AH !== A && this._$AR(), this._$AH = A) : t2 !== this._$AH && t2 !== E && this._(t2) : void 0 !== t2._$litType$ ? this.$(t2) : void 0 !== t2.nodeType ? this.T(t2) : d(t2) ? this.k(t2) : this._(t2);
}
O(t2) {
return this._$AA.parentNode.insertBefore(t2, this._$AB);
}
T(t2) {
this._$AH !== t2 && (this._$AR(), this._$AH = this.O(t2));
}
_(t2) {
this._$AH !== A && a(this._$AH) ? this._$AA.nextSibling.data = t2 : this.T(l.createTextNode(t2)), this._$AH = t2;
}
$(t2) {
const { values: i3, _$litType$: s2 } = t2, e2 = "number" == typeof s2 ? this._$AC(t2) : (void 0 === s2.el && (s2.el = S.createElement(V(s2.h, s2.h[0]), this.options)), s2);
if (this._$AH?._$AD === e2) this._$AH.p(i3);
else {
const t3 = new R(e2, this), s3 = t3.u(this.options);
t3.p(i3), this.T(s3), this._$AH = t3;
}
}
_$AC(t2) {
let i3 = C.get(t2.strings);
return void 0 === i3 && C.set(t2.strings, i3 = new S(t2)), i3;
}
k(t2) {
u(this._$AH) || (this._$AH = [], this._$AR());
const i3 = this._$AH;
let s2, e2 = 0;
for (const h2 of t2) e2 === i3.length ? i3.push(s2 = new k(this.O(c$1()), this.O(c$1()), this, this.options)) : s2 = i3[e2], s2._$AI(h2), e2++;
e2 < i3.length && (this._$AR(s2 && s2._$AB.nextSibling, e2), i3.length = e2);
}
_$AR(t2 = this._$AA.nextSibling, s2) {
for (this._$AP?.(false, true, s2); t2 !== this._$AB; ) {
const s3 = i$2(t2).nextSibling;
i$2(t2).remove(), t2 = s3;
}
}
setConnected(t2) {
void 0 === this._$AM && (this._$Cv = t2, this._$AP?.(t2));
}
}
class H {
get tagName() {
return this.element.tagName;
}
get _$AU() {
return this._$AM._$AU;
}
constructor(t2, i3, s2, e2, h2) {
this.type = 1, this._$AH = A, this._$AN = void 0, this.element = t2, this.name = i3, this._$AM = e2, this.options = h2, s2.length > 2 || "" !== s2[0] || "" !== s2[1] ? (this._$AH = Array(s2.length - 1).fill(new String()), this.strings = s2) : this._$AH = A;
}
_$AI(t2, i3 = this, s2, e2) {
const h2 = this.strings;
let o2 = false;
if (void 0 === h2) t2 = M(this, t2, i3, 0), o2 = !a(t2) || t2 !== this._$AH && t2 !== E, o2 && (this._$AH = t2);
else {
const e3 = t2;
let n3, r2;
for (t2 = h2[0], n3 = 0; n3 < h2.length - 1; n3++) r2 = M(this, e3[s2 + n3], i3, n3), r2 === E && (r2 = this._$AH[n3]), o2 ||= !a(r2) || r2 !== this._$AH[n3], r2 === A ? t2 = A : t2 !== A && (t2 += (r2 ?? "") + h2[n3 + 1]), this._$AH[n3] = r2;
}
o2 && !e2 && this.j(t2);
}
j(t2) {
t2 === A ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t2 ?? "");
}
}
class I extends H {
constructor() {
super(...arguments), this.type = 3;
}
j(t2) {
this.element[this.name] = t2 === A ? void 0 : t2;
}
}
class L extends H {
constructor() {
super(...arguments), this.type = 4;
}
j(t2) {
this.element.toggleAttribute(this.name, !!t2 && t2 !== A);
}
}
class z extends H {
constructor(t2, i3, s2, e2, h2) {
super(t2, i3, s2, e2, h2), this.type = 5;
}
_$AI(t2, i3 = this) {
if ((t2 = M(this, t2, i3, 0) ?? A) === E) return;
const s2 = this._$AH, e2 = t2 === A && s2 !== A || t2.capture !== s2.capture || t2.once !== s2.once || t2.passive !== s2.passive, h2 = t2 !== A && (s2 === A || e2);
e2 && this.element.removeEventListener(this.name, this, s2), h2 && this.element.addEventListener(this.name, this, t2), this._$AH = t2;
}
handleEvent(t2) {
"function" == typeof this._$AH ? this._$AH.call(this.options?.host ?? this.element, t2) : this._$AH.handleEvent(t2);
}
}
class Z {
constructor(t2, i3, s2) {
this.element = t2, this.type = 6, this._$AN = void 0, this._$AM = i3, this.options = s2;
}
get _$AU() {
return this._$AM._$AU;
}
_$AI(t2) {
M(this, t2);
}
}
const B = t$2.litHtmlPolyfillSupport;
B?.(S, k), (t$2.litHtmlVersions ??= []).push("3.3.3");
const D = (t2, i3, s2) => {
const e2 = s2?.renderBefore ?? i3;
let h2 = e2._$litPart$;
if (void 0 === h2) {
const t3 = s2?.renderBefore ?? null;
e2._$litPart$ = h2 = new k(i3.insertBefore(c$1(), t3), t3, void 0, s2 ?? {});
}
return h2._$AI(t2), h2;
};
const s$1 = globalThis;
let i$1 = class i extends y$1 {
constructor() {
super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0;
}
createRenderRoot() {
const t2 = super.createRenderRoot();
return this.renderOptions.renderBefore ??= t2.firstChild, t2;
}
update(t2) {
const r2 = this.render();
this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t2), this._$Do = D(r2, this.renderRoot, this.renderOptions);
}
connectedCallback() {
super.connectedCallback(), this._$Do?.setConnected(true);
}
disconnectedCallback() {
super.disconnectedCallback(), this._$Do?.setConnected(false);
}
render() {
return E;
}
};
i$1._$litElement$ = true, i$1["finalized"] = true, s$1.litElementHydrateSupport?.({ LitElement: i$1 });
const o$4 = s$1.litElementPolyfillSupport;
o$4?.({ LitElement: i$1 });
(s$1.litElementVersions ??= []).push("4.2.2");
const t$1 = (t2) => (e2, o2) => {
void 0 !== o2 ? o2.addInitializer(() => {
customElements.define(t2, e2);
}) : customElements.define(t2, e2);
};
const o$3 = { attribute: true, type: String, converter: u$1, reflect: false, hasChanged: f$2 }, r$3 = (t2 = o$3, e2, r2) => {
const { kind: n3, metadata: i3 } = r2;
let s2 = globalThis.litPropertyMetadata.get(i3);
if (void 0 === s2 && globalThis.litPropertyMetadata.set(i3, s2 = /* @__PURE__ */ new Map()), "setter" === n3 && ((t2 = Object.create(t2)).wrapped = true), s2.set(r2.name, t2), "accessor" === n3) {
const { name: o2 } = r2;
return { set(r3) {
const n4 = e2.get.call(this);
e2.set.call(this, r3), this.requestUpdate(o2, n4, t2, true, r3);
}, init(e3) {
return void 0 !== e3 && this.C(o2, void 0, t2, e3), e3;
} };
}
if ("setter" === n3) {
const { name: o2 } = r2;
return function(r3) {
const n4 = this[o2];
e2.call(this, r3), this.requestUpdate(o2, n4, t2, true, r3);
};
}
throw Error("Unsupported decorator location: " + n3);
};
function n$2(t2) {
return (e2, o2) => "object" == typeof o2 ? r$3(t2, e2, o2) : ((t3, e3, o3) => {
const r2 = e3.hasOwnProperty(o3);
return e3.constructor.createProperty(o3, t3), r2 ? Object.getOwnPropertyDescriptor(e3, o3) : void 0;
})(t2, e2, o2);
}
function r$2(r2) {
return n$2({ ...r2, state: true, attribute: false });
}
const e$3 = (e2, t2, c2) => (c2.configurable = true, c2.enumerable = true, Reflect.decorate && "object" != typeof t2 && Object.defineProperty(e2, t2, c2), c2);
function e$2(e2, r2) {
return (n3, s2, i3) => {
const o2 = (t2) => t2.renderRoot?.querySelector(e2) ?? null;
return e$3(n3, s2, { get() {
return o2(this);
} });
};
}
const t = { CHILD: 2 }, e$1 = (t2) => (...e2) => ({ _$litDirective$: t2, values: e2 });
class i2 {
constructor(t2) {
}
get _$AU() {
return this._$AM._$AU;
}
_$AT(t2, e2, i3) {
this._$Ct = t2, this._$AM = e2, this._$Ci = i3;
}
_$AS(t2, e2) {
return this.update(t2, e2);
}
update(t2, e2) {
return this.render(...e2);
}
}
class e extends i2 {
constructor(i3) {
if (super(i3), this.it = A, i3.type !== t.CHILD) throw Error(this.constructor.directiveName + "() can only be used in child bindings");
}
render(r2) {
if (r2 === A || null == r2) return this._t = void 0, this.it = r2;
if (r2 === E) return r2;
if ("string" != typeof r2) throw Error(this.constructor.directiveName + "() called with a non-string value");
if (r2 === this.it) return this._t;
this.it = r2;
const s2 = [r2];
return s2.raw = s2, this._t = { _$litType$: this.constructor.resultType, strings: s2, values: [] };
}
}
e.directiveName = "unsafeHTML", e.resultType = 1;
const o$2 = e$1(e);
const HOVER_CSS = ":host{position:fixed;z-index:2147483640;display:flex;gap:3px;align-items:center;background:#16161ae6;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:4px 6px;box-shadow:0 4px 16px #0000004d;opacity:0;pointer-events:none;transition:opacity .15s ease,visibility .15s ease;font-family:system-ui,sans-serif;visibility:hidden;user-select:none;-webkit-user-select:none}:host(.visible){opacity:1;visibility:visible;pointer-events:none}.svf-hb-btn{background:transparent;border:none;cursor:pointer;width:24px;height:24px;border-radius:5px;display:grid;place-items:center;color:#fff9;transition:background .15s,color .15s,transform .1s;padding:0;pointer-events:auto}.svf-hb-btn:hover{background:#ffffff1a;color:#fff;transform:scale(1.1)}.svf-hb-btn.like-active{color:var(--svf-primary, #10b981)}.svf-hb-btn.dislike-active{color:#ef4444}.svf-hb-sep{width:1px;height:16px;background:#ffffff1a;margin:0 2px}.svf-hb-label{font-size:10px;color:#fff6;max-width:120px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;padding:0 4px}";
function svgIcon(path, size = 14, viewBox = "0 0 24 24", strokeWidth = "2.5") {
return `<svg width="${size}" height="${size}" viewBox="${viewBox}" fill="none" stroke="currentColor" stroke-width="${strokeWidth}" stroke-linecap="round" stroke-linejoin="round">${path}</svg>`;
}
const ICON_CLOSE = svgIcon('<line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line>', 16, "0 0 24 24", "2.5");
const ICON_SEARCH = svgIcon('<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line>', 14, "0 0 24 24", "2.5");
const ICON_CLEAR = svgIcon('<line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line>', 12, "0 0 24 24", "2.5");
const ICON_COPY = svgIcon('<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>', 14, "0 0 24 24", "2");
const ICON_DOWNLOAD = svgIcon('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>', 14, "0 0 24 24", "2");
const ICON_IMPORT = svgIcon('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>', 14, "0 0 24 24", "2");
const ICON_EDIT = svgIcon('<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 1 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>');
const ICON_DELETE = svgIcon('<polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>');
const ICON_TRIGGER = svgIcon('<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7z"></path><circle cx="12" cy="12" r="3"></circle>', 20, "0 0 24 24", "2.2");
const STAR_FILLED = svgIcon('<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" fill="currentColor" stroke="none"/>', 14, "0 0 24 24", "2.2");
const STAR_EMPTY = svgIcon('<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>', 14, "0 0 24 24", "2.2");
const BAN_ICON = svgIcon('<circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/>', 14, "0 0 24 24", "2.2");
var __defProp$2 = Object.defineProperty;
var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
var __decorateClass$2 = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
for (var i3 = decorators.length - 1, decorator; i3 >= 0; i3--)
if (decorator = decorators[i3])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp$2(target, key, result);
return result;
};
const ATTR = SVF_CONFIG.ITEM_ATTR;
const DOMAIN_ATTR = `${ATTR}-domain`;
let HoverOverlayElement = class extends i$1 {
constructor() {
super(...arguments);
this._activeDomain = "";
this._matchState = "normal";
this._activeItem = null;
this._hideTimer = null;
this._HIDE_DELAY = 350;
this._rafId = null;
this._lastEvent = null;
this._longPressTimer = null;
this._LONGPRESS_MS = 500;
}
connectedCallback() {
super.connectedCallback();
this._unsubscribe?.();
if (this.store) {
this._unsubscribe = this.store.subscribe(() => {
this._updateMatchState();
});
}
this._connectedAbortController?.abort();
this._connectedAbortController = new AbortController();
const signal = this._connectedAbortController.signal;
const docOpts = { signal, passive: true, capture: true };
document.addEventListener("pointermove", (e2) => {
this._lastEvent = e2;
if (this._rafId === null) {
this._rafId = requestAnimationFrame(() => {
if (this._lastEvent) {
this._onPointerMove(this._lastEvent);
}
this._rafId = null;
});
}
}, docOpts);
document.addEventListener("pointerleave", () => {
this._scheduleHide();
}, docOpts);
this.addEventListener("pointerenter", () => this._cancelHide(), { signal });
this.addEventListener("pointerleave", () => this._scheduleHide(), { signal });
const cancelLongPress = () => {
if (this._longPressTimer) {
clearTimeout(this._longPressTimer);
this._longPressTimer = null;
}
};
document.addEventListener("pointerdown", (e2) => {
if (e2.pointerType !== "touch") return;
cancelLongPress();
if (!e2.composedPath().includes(this)) this.classList.remove("visible");
const item = e2.target?.closest(`[${ATTR}]`);
if (!item) return;
this._longPressTimer = setTimeout(() => {
this._activeItem = item;
this._activeDomain = item.getAttribute(DOMAIN_ATTR) ?? "";
this._updateMatchState();
this._positionBar(item);
}, this._LONGPRESS_MS);
}, docOpts);
document.addEventListener("pointerup", cancelLongPress, docOpts);
document.addEventListener("pointercancel", cancelLongPress, docOpts);
document.addEventListener("pointermove", (e2) => {
if (e2.pointerType === "touch") cancelLongPress();
}, docOpts);
document.addEventListener("contextmenu", (e2) => {
if (this.classList.contains("visible") && this._activeItem) {
e2.preventDefault();
}
}, { signal });
}
disconnectedCallback() {
super.disconnectedCallback();
this._unsubscribe?.();
if (this._connectedAbortController) {
this._connectedAbortController.abort();
this._connectedAbortController = void 0;
}
if (this._rafId !== null) {
cancelAnimationFrame(this._rafId);
this._rafId = null;
}
if (this._hideTimer) {
clearTimeout(this._hideTimer);
this._hideTimer = null;
}
if (this._longPressTimer) {
clearTimeout(this._longPressTimer);
this._longPressTimer = null;
}
}
updated(changedProperties) {
super.updated(changedProperties);
if (this.store) {
applyAccentVariables(this, this.store.accent);
}
}
_onPointerMove(e2) {
if (e2.pointerType === "touch") return;
const shadowHost = document.getElementById("svf-shadow-host");
const panel = (shadowHost?.shadowRoot || shadowHost)?.querySelector("svf-panel");
if (panel && typeof panel.isOpen === "function" && panel.isOpen()) {
this._scheduleHide();
return;
}
let target = document.elementFromPoint(e2.clientX, e2.clientY);
if (!target) {
this._scheduleHide();
return;
}
while (target && target.shadowRoot) {
const inner = target.shadowRoot.elementFromPoint(e2.clientX, e2.clientY);
if (!inner || inner === target) break;
target = inner;
}
const item = target.closest(`[${ATTR}]`);
if (!item) {
if (target === shadowHost || this.contains(target)) {
this._cancelHide();
return;
}
this._scheduleHide();
return;
}
this._cancelHide();
if (item === this._activeItem) return;
this._activeItem = item;
this._activeDomain = item.getAttribute(DOMAIN_ATTR) ?? "";
this._updateMatchState();
this._positionBar(item);
}
_positionBar(item) {
const rect = item.getBoundingClientRect();
const barW = this.offsetWidth || 160;
const barH = this.offsetHeight || 32;
let top = rect.top + 4;
if (rect.top < 0) {
top = Math.max(4, rect.bottom - barH - 4);
}
let left = rect.right - barW - 4;
if (left < 8) left = rect.left + 8;
if (left + barW > window.innerWidth - 8) left = window.innerWidth - barW - 8;
this.style.top = `${top}px`;
this.style.left = `${left}px`;
this.classList.add("visible");
}
_updateMatchState() {
if (!this._activeDomain || !this.store) return;
this._matchState = this.store.matchDomain(this._activeDomain);
}
_scheduleHide() {
if (this._hideTimer) return;
this._hideTimer = setTimeout(() => {
this.classList.remove("visible");
this._activeItem = null;
this._activeDomain = "";
this._hideTimer = null;
}, this._HIDE_DELAY);
}
_cancelHide() {
if (this._hideTimer) {
clearTimeout(this._hideTimer);
this._hideTimer = null;
}
}
_toggleLike(e2) {
e2.stopPropagation();
if (!this._activeDomain) return;
if (this._matchState === "liked") {
this.store.removeDomain("liked", this._activeDomain);
} else {
this.store.addDomain("liked", this._activeDomain);
}
this.scanner.reapply();
}
_toggleDislike(e2) {
e2.stopPropagation();
if (!this._activeDomain) return;
if (this._matchState === "disliked") {
this.store.removeDomain("disliked", this._activeDomain);
} else {
this.store.addDomain("disliked", this._activeDomain);
}
this.scanner.reapply();
}
render() {
return b`
<button
class="svf-hb-btn ${this._matchState === "liked" ? "like-active" : ""}"
title=${this._matchState === "liked" ? "Remove from Liked" : "Add to Liked"}
@click=${this._toggleLike}
>
${o$2(this._matchState === "liked" ? STAR_FILLED : STAR_EMPTY)}
</button>
<button
class="svf-hb-btn ${this._matchState === "disliked" ? "dislike-active" : ""}"
title=${this._matchState === "disliked" ? "Remove from Disliked" : "Add to Disliked"}
@click=${this._toggleDislike}
>
${o$2(BAN_ICON)}
</button>
<div class="svf-hb-sep"></div>
<span class="svf-hb-label" title=${this._activeDomain}>${this._activeDomain}</span>
`;
}
};
HoverOverlayElement.styles = r$6(HOVER_CSS);
__decorateClass$2([
n$2({ attribute: false })
], HoverOverlayElement.prototype, "store", 2);
__decorateClass$2([
n$2({ attribute: false })
], HoverOverlayElement.prototype, "scanner", 2);
__decorateClass$2([
n$2({ attribute: false })
], HoverOverlayElement.prototype, "signal", 2);
__decorateClass$2([
r$2()
], HoverOverlayElement.prototype, "_activeDomain", 2);
__decorateClass$2([
r$2()
], HoverOverlayElement.prototype, "_matchState", 2);
HoverOverlayElement = __decorateClass$2([
t$1("svf-hover-overlay")
], HoverOverlayElement);
const r$1 = (o2) => void 0 === o2.strings;
const s = (i3, t2) => {
const e2 = i3._$AN;
if (void 0 === e2) return false;
for (const i4 of e2) i4._$AO?.(t2, false), s(i4, t2);
return true;
}, o$1 = (i3) => {
let t2, e2;
do {
if (void 0 === (t2 = i3._$AM)) break;
e2 = t2._$AN, e2.delete(i3), i3 = t2;
} while (0 === e2?.size);
}, r = (i3) => {
for (let t2; t2 = i3._$AM; i3 = t2) {
let e2 = t2._$AN;
if (void 0 === e2) t2._$AN = e2 = /* @__PURE__ */ new Set();
else if (e2.has(i3)) break;
e2.add(i3), c(t2);
}
};
function h(i3) {
void 0 !== this._$AN ? (o$1(this), this._$AM = i3, r(this)) : this._$AM = i3;
}
function n$1(i3, t2 = false, e2 = 0) {
const r2 = this._$AH, h2 = this._$AN;
if (void 0 !== h2 && 0 !== h2.size) if (t2) if (Array.isArray(r2)) for (let i4 = e2; i4 < r2.length; i4++) s(r2[i4], false), o$1(r2[i4]);
else null != r2 && (s(r2, false), o$1(r2));
else s(this, i3);
}
const c = (i3) => {
i3.type == t.CHILD && (i3._$AP ??= n$1, i3._$AQ ??= h);
};
class f extends i2 {
constructor() {
super(...arguments), this._$AN = void 0;
}
_$AT(i3, t2, e2) {
super._$AT(i3, t2, e2), r(this), this.isConnected = i3._$AU;
}
_$AO(i3, t2 = true) {
i3 !== this.isConnected && (this.isConnected = i3, i3 ? this.reconnected?.() : this.disconnected?.()), t2 && (s(this, i3), o$1(this));
}
setValue(t2) {
if (r$1(this._$Ct)) this._$Ct._$AI(t2, this);
else {
const i3 = [...this._$Ct._$AH];
i3[this._$Ci] = t2, this._$Ct._$AI(i3, this, 0);
}
}
disconnected() {
}
reconnected() {
}
}
const o = /* @__PURE__ */ new WeakMap(), n2 = e$1(class extends f {
render(i3) {
return A;
}
update(i3, [s2]) {
const e2 = s2 !== this.G;
return e2 && this.rt(void 0), (e2 || this.lt !== this.ct) && (this.G = s2, this.ht = i3.options?.host, this.rt(this.ct = i3.element)), A;
}
rt(t2) {
if (void 0 !== this.G) if (this.isConnected || (t2 = void 0), "function" == typeof this.G) {
const i3 = this.ht ?? globalThis;
let s2 = o.get(i3);
void 0 === s2 && (s2 = /* @__PURE__ */ new WeakMap(), o.set(i3, s2)), void 0 !== s2.get(this.G) && this.G.call(this.ht, void 0), s2.set(this.G, t2), void 0 !== t2 && this.G.call(this.ht, t2);
} else this.G.value = t2;
}
get lt() {
return "function" == typeof this.G ? o.get(this.ht ?? globalThis)?.get(this.G) : this.G?.value;
}
disconnected() {
this.lt === this.ct && this.rt(void 0);
}
reconnected() {
this.rt(this.ct);
}
});
function parseJsonRobust(text) {
if (text.charCodeAt(0) === 65279) {
text = text.slice(1);
}
let inString = false;
let escape = false;
let out = "";
let lastCommaIdx = -1;
for (let i3 = 0; i3 < text.length; i3++) {
const char = text[i3];
if (inString) {
out += char;
if (escape) {
escape = false;
} else if (char === "\\") {
escape = true;
} else if (char === '"') {
inString = false;
}
} else {
if (char === "/" && text[i3 + 1] === "/") {
while (i3 < text.length && text[i3] !== "\n" && text[i3] !== "\r") {
i3++;
}
out += "\n";
} else if (char === "/" && text[i3 + 1] === "*") {
i3 += 2;
while (i3 < text.length && !(text[i3] === "*" && text[i3 + 1] === "/")) {
i3++;
}
i3++;
} else {
if (char === '"') {
inString = true;
}
if (char === ",") {
lastCommaIdx = out.length;
out += char;
} else if ((char === "}" || char === "]") && lastCommaIdx !== -1) {
let onlyWhitespace = true;
for (let j = lastCommaIdx + 1; j < out.length; j++) {
if (!/\s/.test(out[j])) {
onlyWhitespace = false;
break;
}
}
if (onlyWhitespace) {
out = out.slice(0, lastCommaIdx) + out.slice(lastCommaIdx + 1);
}
lastCommaIdx = -1;
out += char;
} else {
if (!/\s/.test(char)) {
lastCommaIdx = -1;
}
out += char;
}
}
}
}
return JSON.parse(out);
}
function getRegisterableDomain(domain) {
let clean = domain.toLowerCase().trim();
if (clean.startsWith("*.")) {
clean = clean.slice(2);
}
if (/^\d+\.\d+\.\d+\.\d+$/.test(clean)) {
return clean;
}
const parts = clean.split(".");
if (parts.length <= 2) return clean;
const tld = parts[parts.length - 1];
const sld = parts[parts.length - 2];
const isCcTld = tld.length === 2;
const isCommonSld = /^(co|com|org|net|gov|edu|ac|or|ne|ltd|plc|sch|asn)$/.test(sld);
if (isCcTld && isCommonSld && parts.length >= 3) {
return parts.slice(-3).join(".");
}
return parts.slice(-2).join(".");
}
const PANEL_CSS = ':host{--svf-bg: rgba(18, 18, 22, .85);--svf-border: rgba(255, 255, 255, .08);--svf-border-hover: rgba(255, 255, 255, .16);--svf-primary: #10b981;--svf-primary-glow: rgba(16, 185, 129, .15);--svf-primary-bg-hover: rgba(16, 185, 129, .08);--svf-primary-border: rgba(16, 185, 129, .25);--svf-primary-status-bg: rgba(16, 185, 129, .1);--svf-danger: #ef4444;--svf-text: #f3f4f6;--svf-text-muted: #9ca3af;--svf-font: "Outfit", "Inter", system-ui, -apple-system, sans-serif;--svf-glow: 0 24px 50px rgba(0, 0, 0, .6);user-select:none;-webkit-user-select:none}*,*:before,*:after{box-sizing:border-box}.svf-color-picker-row{display:flex;gap:10px;margin-bottom:6px;padding:4px 0}.svf-color-dot{width:24px;height:24px;border-radius:50%;cursor:pointer;border:2px solid transparent;transition:all .2s cubic-bezier(.4,0,.2,1)}.svf-color-dot:hover{transform:scale(1.15)}.svf-color-dot.active{border-color:#fff;box-shadow:0 0 10px var(--dot-glow)}.svf-backdrop{position:fixed;inset:0;background:#0009;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);opacity:0;visibility:hidden;pointer-events:none;transition:opacity .3s ease,visibility .3s ease;will-change:opacity;z-index:2147483645}.svf-backdrop.open{opacity:1;visibility:visible;pointer-events:auto}.svf-card{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(.9);width:90%;max-width:480px;max-height:85vh;background:var(--svf-bg);backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);border:1px solid var(--svf-border);border-radius:20px;box-shadow:var(--svf-glow);color:var(--svf-text);display:flex;flex-direction:column;overflow:hidden;opacity:0;visibility:hidden;pointer-events:none;transition:transform .35s cubic-bezier(.34,1.56,.64,1),opacity .3s ease,visibility .3s ease;will-change:transform,opacity;z-index:2147483646;font-family:var(--svf-font)}.svf-card.open{transform:translate(-50%,-50%) scale(1);opacity:1;visibility:visible;pointer-events:auto}.svf-header{display:flex;justify-content:space-between;align-items:center;padding:20px 24px;border-bottom:1px solid var(--svf-border)}.svf-title-group{display:flex;flex-direction:column}.svf-title{font-size:20px;font-weight:800;letter-spacing:-.4px;background:linear-gradient(135deg,#ffffff 15%,var(--svf-primary) 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.svf-close-btn{background:transparent;border:none;color:#ef4444b3;cursor:pointer;width:32px;height:32px;border-radius:50%;display:grid;place-items:center;transition:background .2s ease,color .2s ease,transform .2s ease;padding:0}.svf-close-btn:hover{background:#ef44441f;color:var(--svf-danger);transform:rotate(90deg)}.svf-body{padding:24px;overflow-y:auto;overflow-x:hidden;display:flex;flex-direction:column;gap:24px;max-height:calc(85vh - 140px)}.svf-control-group{display:flex;flex-direction:column;gap:8px}.svf-label{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--svf-text-muted)}.svf-segmented{display:flex;background:#0000004d;border:1px solid var(--svf-border);padding:4px;border-radius:12px}.svf-seg-btn{flex:1;background:transparent;border:none;color:var(--svf-text-muted);padding:9px 16px;font-size:13px;font-weight:600;border-radius:9px;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);font-family:var(--svf-font)}.svf-seg-btn.active{background:var(--svf-primary);color:#0c0d10;box-shadow:0 4px 12px var(--svf-primary-glow);font-weight:700}.svf-section{display:flex;flex-direction:column;gap:12px}.svf-section-header{display:flex;justify-content:space-between;align-items:center}.svf-badge{font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;background:#ffffff14}.svf-section.liked .svf-badge{background:var(--svf-primary-glow);color:var(--svf-primary)}.svf-section.disliked .svf-badge{background:#ef444426;color:var(--svf-danger)}.svf-textarea{width:100%;height:120px;background:#00000059;border:1px solid var(--svf-border);border-radius:12px;padding:10px 12px;color:var(--svf-text);font-family:var(--svf-mono, monospace);font-size:12.5px;line-height:1.5;resize:none;outline:none;transition:border-color .2s ease,box-shadow .2s ease;overflow-x:hidden;white-space:pre-wrap;word-break:break-all}.svf-textarea:focus{border-color:var(--svf-primary);box-shadow:0 0 0 2px var(--svf-primary-glow)}.svf-textarea-status-row{display:flex;justify-content:space-between;align-items:center;margin-top:6px}.svf-textarea-msg-info{font-size:11px;color:var(--svf-text-muted)}.svf-apply-btn{background:var(--svf-primary-bg-hover);border:1px solid var(--svf-primary-border);color:var(--svf-primary);padding:6px 14px;border-radius:8px;font-family:var(--svf-font);font-size:12px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1)}.svf-apply-btn:hover{background:var(--svf-primary);border-color:var(--svf-primary);color:#0c0d10;box-shadow:0 0 12px var(--svf-primary-glow)}.svf-merger-card{background:#f59e0b14;border:1px solid rgba(245,158,11,.25);border-radius:12px;padding:10px 14px;display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;box-shadow:0 4px 12px #f59e0b0d;animation:svf-fade-in .25s cubic-bezier(.4,0,.2,1)}@keyframes svf-fade-in{0%{opacity:0;transform:translateY(-3px)}to{opacity:1;transform:translateY(0)}}.svf-merger-info{display:flex;flex-direction:column;gap:2px}.svf-merger-title{font-size:12px;font-weight:700;color:#f59e0b}.svf-merger-desc{font-size:11px;color:var(--svf-text-muted)}.svf-merger-btn{background:#f59e0b;border:none;color:#0f1015;padding:6px 12px;border-radius:8px;font-family:var(--svf-font);font-size:11.5px;font-weight:700;cursor:pointer;transition:all .2s;box-shadow:0 2px 6px #f59e0b4d;flex-shrink:0}.svf-merger-btn:hover{opacity:.95;transform:translateY(-1px);box-shadow:0 4px 10px #f59e0b80}.svf-io-section{display:flex;flex-direction:column;gap:12px;border-top:1px solid var(--svf-border);padding-top:20px}.svf-io-row{display:flex;gap:8px}.svf-io-btn{flex:1;border:1px solid var(--svf-border);background:#ffffff08;color:var(--svf-text);border-radius:12px;padding:10px 14px;font-size:13px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);font-family:var(--svf-font);display:flex;align-items:center;justify-content:center;gap:8px}.svf-io-btn:hover{background:#ffffff14;border-color:var(--svf-border-hover);box-shadow:0 4px 12px #00000026}.svf-io-btn:active{transform:scale(.97)}.svf-io-btn.primary{background:var(--svf-primary-bg-hover);border-color:var(--svf-primary-border);color:var(--svf-primary)}.svf-io-btn.primary:hover{background:var(--svf-primary);border-color:var(--svf-primary);color:#0c0d10;box-shadow:0 4px 12px var(--svf-primary-glow)}.svf-io-url-form{display:flex;gap:8px}.svf-io-url-input{flex:1;background:#00000040;border:1px solid var(--svf-border);color:var(--svf-text);border-radius:12px;padding:10px 14px;font-size:12.5px;outline:none;transition:all .2s;font-family:var(--svf-font)}.svf-io-url-input:focus{border-color:#fff3;box-shadow:0 0 0 2px #ffffff0d}.svf-io-status{font-size:11.5px;padding:6px 10px;border-radius:8px;transition:opacity .3s}.svf-io-status.success{background:var(--svf-primary-status-bg);color:var(--svf-primary)}.svf-io-status.error{background:#ef44441a;color:var(--svf-danger)}.svf-io-file{display:none}.svf-body::-webkit-scrollbar,.svf-textarea::-webkit-scrollbar,.svf-filtered-list::-webkit-scrollbar{width:6px;height:6px}.svf-body::-webkit-scrollbar-track,.svf-textarea::-webkit-scrollbar-track,.svf-filtered-list::-webkit-scrollbar-track{background:transparent}.svf-body::-webkit-scrollbar-thumb,.svf-textarea::-webkit-scrollbar-thumb,.svf-filtered-list::-webkit-scrollbar-thumb{background:#ffffff26;border-radius:3px}.svf-body::-webkit-scrollbar-thumb:hover,.svf-textarea::-webkit-scrollbar-thumb:hover,.svf-filtered-list::-webkit-scrollbar-thumb:hover{background:#ffffff4d}.svf-body,.svf-textarea,.svf-filtered-list{scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.15) transparent}.svf-switch-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0}.svf-switch-label-group{display:flex;flex-direction:column}.svf-switch-title{font-size:13px;font-weight:600;color:var(--svf-text)}.svf-switch-desc{font-size:11px;color:var(--svf-text-muted);margin-top:1px}.svf-switch{position:relative;display:inline-block;width:40px;height:22px;flex-shrink:0}.svf-switch input{opacity:0;width:0;height:0}.svf-slider{position:absolute;cursor:pointer;inset:0;background-color:#ffffff14;border:1px solid var(--svf-border);transition:.3s cubic-bezier(.4,0,.2,1);border-radius:34px}.svf-slider:before{position:absolute;content:"";height:14px;width:14px;left:3px;bottom:3px;background-color:var(--svf-text-muted);transition:.3s cubic-bezier(.4,0,.2,1);border-radius:50%}input:checked+.svf-slider{background-color:var(--svf-primary);border-color:var(--svf-primary)}input:checked+.svf-slider:before{transform:translate(18px);background-color:#0c0d10}@keyframes svf-shake{0%,to{transform:translate(0)}25%{transform:translate(-4px)}75%{transform:translate(4px)}}.svf-io-url-input.invalid{border-color:var(--svf-danger)!important;animation:svf-shake .2s ease-in-out 2}.svf-tab-nav{display:flex;background:#00000026;border-bottom:1px solid var(--svf-border);padding:0 12px}.svf-tab-btn{background:transparent;border:none;color:var(--svf-text-muted);padding:14px 16px;font-family:var(--svf-font);font-weight:600;font-size:13px;cursor:pointer;border-bottom:2px solid transparent;transition:all .2s ease}.svf-tab-btn:hover{color:var(--svf-text)}.svf-tab-btn.active{color:var(--svf-primary);border-bottom-color:var(--svf-primary)}.svf-tab-content{display:flex;flex-direction:column;gap:20px;width:100%}.svf-search-wrapper{position:relative;display:flex;align-items:center;margin-bottom:2px}.svf-search-input{width:100%;background:#00000040;border:1px solid var(--svf-border);border-radius:12px;padding:10px 14px 10px 36px;color:var(--svf-text);font-family:var(--svf-font);font-size:13px;outline:none;transition:all .2s cubic-bezier(.4,0,.2,1)}.svf-search-input:focus{border-color:var(--svf-primary);box-shadow:0 0 0 2px var(--svf-primary-glow)}.svf-search-icon{position:absolute;left:14px;color:var(--svf-text-muted);pointer-events:none;display:flex;align-items:center;justify-content:center}.svf-clear-btn{position:absolute;right:12px;background:transparent;border:none;color:var(--svf-text-muted);cursor:pointer;width:20px;height:20px;border-radius:50%;display:grid;place-items:center;transition:background .2s,color .2s;padding:0}.svf-clear-btn:hover{background:#ffffff14;color:#fff}.svf-filtered-list{display:flex;flex-direction:column;gap:8px;background:#00000026;border:1px solid var(--svf-border);border-radius:12px;padding:10px;max-height:180px;overflow-y:auto;scrollbar-width:thin}.svf-filtered-item{background:#ffffff05;border:1px solid rgba(255,255,255,.05);border-radius:8px;padding:8px 12px;display:flex;align-items:center;justify-content:space-between;font-family:var(--svf-mono, monospace);font-size:12px;transition:all .2s cubic-bezier(.4,0,.2,1)}.svf-filtered-item:hover{background:#ffffff0a;border-color:var(--svf-border-hover)}.svf-filtered-text{color:var(--svf-text);word-break:break-all}.svf-filtered-actions{display:flex;gap:6px;flex-shrink:0}.svf-filtered-btn{background:transparent;border:none;color:var(--svf-text-muted);cursor:pointer;width:28px;height:28px;border-radius:6px;display:grid;place-items:center;transition:all .15s ease;padding:0}.svf-filtered-btn:hover{color:#fff;background:#ffffff14}.svf-filtered-btn.delete:hover{color:var(--svf-danger);background:#ef44441a}.svf-filtered-input{width:100%;background:#00000080;border:1px solid var(--svf-primary);border-radius:6px;padding:4px 8px;color:#fff;font-family:var(--svf-mono, monospace);font-size:12px;outline:none}.svf-filtered-empty{padding:12px;font-size:12px;color:var(--svf-text-muted);text-align:center}.svf-textarea,.svf-search-input,.svf-filtered-input,.svf-io-url-input{user-select:text;-webkit-user-select:text}';
var __defProp$1 = Object.defineProperty;
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
var __decorateClass$1 = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
for (var i3 = decorators.length - 1, decorator; i3 >= 0; i3--)
if (decorator = decorators[i3])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp$1(target, key, result);
return result;
};
let PanelElement = class extends i$1 {
constructor() {
super(...arguments);
this._isOpen = false;
this._activeTab = "filters";
this._searchQuery = "";
this._ioStatus = null;
this._isFetchingUrl = false;
this._likedText = "";
this._dislikedText = "";
this._editingMatches = {};
this._lastActiveElement = null;
this._isTextareaFocused = { liked: false, disliked: false };
this._handleKeydown = (e2) => {
if (!this._isOpen) return;
if (e2.key === "Escape") {
this.close();
} else if (e2.key === "Tab") {
this._handleTab(e2);
}
};
}
connectedCallback() {
super.connectedCallback();
if (!document.querySelector('link[href*="fonts.googleapis.com/css2?family=Outfit"]')) {
const fontLink = document.createElement("link");
fontLink.rel = "stylesheet";
fontLink.href = "https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&display=swap";
document.head.appendChild(fontLink);
}
this.style.display = "none";
this._unsubscribe?.();
if (this.store) {
this._syncTextFromStore();
this._unsubscribe = this.store.subscribe(() => {
this.requestUpdate();
this._syncTextFromStore();
});
}
window.removeEventListener("keydown", this._handleKeydown);
window.addEventListener("keydown", this._handleKeydown);
}
disconnectedCallback() {
super.disconnectedCallback();
this._unsubscribe?.();
window.removeEventListener("keydown", this._handleKeydown);
}
_syncTextFromStore() {
if (!this._isTextareaFocused.liked) {
this._likedText = this.store.liked.join("\n");
}
if (!this._isTextareaFocused.disliked) {
this._dislikedText = this.store.disliked.join("\n");
}
}
updated(changedProperties) {
super.updated(changedProperties);
if (this.store) {
applyAccentVariables(this, this.store.accent);
}
const textChanged = changedProperties.has("_likedText") || changedProperties.has("_dislikedText");
const tabChangedToFilters = changedProperties.has("_activeTab") && this._activeTab === "filters";
const panelOpened = changedProperties.has("_isOpen") && this._isOpen;
if (textChanged || tabChangedToFilters || panelOpened) {
if (this._activeTab === "filters") {
const delay = panelOpened ? 200 : 0;
if (this._likedTextarea) this._autoResize(this._likedTextarea, delay);
if (this._dislikedTextarea) this._autoResize(this._dislikedTextarea, delay);
}
}
}
open() {
this.style.display = "block";
this.offsetHeight;
this._lastActiveElement = document.activeElement;
this._isOpen = true;
this._syncTextFromStore();
setTimeout(() => {
this._closeBtn?.focus();
}, 50);
}
close() {
this._isOpen = false;
if (this._lastActiveElement) {
this._lastActiveElement.focus();
this._lastActiveElement = null;
}
const card = this.shadowRoot?.querySelector(".svf-card");
if (card) {
card.addEventListener("transitionend", (e2) => {
if (!this._isOpen && e2.propertyName === "opacity") {
this.style.display = "none";
}
}, { once: true });
} else {
this.style.display = "none";
}
}
isOpen() {
return this._isOpen;
}
_handleTab(e2) {
const focusables = Array.from(
this.shadowRoot.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
)
).filter((el) => {
if (el.disabled) return false;
const rect = el.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return false;
const style = window.getComputedStyle(el);
return style.visibility !== "hidden" && style.opacity !== "0";
});
if (focusables.length === 0) return;
const first = focusables[0];
const last = focusables[focusables.length - 1];
const active = this.shadowRoot.activeElement;
if (e2.shiftKey) {
if (active === first || !focusables.includes(active)) {
last.focus();
e2.preventDefault();
}
} else {
if (active === last || !focusables.includes(active)) {
first.focus();
e2.preventDefault();
}
}
}
render() {
return b`
<div class="svf-backdrop ${this._isOpen ? "open" : ""}" @click=${this.close}></div>
<div class="svf-card ${this._isOpen ? "open" : ""}">
<div class="svf-header">
<div class="svf-title-group">
<div class="svf-title">Better Search ⭐</div>
</div>
<button class="svf-close-btn" @click=${this.close}>${o$2(ICON_CLOSE)}</button>
</div>
<div class="svf-tab-nav">
<button class="svf-tab-btn ${this._activeTab === "filters" ? "active" : ""}" @click=${() => this._activeTab = "filters"}>Domain Filters</button>
<button class="svf-tab-btn ${this._activeTab === "settings" ? "active" : ""}" @click=${() => this._activeTab = "settings"}>Settings</button>
</div>
<div class="svf-body">
<div class="svf-tab-content ${this._activeTab === "filters" ? "active" : ""}" style="display: ${this._activeTab === "filters" ? "flex" : "none"}">
${this._renderFiltersTab()}
</div>
<div class="svf-tab-content ${this._activeTab === "settings" ? "active" : ""}" style="display: ${this._activeTab === "settings" ? "flex" : "none"}">
${this._renderSettingsTab()}
</div>
</div>
</div>
`;
}
_renderFiltersTab() {
return b`
<div class="svf-search-wrapper">
<span class="svf-search-icon">${o$2(ICON_SEARCH)}</span>
<input type="text" id="svf-unified-search" class="svf-search-input" placeholder="Filter all domains (e.g. github)..." .value=${this._searchQuery} @input=${(e2) => this._searchQuery = e2.target.value.trim().toLowerCase()}>
<button type="button" class="svf-clear-btn" style="display: ${this._searchQuery ? "block" : "none"}" @click=${() => {
this._searchQuery = "";
this.shadowRoot?.querySelector("#svf-unified-search")?.focus();
}}>
${o$2(ICON_CLEAR)}
</button>
</div>
${this._renderSection("liked", "Preferred Domains")}
${this._renderSection("disliked", "Disliked Domains")}
`;
}
_renderSection(type, titleText) {
const domains = type === "liked" ? this.store.liked : this.store.disliked;
const textValue = type === "liked" ? this._likedText : this._dislikedText;
const lines = this._getLines(textValue);
const uniques = new Set(lines);
const duplicateCount = lines.length - uniques.size;
let proposalFound = false;
let activeProposal = null;
if (!this._searchQuery) {
const tree = {};
lines.forEach((dom) => {
const root = getRegisterableDomain(dom);
if (!tree[root]) tree[root] = [];
tree[root].push(dom);
});
for (const root of Object.keys(tree)) {
const subCount = tree[root].length;
const isWildcardRule = tree[root].includes(`*.${root}`);
if (subCount >= 3 && !isWildcardRule) {
activeProposal = root;
proposalFound = true;
break;
}
}
}
const matches = this._searchQuery ? domains.filter((d2) => d2.toLowerCase().includes(this._searchQuery)) : [];
const badgeText = this._searchQuery ? `${matches.length} match${matches.length !== 1 ? "es" : ""}` : String(domains.length);
return b`
<div class="svf-section ${type}">
<div class="svf-section-header">
<div class="svf-label">${titleText}</div>
<span class="svf-badge" id="svf-${type}-count">${badgeText}</span>
</div>
${!this._searchQuery && proposalFound && activeProposal ? b`
<div class="svf-merger-card" id="svf-${type}-proposal-card" style="display: flex">
<div class="svf-merger-info">
<div class="svf-merger-title">💡 Wildcard Suggestion</div>
<div class="svf-merger-desc">${lines.filter((l2) => getRegisterableDomain(l2) === activeProposal).length} subdomains of '${activeProposal}' found. Merge to *.${activeProposal}?</div>
</div>
<button class="svf-merger-btn" type="button" @click=${() => {
const rule = `*.${activeProposal}`;
const filtered = lines.filter((line) => getRegisterableDomain(line) !== activeProposal);
if (!filtered.includes(rule)) filtered.push(rule);
this._saveAndReapply(type, filtered);
if (type === "liked") this._likedText = filtered.join("\n");
else this._dislikedText = filtered.join("\n");
}}>Consolidate</button>
</div>
` : A}
${this._searchQuery ? b`
<div class="svf-filtered-list" id="svf-${type}-filtered-list" style="display: flex">
${matches.length === 0 ? b`
<div class="svf-filtered-empty">No matching domains</div>
` : matches.map((match) => {
const isEditing = this._editingMatches[`${type}:${match}`] !== void 0;
return b`
<div class="svf-filtered-item">
${isEditing ? b`
<input type="text" class="svf-filtered-input" .value=${this._editingMatches[`${type}:${match}`]}
@blur=${(e2) => this._saveEdit(type, match, e2.target.value)}
@keydown=${(e2) => {
if (e2.key === "Enter") this._saveEdit(type, match, e2.target.value);
if (e2.key === "Escape") this._cancelEdit(type, match);
}}
${n2((el) => {
if (el) setTimeout(() => el.focus(), 0);
})}
>
` : b`
<span class="svf-filtered-text">${match}</span>
<div class="svf-filtered-actions">
<button type="button" class="svf-filtered-btn" title="Edit" @click=${() => this._startEdit(type, match)}>
${o$2(ICON_EDIT)}
</button>
<button type="button" class="svf-filtered-btn delete" title="Remove" @click=${() => this._deleteMatch(type, match)}>
${o$2(ICON_DELETE)}
</button>
</div>
`}
</div>
`;
})}
</div>
` : b`
<textarea class="svf-textarea" id="svf-${type}-textarea" placeholder="e.g.\nstackoverflow.com\ngithub.com"
style="display: block"
.value=${textValue}
@input=${(e2) => {
if (type === "liked") this._likedText = e2.target.value;
else this._dislikedText = e2.target.value;
}}
@focus=${() => this._isTextareaFocused[type] = true}
@blur=${() => {
this._isTextareaFocused[type] = false;
}}
></textarea>
<div class="svf-textarea-status-row" style="display: flex">
<div class="svf-textarea-msg-info" id="svf-${type}-msg-info">
${duplicateCount > 0 ? b`<span style="color: var(--svf-danger)">⚠️ ${duplicateCount} duplicates found (will merge on apply)</span>` : b`${lines.length} domains configured`}
</div>
<button class="svf-apply-btn" id="svf-${type}-apply" type="button" @click=${() => this._saveAndReapply(type, lines)}>
Apply Changes
</button>
</div>
`}
</div>
`;
}
_renderSettingsTab() {
const accent = this.store.accent;
const mode = this.store.dislikeMode;
return b`
<div class="svf-control-group">
<div class="svf-label">Dislike Filter Mode</div>
<div class="svf-segmented">
<button class="svf-seg-btn ${mode === "fade" ? "active" : ""}" @click=${() => {
this.store.setDislikeMode("fade");
this.scanner.reapply();
}}>Fade Results</button>
<button class="svf-seg-btn ${mode === "hide" ? "active" : ""}" @click=${() => {
this.store.setDislikeMode("hide");
this.scanner.reapply();
}}>Hide (Reveal-on-click)</button>
</div>
</div>
<div class="svf-control-group">
<div class="svf-label">Theme Accent Color</div>
<div class="svf-color-picker-row">
${Object.keys(ACCENT_COLORS).map((name) => {
const acc = ACCENT_COLORS[name];
return b`
<div class="svf-color-dot ${name === accent ? "active" : ""}" data-accent="${name}" style="background-color: ${acc.primary}; --dot-glow: ${acc.dotGlow}" @click=${() => this.store.setAccent(name)}></div>
`;
})}
</div>
</div>
<div class="svf-control-group">
<div class="svf-label">Preferences</div>
<div class="svf-switch-row">
<div class="svf-switch-label-group">
<div class="svf-switch-title">Show floating button</div>
<div class="svf-switch-desc">Toggle settings icon visibility in the bottom right</div>
</div>
<label class="svf-switch">
<input type="checkbox" id="svf-pref-show-trigger" .checked=${this.store.showTrigger} @change=${(e2) => this.store.setShowTrigger(e2.target.checked)}>
<span class="svf-slider"></span>
</label>
</div>
</div>
<div class="svf-io-section">
<div class="svf-label">Import / Export</div>
<div class="svf-io-row">
<button class="svf-io-btn" @click=${this._copyJson}>${o$2(ICON_COPY)} Copy JSON</button>
<button class="svf-io-btn" @click=${this._downloadJson}>${o$2(ICON_DOWNLOAD)} Download</button>
</div>
<div class="svf-io-row">
<input type="file" accept=".json,application/json" class="svf-io-file" id="svf-io-file" @change=${this._importFile}>
<button class="svf-io-btn primary" @click=${() => this.shadowRoot?.querySelector("#svf-io-file")?.click()}>
${o$2(ICON_IMPORT)} Import File
</button>
</div>
<div class="svf-label">Import from URL</div>
<form class="svf-io-url-form" @submit=${this._importUrl}>
<input type="url" class="svf-io-url-input" id="svf-io-url-input" value="https://gist.githubusercontent.com/quantavil/12880b87fd1ebd497469455d1898088b/raw/63daeaec36bc08e68f94a2db9dc5ec08af6e3ff9/domains.json" placeholder="https://gist.githubusercontent.com/…/domains.json" ?disabled=${this._isFetchingUrl} @input=${(e2) => e2.target.classList.remove("invalid")}>
<button type="submit" class="svf-io-btn primary" id="svf-url-fetch-btn" style="flex: 0 0 auto" ?disabled=${this._isFetchingUrl}>${this._isFetchingUrl ? "Fetching..." : "Fetch"}</button>
</form>
${this._ioStatus ? b`<div class="svf-io-status ${this._ioStatus.type}" style="display: block">${this._ioStatus.msg}</div>` : b`<div class="svf-io-status" style="display: none"></div>`}
</div>
`;
}
_showStatus(msg, type) {
this._ioStatus = { msg, type };
clearTimeout(this._ioTimeout);
this._ioTimeout = window.setTimeout(() => {
this._ioStatus = null;
}, 3500);
}
_copyJson() {
const json = JSON.stringify(this.store.exportDomains(), null, 2);
if (typeof GM_setClipboard === "function") {
GM_setClipboard(json, "text");
this._showStatus("Copied to clipboard!", "success");
} else {
navigator.clipboard.writeText(json).then(() => {
this._showStatus("Copied to clipboard!", "success");
}).catch(() => {
this._showStatus("Failed to copy", "error");
});
}
}
_downloadJson() {
const json = JSON.stringify(this.store.exportDomains(), null, 2);
const blob = new Blob([json], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a2 = document.createElement("a");
a2.href = url;
a2.download = "svf-domains.json";
a2.click();
URL.revokeObjectURL(url);
this._showStatus("Downloaded svf-domains.json", "success");
}
_importFile(e2) {
const fileInput = e2.target;
const file = fileInput.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
this._applyImport(reader.result, "file");
fileInput.value = "";
};
reader.readAsText(file);
}
_importUrl(e2) {
e2.preventDefault();
const urlInput = this.shadowRoot?.querySelector("#svf-io-url-input");
if (!urlInput) return;
const url = urlInput.value.trim();
if (!url) return;
try {
new URL(url);
this._isFetchingUrl = true;
this._fetchAndImport(url, () => {
this._isFetchingUrl = false;
});
} catch {
urlInput.classList.add("invalid");
}
}
_applyImport(text, source) {
try {
const data = parseJsonRobust(text);
const n3 = this.store.importDomains(data);
this.scanner.reapply();
const suffix = source === "url" ? " from URL" : "";
this._showStatus(`Imported ${n3} new domain${n3 !== 1 ? "s" : ""}${suffix}`, "success");
} catch {
const errorMsg = source === "url" ? "Invalid JSON at URL" : "Invalid JSON file";
this._showStatus(errorMsg, "error");
}
}
_fetchAndImport(url, onDone) {
const TIMEOUT_MS = 1e4;
if (typeof GM_xmlhttpRequest === "function") {
let timedOut = false;
const timeoutId = setTimeout(() => {
timedOut = true;
this._showStatus("Request timed out", "error");
onDone();
}, TIMEOUT_MS);
GM_xmlhttpRequest({
method: "GET",
url,
timeout: TIMEOUT_MS,
onload: (resp) => {
if (timedOut) return;
clearTimeout(timeoutId);
if (resp.status >= 200 && resp.status < 300) {
this._applyImport(resp.responseText, "url");
} else {
this._showStatus(`Fetch failed: ${resp.status} ${resp.statusText}`, "error");
}
onDone();
},
onerror: () => {
if (timedOut) return;
clearTimeout(timeoutId);
this._showStatus("Network error fetching URL", "error");
onDone();
},
ontimeout: () => {
if (timedOut) return;
clearTimeout(timeoutId);
this._showStatus("Request timed out", "error");
onDone();
}
});
} else {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
fetch(url, { signal: controller.signal }).then((r2) => {
if (!r2.ok) throw new Error(String(r2.status));
return r2.text();
}).then((text) => {
this._applyImport(text, "url");
}).catch((err) => {
if (err?.name === "AbortError") {
this._showStatus("Request timed out", "error");
} else {
this._showStatus("Failed to fetch or parse URL", "error");
}
}).finally(() => {
clearTimeout(timeoutId);
onDone();
});
}
}
_startEdit(type, match) {
this._editingMatches = { ...this._editingMatches, [`${type}:${match}`]: match };
}
_cancelEdit(type, match) {
if (this._editingMatches[`${type}:${match}`] === void 0) return;
const next = { ...this._editingMatches };
delete next[`${type}:${match}`];
this._editingMatches = next;
}
_saveEdit(type, oldMatch, newValRaw) {
if (this._editingMatches[`${type}:${oldMatch}`] === void 0) return;
const newVal = newValRaw.trim().toLowerCase();
if (newVal && newVal !== oldMatch) {
const domains = type === "liked" ? this.store.liked : this.store.disliked;
const updated = domains.map((d2) => d2 === oldMatch ? newVal : d2);
this._saveAndReapply(type, updated);
}
this._cancelEdit(type, oldMatch);
}
_deleteMatch(type, match) {
const domains = type === "liked" ? this.store.liked : this.store.disliked;
const updated = domains.filter((d2) => d2 !== match);
this._saveAndReapply(type, updated);
}
_getLines(text) {
return text.split("\n").map((l2) => l2.trim().toLowerCase()).filter(Boolean);
}
_saveAndReapply(type, domains) {
const deduped = [...new Set(domains)];
deduped.sort();
this.store.setDomains(type, deduped);
this.scanner.reapply();
}
_autoResize(textarea, delay = 0) {
const resize = () => {
if (!textarea) return;
textarea.style.height = "auto";
textarea.style.height = `${Math.min(Math.max(textarea.scrollHeight + 2, 60), 150)}px`;
};
if (delay > 0) setTimeout(resize, delay);
else resize();
}
};
PanelElement.styles = r$6(PANEL_CSS);
__decorateClass$1([
n$2({ attribute: false })
], PanelElement.prototype, "store", 2);
__decorateClass$1([
n$2({ attribute: false })
], PanelElement.prototype, "scanner", 2);
__decorateClass$1([
r$2()
], PanelElement.prototype, "_isOpen", 2);
__decorateClass$1([
r$2()
], PanelElement.prototype, "_activeTab", 2);
__decorateClass$1([
r$2()
], PanelElement.prototype, "_searchQuery", 2);
__decorateClass$1([
r$2()
], PanelElement.prototype, "_ioStatus", 2);
__decorateClass$1([
r$2()
], PanelElement.prototype, "_isFetchingUrl", 2);
__decorateClass$1([
r$2()
], PanelElement.prototype, "_likedText", 2);
__decorateClass$1([
r$2()
], PanelElement.prototype, "_dislikedText", 2);
__decorateClass$1([
r$2()
], PanelElement.prototype, "_editingMatches", 2);
__decorateClass$1([
e$2(".svf-close-btn")
], PanelElement.prototype, "_closeBtn", 2);
__decorateClass$1([
e$2("#svf-liked-textarea")
], PanelElement.prototype, "_likedTextarea", 2);
__decorateClass$1([
e$2("#svf-disliked-textarea")
], PanelElement.prototype, "_dislikedTextarea", 2);
PanelElement = __decorateClass$1([
t$1("svf-panel")
], PanelElement);
const TRIGGER_CSS = ":host{user-select:none;-webkit-user-select:none}.svf-trigger{position:fixed;bottom:24px;right:24px;width:48px;height:48px;border-radius:50%;background:#121216d9;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.08);box-shadow:0 8px 32px #0000004d,0 0 0 1px #ffffff05;color:#ffffffb3;cursor:pointer;display:grid;place-items:center;z-index:2147483640;transition:transform .25s cubic-bezier(.25,.8,.25,1),border-color .2s ease,box-shadow .2s ease,color .2s ease;will-change:transform;pointer-events:auto;padding:0;outline:none}.svf-trigger:hover{transform:translateY(-3px) scale(1.05);border-color:var(--svf-primary);box-shadow:0 12px 36px var(--svf-primary-glow),0 0 0 1px var(--svf-primary-glow);color:var(--svf-primary)}.svf-trigger:active{transform:translateY(0) scale(.95)}.svf-trigger svg{transition:transform .4s ease}.svf-trigger:hover svg{transform:rotate(30deg) scale(1.05)}";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i3 = decorators.length - 1, decorator; i3 >= 0; i3--)
if (decorator = decorators[i3])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp(target, key, result);
return result;
};
let TriggerElement = class extends i$1 {
constructor() {
super(...arguments);
this._show = false;
}
updated(changedProperties) {
super.updated(changedProperties);
if (this.store) {
applyAccentVariables(this, this.store.accent);
}
}
connectedCallback() {
super.connectedCallback();
this._unsubscribe?.();
if (this.store) {
this._show = this.store.showTrigger;
this._unsubscribe = this.store.subscribe(() => {
this._show = this.store.showTrigger;
});
}
}
disconnectedCallback() {
super.disconnectedCallback();
this._unsubscribe?.();
}
render() {
return b`
<button
class="svf-trigger"
aria-label="Open Better Search Settings"
title="Better Search Settings"
style="display: ${this._show ? "grid" : "none"}"
@click=${this._handleClick}
>
${o$2(ICON_TRIGGER)}
</button>
`;
}
_handleClick(e2) {
e2.stopPropagation();
if (this.panel) {
if (this.panel.isOpen()) {
this.panel.close();
} else {
this.panel.open();
}
}
}
};
TriggerElement.styles = r$6(TRIGGER_CSS);
__decorateClass([
n$2({ attribute: false })
], TriggerElement.prototype, "store", 2);
__decorateClass([
n$2({ attribute: false })
], TriggerElement.prototype, "panel", 2);
__decorateClass([
r$2()
], TriggerElement.prototype, "_show", 2);
TriggerElement = __decorateClass([
t$1("svf-trigger")
], TriggerElement);
const GOOGLE_INTERNAL_HOSTS = /* @__PURE__ */ new Set([
"www.google.com",
"accounts.google.com",
"maps.google.com",
"webcache.googleusercontent.com"
]);
function extractUrl$4(item) {
const candidates = item.querySelectorAll("a[href]");
for (const a2 of candidates) {
const href = a2.href;
if (!href || href.startsWith("javascript") || href.startsWith("#")) continue;
try {
const u2 = new URL(href);
if (GOOGLE_INTERNAL_HOSTS.has(u2.hostname) || u2.pathname.startsWith("/search") || u2.hostname.includes("gstatic.")) {
continue;
}
return href;
} catch (err) {
console.debug("[SVF] Skipped malformed Google result URL:", href, err);
}
}
return null;
}
const googleEngine = {
name: "google",
// The element that contains all organic .g cards — stable across layout changes
containerSelector: '#search, #rso, [data-async-context] [id="search"]',
// Each organic result card
itemSelector: ".g, .tF2Cxc, .MjjYud",
extractUrl: extractUrl$4
};
function decodeBingUrl(href) {
try {
if (!href.includes("bing.com/ck/a")) return href;
const uObj = new URL(href);
let u2 = uObj.searchParams.get("u");
if (u2) {
u2 = u2.replace(/^a[0-9]/, "");
let normalized = u2.replace(/-/g, "+").replace(/_/g, "/");
normalized = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
const decoded = decodeURIComponent(
atob(normalized).split("").map((c2) => "%" + ("00" + c2.charCodeAt(0).toString(16)).slice(-2)).join("")
);
if (decoded && decoded.startsWith("http")) {
return decoded;
}
}
} catch (err) {
console.debug("[SVF] Failed to decode Bing URL:", href, err);
}
return null;
}
function extractUrl$3(item) {
const h2a = item.querySelector("h2 a[href]");
if (h2a?.href) {
const decoded = decodeBingUrl(h2a.href);
if (decoded) return decoded;
}
const cite = item.querySelector("cite");
if (cite?.textContent) {
let t2 = cite.textContent.trim();
if (t2.includes("›")) {
t2 = t2.split("›")[0].trim();
}
if (t2.startsWith("http")) return t2;
if (!t2.includes(".")) return null;
return "https://" + t2;
}
return null;
}
const bingEngine = {
name: "bing",
containerSelector: "#b_results",
itemSelector: ".b_algo",
extractUrl: extractUrl$3
};
function extractUrl$2(item) {
const titleA = item.querySelector(
'a[data-testid="result-title-a"], h2 a[href], a.result__a[href]'
);
let url = null;
if (titleA?.href) {
url = titleA.href;
} else {
for (const a2 of item.querySelectorAll("a[href]")) {
const href = a2.href;
if (!href || href.startsWith("#") || href.startsWith("javascript")) continue;
if (!href.includes("duckduckgo.com") || href.includes("/l/")) {
url = href;
break;
}
}
}
if (url && url.includes("/l/")) {
try {
const u2 = new URL(url);
const uddg = u2.searchParams.get("uddg");
if (uddg) {
return decodeURIComponent(uddg);
}
} catch (err) {
console.debug("[SVF] Failed to parse DDG redirect URL:", url, err);
}
}
return url;
}
const ddgEngine = {
name: "ddg",
containerSelector: ".react-results--main, #links, .serp__results",
itemSelector: 'article[data-testid="result"], .result.results_links_deep, .nrn-react-div[data-nr]',
extractUrl: extractUrl$2
};
function extractUrl$1(item) {
const a2 = item.querySelector(
"a.result-header[href], a.svelte-1dh4yef[href], .snippet-title a[href]"
);
if (a2?.href) return a2.href;
for (const link of item.querySelectorAll("a[href]")) {
const h2 = link.href;
if (h2 && !h2.includes("search.brave.com") && !h2.startsWith("#")) return h2;
}
return null;
}
const braveEngine = {
name: "brave",
containerSelector: "#results, .results",
itemSelector: ".snippet[data-pos], .result[data-pos]",
extractUrl: extractUrl$1
};
function extractUrl(item) {
const a2 = item.querySelector(
"a.link.organic__url[href], h2 a.link[href], .OrganicTitle a[href]"
);
if (a2?.href) return a2.href;
for (const link of item.querySelectorAll("a[href]")) {
const h2 = link.href;
if (!h2 || h2.startsWith("#") || h2.startsWith("javascript")) continue;
if (!h2.includes("yandex.") && !h2.includes("ya.ru")) return h2;
}
return null;
}
const yandexEngine = {
name: "yandex",
containerSelector: '.content__left, #search-result, [data-layout-part="cl"], ul[id^="search-"]',
itemSelector: ".serp-item.organic, li.serp-item[data-cid], li:has(.Organic), .Organic",
extractUrl,
shouldActivate: (url) => url.pathname.startsWith("/search")
};
const ENGINES = [
{ pattern: /(?:^|\.)google\.[a-z]{2,6}(?:\.[a-z]{2,3})?$/i, config: googleEngine },
{ pattern: /(?:^|\.)bing\.com$/i, config: bingEngine },
{ pattern: /(?:^|\.)duckduckgo\.com$/i, config: ddgEngine },
{ pattern: /(?:^|\.)search\.brave\.com$/i, config: braveEngine },
{ pattern: /(?:^|\.)(?:yandex\.[a-z]{2,}|ya\.ru)$/i, config: yandexEngine }
];
function detectEngine() {
const host = window.location.hostname;
for (const { pattern, config } of ENGINES) {
if (pattern.test(host)) {
if (config.shouldActivate && !config.shouldActivate(new URL(window.location.href))) {
continue;
}
return config;
}
}
return null;
}
let historyHooked = false;
function hookHistoryEvents() {
if (historyHooked) return;
historyHooked = true;
const patch = (type) => {
const orig = history[type];
history[type] = function(...args) {
const res = orig.apply(this, args);
window.dispatchEvent(new Event("svf-location-change"));
return res;
};
};
patch("pushState");
patch("replaceState");
}
class Controller {
constructor() {
this._store = null;
this._styleSheet = null;
this._scanner = null;
this._hoverOverlay = null;
this._panel = null;
this._trigger = null;
this._shadowHost = null;
this._abortController = null;
this._engine = null;
this._attachInterval = null;
this._locationTimeout = null;
this._onLocationChange = this._onLocationChange.bind(this);
}
init() {
const globalKey = "__svf_controller";
const prevController = window[globalKey];
if (prevController && prevController !== this) {
try {
prevController.destroy();
} catch (err) {
console.error("[SVF] Error destroying previous controller instance:", err);
}
}
this.destroy();
window[globalKey] = this;
try {
this._abortController = new AbortController();
const signal = this._abortController.signal;
this._engine = detectEngine();
if (!this._engine) {
return;
}
this._store = new Store();
this._styleSheet = new FilterStyleSheet(this._store);
this._scanner = new Scanner(this._store, this._engine, signal);
this._shadowHost = document.createElement("div");
this._shadowHost.id = "svf-shadow-host";
this._shadowHost.style.position = "fixed";
this._shadowHost.style.zIndex = "2147483644";
this._shadowHost.style.pointerEvents = "none";
this._shadowHost.style.userSelect = "none";
this._shadowHost.style.webkitUserSelect = "none";
const shadow = this._shadowHost.attachShadow({ mode: "open" });
(document.body || document.documentElement).appendChild(this._shadowHost);
this._panel = document.createElement("svf-panel");
this._panel.store = this._store;
this._panel.scanner = this._scanner;
shadow.appendChild(this._panel);
this._trigger = document.createElement("svf-trigger");
this._trigger.store = this._store;
this._trigger.panel = this._panel;
shadow.appendChild(this._trigger);
this._hoverOverlay = document.createElement("svf-hover-overlay");
this._hoverOverlay.store = this._store;
this._hoverOverlay.scanner = this._scanner;
this._hoverOverlay.signal = signal;
shadow.appendChild(this._hoverOverlay);
if (typeof GM_registerMenuCommand === "function") {
GM_registerMenuCommand("Better Search Settings", () => {
this._panel?.open();
});
}
if (!this._scanner?.attach()) {
this._startAttachInterval();
}
window.addEventListener("popstate", this._onLocationChange, { signal });
window.addEventListener("svf-location-change", this._onLocationChange, { signal });
hookHistoryEvents();
} catch (err) {
this.destroy();
throw err;
}
}
destroy() {
if (this._locationTimeout) {
clearTimeout(this._locationTimeout);
this._locationTimeout = null;
}
if (this._attachInterval) {
clearInterval(this._attachInterval);
this._attachInterval = null;
}
if (this._abortController) {
this._abortController.abort();
this._abortController = null;
}
if (this._scanner) {
this._scanner.destroy();
this._scanner = null;
}
if (this._hoverOverlay) {
this._hoverOverlay.remove();
this._hoverOverlay = null;
}
if (this._styleSheet) {
this._styleSheet.destroy();
this._styleSheet = null;
}
if (this._shadowHost) {
this._shadowHost.remove();
this._shadowHost = null;
}
if (this._store) {
this._store.destroy();
this._store = null;
}
this._panel = null;
this._trigger = null;
this._engine = null;
const globalKey = "__svf_controller";
if (window[globalKey] === this) {
window[globalKey] = null;
}
}
_onLocationChange() {
if (this._locationTimeout) {
clearTimeout(this._locationTimeout);
}
this._locationTimeout = setTimeout(() => {
this._locationTimeout = null;
const nextEngine = detectEngine();
if (!nextEngine) {
this.destroy();
return;
}
if (!this._engine || this._engine.name !== nextEngine.name) {
this.init();
} else {
if (!this._scanner?.attach()) {
this._startAttachInterval();
}
}
}, 150);
}
_startAttachInterval() {
this._clearAttachInterval();
let attempts = 0;
this._attachInterval = setInterval(() => {
if (this._scanner?.attach()) {
this._clearAttachInterval();
} else if (++attempts > 40) {
this._clearAttachInterval();
}
}, 500);
}
_clearAttachInterval() {
if (this._attachInterval) {
clearInterval(this._attachInterval);
this._attachInterval = null;
}
}
}
try {
const controller = new Controller();
controller.init();
} catch (err) {
console.error("[SVF] Init error:", err);
}
})();