Better Rule34Video

Streamlined filter bar, instant client search & filtering, ad cleaner, and seamless auto next page infinite scroll for Rule34Video.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Better Rule34Video
// @namespace    https://github.com/quantavil/userscript/
// @version      1.0.0
// @author       quantavil
// @description  Streamlined filter bar, instant client search & filtering, ad cleaner, and seamless auto next page infinite scroll for Rule34Video.
// @license      MIT
// @match        *://*.rule34video.com/*
// @match        *://rule34video.com/*
// @run-at       document-end
// ==/UserScript==

(function () {
  'use strict';

  var __defProp = Object.defineProperty;
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  const BASE_YEAR = 2018;
  const DEFAULT_FILTER = {
    query: "",
    soundOnly: false,
    hdOnly: false,
    futaFilter: "all",
    hideWatched: false,
    minRating: 0,
    minViews: 0,
    minYear: BASE_YEAR,
    durationMinSeconds: null
  };
  function parseDuration(timeStr) {
    if (!timeStr) return 0;
    const cleaned = timeStr.trim().replace(/[^\d:]/g, "");
    if (!cleaned) return 0;
    const parts = cleaned.split(":").map((p) => parseInt(p, 10));
    if (parts.some(isNaN)) return 0;
    if (parts.length === 1) return parts[0];
    if (parts.length === 2) return parts[0] * 60 + parts[1];
    if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2];
    return 0;
  }
  function parseViews(viewStr) {
    var _a;
    if (!viewStr) return 0;
    const match = /([\d.]+)\s*([KkMmBb])?/.exec(viewStr.replace(/,/g, "").trim());
    if (!match) return 0;
    const num = parseFloat(match[1]);
    if (isNaN(num)) return 0;
    const unit = (_a = match[2]) == null ? void 0 : _a.toUpperCase();
    if (unit === "K") return Math.round(num * 1e3);
    if (unit === "M") return Math.round(num * 1e6);
    if (unit === "B") return Math.round(num * 1e9);
    return Math.round(num);
  }
  function parseRating(ratingStr) {
    if (!ratingStr) return { percent: 0, count: 0 };
    const percentMatch = /(\d+)%/.exec(ratingStr);
    const countMatch = /\(([\d,]+)\)/.exec(ratingStr);
    const percent = percentMatch ? parseInt(percentMatch[1], 10) : 0;
    const count = countMatch ? parseInt(countMatch[1].replace(/,/g, ""), 10) : 0;
    return { percent, count };
  }
  function parseSubmittedYear(dateStr, currentYear = (/* @__PURE__ */ new Date()).getFullYear()) {
    if (!dateStr) return currentYear;
    const text = dateStr.toLowerCase().trim();
    const explicitYearMatch = /\b(20[12]\d)\b/.exec(text);
    if (explicitYearMatch) {
      return parseInt(explicitYearMatch[1], 10);
    }
    const yearsAgoMatch = /(\d+)\s+years?\s+ago/.exec(text);
    if (yearsAgoMatch) {
      const diff = parseInt(yearsAgoMatch[1], 10);
      return Math.max(BASE_YEAR, currentYear - diff);
    }
    const monthsAgoMatch = /(\d+)\s+months?\s+ago/.exec(text);
    if (monthsAgoMatch) {
      const months = parseInt(monthsAgoMatch[1], 10);
      const d = /* @__PURE__ */ new Date();
      d.setMonth(d.getMonth() - months);
      return d.getFullYear();
    }
    return currentYear;
  }
  function isAdCard(el) {
    if (el.classList.contains("spot-thumb") || el.closest(".spots")) return true;
    if (el.querySelector("iframe")) return true;
    const header = el.querySelector("header");
    if (header && /AD/i.test(header.textContent ?? "")) return true;
    const link = el.querySelector("a.th, a");
    if (link) {
      const href = link.getAttribute("href") || "";
      if (href.includes("/v1/d.php") || href.includes("sadbaguette") || href.includes("ku34bh9la09")) {
        return true;
      }
    }
    const hasVideoCardId = Boolean(el.dataset.videoCardId);
    const hasVideoHref = Boolean(el.querySelector('a[href*="/video/"]'));
    return !hasVideoCardId && !hasVideoHref;
  }
  function extractCardData(el) {
    var _a, _b, _c, _d, _e, _f;
    if (isAdCard(el)) return null;
    const cardLink = el.querySelector('a.th, a[href*="/video/"]');
    const href = (cardLink == null ? void 0 : cardLink.getAttribute("href")) || "";
    const idMatch = /video\/(\d+)/.exec(href);
    const id = el.dataset.videoCardId || (idMatch == null ? void 0 : idMatch[1]) || "";
    const titleEl = el.querySelector(".thumb_title");
    const title = ((_a = titleEl == null ? void 0 : titleEl.textContent) == null ? void 0 : _a.trim()) || ((_b = cardLink == null ? void 0 : cardLink.getAttribute("title")) == null ? void 0 : _b.trim()) || "";
    const imgEl = el.querySelector("img.thumb, img");
    const thumbUrl = (imgEl == null ? void 0 : imgEl.dataset.webp) || (imgEl == null ? void 0 : imgEl.dataset.original) || (imgEl == null ? void 0 : imgEl.getAttribute("data-webp")) || (imgEl == null ? void 0 : imgEl.getAttribute("data-original")) || (imgEl == null ? void 0 : imgEl.src) || "";
    const previewWrap = el.querySelector(".wrap_image");
    const previewUrl = (previewWrap == null ? void 0 : previewWrap.dataset.preview) || (previewWrap == null ? void 0 : previewWrap.getAttribute("data-preview")) || null;
    const timeEl = el.querySelector(".time");
    const durationFormatted = ((_c = timeEl == null ? void 0 : timeEl.textContent) == null ? void 0 : _c.trim()) || "";
    const durationSeconds = parseDuration(durationFormatted);
    const ratingEl = el.querySelector(".video-card-meta__rating") || el.querySelector(".rating");
    const { percent: ratingPercent, count: votesCount } = parseRating((ratingEl == null ? void 0 : ratingEl.textContent) ?? "");
    const viewsEl = el.querySelector(".video-views-count") || el.querySelector(".views");
    const viewsFormatted = ((_d = viewsEl == null ? void 0 : viewsEl.textContent) == null ? void 0 : _d.trim()) || "";
    const viewsCount = parseViews(viewsFormatted);
    const commentsEl = el.querySelector(".video-comments-count");
    const commentsCount = parseInt(((_e = commentsEl == null ? void 0 : commentsEl.textContent) == null ? void 0 : _e.trim()) || "0", 10) || 0;
    const hasSound = el.querySelector(".sound") !== null;
    const isHd = el.querySelector(".quality") !== null || el.querySelector(".custom-hd") !== null;
    const isFuta = el.querySelector(".futa") !== null;
    const isWatched = (previewWrap == null ? void 0 : previewWrap.classList.contains("watched")) || el.classList.contains("watched") || Boolean(el.querySelector(".watched"));
    const dateEl = el.querySelector(".video-card-meta__date") || el.querySelector(".added");
    const submittedAgo = (dateEl == null ? void 0 : dateEl.getAttribute("title")) || ((_f = dateEl == null ? void 0 : dateEl.textContent) == null ? void 0 : _f.trim()) || "";
    const submittedYear = parseSubmittedYear(submittedAgo);
    return {
      id,
      title,
      url: href.startsWith("http") ? href : new URL(href, "https://rule34video.com").href,
      previewUrl,
      thumbUrl,
      durationSeconds,
      durationFormatted,
      ratingPercent,
      votesCount,
      viewsCount,
      viewsFormatted,
      commentsCount,
      hasSound,
      isHd,
      isFuta,
      isWatched,
      submittedAgo,
      submittedYear
    };
  }
  function matchesClientFilter(card, filter) {
    if (filter.query.trim()) {
      const keywords = filter.query.toLowerCase().trim().split(/\s+/);
      const titleLower = card.title.toLowerCase();
      for (const kw of keywords) {
        if (!titleLower.includes(kw)) return false;
      }
    }
    if (filter.soundOnly && !card.hasSound) return false;
    if (filter.hdOnly && !card.isHd) return false;
    if (filter.futaFilter === "hide" && card.isFuta) return false;
    if (filter.futaFilter === "only" && !card.isFuta) return false;
    if (filter.hideWatched && card.isWatched) return false;
    if (filter.minRating > 0 && card.ratingPercent < filter.minRating) return false;
    if (filter.minViews > 0 && card.viewsCount < filter.minViews) return false;
    if (filter.minYear > BASE_YEAR && card.submittedYear < filter.minYear) return false;
    if (filter.durationMinSeconds !== null && card.durationSeconds < filter.durationMinSeconds) {
      return false;
    }
    return true;
  }
  function resolveNextPageUrl(currentUrlStr, nextRawHref) {
    try {
      const trimmedHref = nextRawHref.trim();
      if (!trimmedHref || trimmedHref.startsWith("#") || trimmedHref.startsWith("javascript:")) {
        return "";
      }
      const current = new URL(currentUrlStr);
      const next = new URL(trimmedHref, current.href);
      if (!next.search && current.search) {
        next.search = current.search;
      } else if (current.search && next.search) {
        current.searchParams.forEach((val, key) => {
          if (!next.searchParams.has(key)) {
            next.searchParams.set(key, val);
          }
        });
      }
      return next.toString();
    } catch {
      return nextRawHref;
    }
  }
  function parseCurrentUrlFilters(urlStr) {
    try {
      const url = new URL(urlStr);
      const params = url.searchParams;
      const result = {};
      const fromDate = params.get("post_date_from");
      if (fromDate) {
        const yrMatch = /^(\d{4})/.exec(fromDate);
        if (yrMatch) {
          result.minYear = Math.max(BASE_YEAR, parseInt(yrMatch[1], 10));
        }
      }
      const fromDur = params.get("duration_from");
      if (fromDur && !isNaN(parseInt(fromDur, 10))) {
        result.durationMinSeconds = parseInt(fromDur, 10);
      }
      return result;
    } catch {
      return {};
    }
  }
  const AD_SELECTORS = [
    ".spot-thumb",
    ".spots",
    ".sidebar_ad_buttons",
    ".footer_spots",
    "ins.adsbyjuicy",
    'iframe[src*="sadbaguette"]',
    'iframe[src*="traffic"]',
    'iframe[src*="ad"]',
    ".item.thumb:has(header)",
    ".item.thumb:has(iframe)",
    '.item.thumb a[href*="/v1/d.php"]'
  ];
  function cleanAds(root = document) {
    let count = 0;
    for (const selector of AD_SELECTORS) {
      try {
        const elements = root.querySelectorAll(selector);
        for (const el of elements) {
          const cardParent = el.closest(".item.thumb") ?? el;
          cardParent.remove();
          count++;
        }
      } catch {
      }
    }
    const thumbs = root.querySelectorAll(".item.thumb");
    for (const thumb of thumbs) {
      if (isAdCard(thumb)) {
        thumb.remove();
        count++;
      }
    }
    return count;
  }
  function findVideosContainer() {
    return document.querySelector(".content_general .thumbs") || document.querySelector('[id^="custom_list_videos_"][id$="_items"].thumbs') || document.querySelector(".twocolumns .thumbs") || document.querySelector(".thumbs") || document.querySelector('[id$="_items"]');
  }
  function unclipBodyOverflow() {
    const wrappers = document.querySelectorAll("body > div");
    for (const w of wrappers) {
      if (w.style.overflow === "hidden") {
        w.style.overflow = "visible";
      }
    }
  }
  class AutoPager {
    constructor(options) {
      __publicField(this, "container", null);
      __publicField(this, "statusContainer", null);
      __publicField(this, "sentinel", null);
      __publicField(this, "observer", null);
      __publicField(this, "nextUrl", null);
      __publicField(this, "currentPage", 1);
      __publicField(this, "isLoading", false);
      __publicField(this, "isAppending", false);
      __publicField(this, "seenCardIds", /* @__PURE__ */ new Set());
      __publicField(this, "onNewCards");
      __publicField(this, "onPageLoaded");
      __publicField(this, "scrollHandler", null);
      this.onNewCards = options.onNewCards;
      this.onPageLoaded = options.onPageLoaded;
    }
    init() {
      var _a;
      unclipBodyOverflow();
      this.container = findVideosContainer();
      if (!this.container) return;
      this.currentPage = this.detectCurrentPageNumber();
      const initialCards = this.container.querySelectorAll(".item.thumb");
      for (const card of initialCards) {
        if (isAdCard(card)) {
          card.remove();
          continue;
        }
        const id = card.dataset.videoCardId || ((_a = card.querySelector('a[href*="/video/"]')) == null ? void 0 : _a.getAttribute("href"));
        if (id) this.seenCardIds.add(id);
      }
      this.detectNextPageUrl(document);
      this.mountStatusElements();
      this.setupObserver();
      this.setupScrollListener();
    }
    getIsAppending() {
      return this.isAppending;
    }
    getNextUrl() {
      return this.nextUrl;
    }
    getCurrentPage() {
      return this.currentPage;
    }
    getTotalLoadedCount() {
      return this.seenCardIds.size;
    }
    detectCurrentPageNumber() {
      try {
        const url = new URL(window.location.href);
        const fromParam = url.searchParams.get("from_videos") || url.searchParams.get("from");
        if (fromParam) {
          const p = parseInt(fromParam, 10);
          if (!isNaN(p) && p > 0) return p;
        }
        const pathMatch = /\/(\d+)\/?$/.exec(url.pathname);
        if (pathMatch) {
          const p = parseInt(pathMatch[1], 10);
          if (!isNaN(p) && p > 0) return p;
        }
      } catch {
      }
      return 1;
    }
    detectNextPageUrl(root) {
      const nextLink = root.querySelector(
        ".pagination .item.pager.next a, .pagination .item.active + .item a, .pagination a.next"
      );
      this.nextUrl = null;
      if (nextLink) {
        const raw = nextLink.getAttribute("href") || "";
        const dataParams = nextLink.getAttribute("data-parameters") || "";
        if (raw && !raw.startsWith("#") && !raw.startsWith("javascript:")) {
          const resolved = resolveNextPageUrl(window.location.href, raw);
          if (resolved) this.nextUrl = resolved;
        } else if (dataParams) {
          const match = /(?:from_videos|from):(\d+)/.exec(dataParams);
          if (match) {
            const pageNum = parseInt(match[1], 10);
            this.nextUrl = this.computeNextPageUrlFromCurrent(window.location.href, pageNum);
          }
        }
      }
      if (!this.nextUrl) {
        this.nextUrl = this.computeNextPageUrlFromCurrent(window.location.href, this.currentPage + 1);
      }
      const nativePagination = document.querySelector(".pagination");
      if (nativePagination) {
        nativePagination.style.display = "none";
      }
    }
    computeNextPageUrlFromCurrent(currentUrlStr, nextPageNum) {
      try {
        const url = new URL(currentUrlStr);
        const pathname = url.pathname;
        if (pathname === "" || pathname === "/") {
          url.pathname = `/latest-updates/${nextPageNum}/`;
          return url.toString();
        }
        if (pathname.includes("/search/")) {
          url.searchParams.set("from_videos", String(nextPageNum));
          return url.toString();
        }
        if (/\/\d+\/?$/.test(pathname)) {
          url.pathname = pathname.replace(/\/\d+\/?$/, `/${nextPageNum}/`);
          return url.toString();
        }
        if (pathname.endsWith("/")) {
          url.pathname = `${pathname}${nextPageNum}/`;
          return url.toString();
        } else {
          url.pathname = `${pathname}/${nextPageNum}/`;
          return url.toString();
        }
      } catch {
        return null;
      }
    }
    mountStatusElements() {
      if (!this.container) return;
      if (!this.statusContainer) {
        this.statusContainer = document.createElement("div");
        this.statusContainer.className = "br34-autopager-container";
        this.container.after(this.statusContainer);
      }
      if (!this.sentinel) {
        this.sentinel = document.createElement("div");
        this.sentinel.className = "br34-sentinel";
        this.sentinel.style.height = "1px";
        this.statusContainer.before(this.sentinel);
      }
      this.updateStatusDisplay();
    }
    setupObserver() {
      var _a;
      if (typeof IntersectionObserver === "undefined") return;
      (_a = this.observer) == null ? void 0 : _a.disconnect();
      this.observer = new IntersectionObserver(
        (entries) => {
          if (this.isLoading || !this.nextUrl) return;
          for (const entry of entries) {
            if (entry.isIntersecting) {
              void this.loadNextPage();
              break;
            }
          }
        },
        {
          rootMargin: "1000px 0px",
          threshold: 0
        }
      );
      if (this.sentinel) {
        this.observer.observe(this.sentinel);
      }
    }
    setupScrollListener() {
      if (this.scrollHandler) return;
      let ticking = false;
      this.scrollHandler = () => {
        if (ticking) return;
        ticking = true;
        requestAnimationFrame(() => {
          ticking = false;
          if (this.isLoading || !this.nextUrl) return;
          const scrollY = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
          const winHeight = window.innerHeight || document.documentElement.clientHeight;
          const docHeight = Math.max(
            document.body.scrollHeight,
            document.documentElement.scrollHeight,
            document.body.offsetHeight,
            document.documentElement.offsetHeight
          );
          if (scrollY + winHeight >= docHeight - 1200) {
            void this.loadNextPage();
          }
        });
      };
      window.addEventListener("scroll", this.scrollHandler, { passive: true });
      window.addEventListener("touchmove", this.scrollHandler, { passive: true });
      window.addEventListener("resize", this.scrollHandler, { passive: true });
    }
    updateStatusDisplay() {
      if (!this.statusContainer) return;
      if (this.isLoading) {
        this.statusContainer.innerHTML = `
        <div class="br34-autopager-loading">
          <div class="br34-spinner"></div>
          <span>ACQUIRING SECTOR // PAGE ${this.currentPage + 1}...</span>
        </div>
      `;
      } else if (!this.nextUrl) {
        this.statusContainer.innerHTML = `
        <div class="br34-autopager-end">
          [ ARCHIVE EXHAUSTED // ${this.seenCardIds.size} UNITS INDEXED ]
        </div>
      `;
      } else {
        this.statusContainer.innerHTML = "";
      }
    }
    async loadNextPage() {
      var _a, _b, _c;
      if (this.isLoading || !this.nextUrl) return;
      this.container = findVideosContainer();
      if (!this.container) return;
      this.isLoading = true;
      this.updateStatusDisplay();
      const fetchUrl = this.nextUrl;
      try {
        const response = await fetch(fetchUrl, {
          credentials: "include",
          headers: {
            Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
          }
        });
        if (!response.ok) {
          throw new Error(`HTTP ${response.status} fetching ${fetchUrl}`);
        }
        const htmlText = await response.text();
        const doc = new DOMParser().parseFromString(htmlText, "text/html");
        cleanAds(doc);
        const newCardElements = Array.from(doc.querySelectorAll(".thumbs .item.thumb, .item.thumb"));
        const cardsToAppend = [];
        for (const card of newCardElements) {
          if (isAdCard(card)) continue;
          const id = card.dataset.videoCardId || ((_a = card.querySelector('a[href*="/video/"]')) == null ? void 0 : _a.getAttribute("href"));
          if (id && this.seenCardIds.has(id)) continue;
          if (id) this.seenCardIds.add(id);
          const img = card.querySelector("img");
          if (img) {
            const webp = img.getAttribute("data-webp") || img.dataset.webp;
            const original = img.getAttribute("data-original") || img.dataset.original;
            const resolvedSrc = webp || original;
            if (resolvedSrc) {
              img.src = resolvedSrc;
            }
            img.removeAttribute("data-original");
            img.removeAttribute("data-webp");
            img.classList.remove("lazy-load");
            img.loading = "lazy";
          }
          cardsToAppend.push(card);
        }
        if (cardsToAppend.length > 0) {
          this.isAppending = true;
          try {
            this.container.append(...cardsToAppend);
          } finally {
            setTimeout(() => {
              this.isAppending = false;
            }, 50);
          }
          this.onNewCards(cardsToAppend);
        }
        this.currentPage++;
        (_b = this.onPageLoaded) == null ? void 0 : _b.call(this, this.currentPage);
        const nextLink = doc.querySelector(
          ".pagination .item.pager.next a, .pagination .item.active + .item a, .pagination a.next"
        );
        this.nextUrl = null;
        if (nextLink) {
          const rawHref = nextLink.getAttribute("href") || "";
          const dataParams = nextLink.getAttribute("data-parameters") || "";
          if (rawHref && !rawHref.startsWith("#") && !rawHref.startsWith("javascript:")) {
            const resolved = resolveNextPageUrl(fetchUrl, rawHref);
            if (resolved) this.nextUrl = resolved;
          } else if (dataParams) {
            const match = /(?:from_videos|from):(\d+)/.exec(dataParams);
            if (match) {
              const pageNum = parseInt(match[1], 10);
              this.nextUrl = this.computeNextPageUrlFromCurrent(fetchUrl, pageNum);
            }
          }
        }
        if (!this.nextUrl && cardsToAppend.length > 0) {
          this.nextUrl = this.computeNextPageUrlFromCurrent(fetchUrl, this.currentPage + 1);
        }
      } catch (err) {
        console.error("[Better Rule34] AutoPager error:", err);
        if (this.statusContainer) {
          this.statusContainer.innerHTML = `
          <div class="br34-autopager-end" style="border-color: #ff0055; color: #ff0055;">
            [ ERROR FETCHING SECTOR // <button type="button" class="br34-load-more-btn" style="padding: 4px 10px; font-size: 10px; margin-left: 6px;">RETRY</button> ]
          </div>
        `;
          (_c = this.statusContainer.querySelector("button")) == null ? void 0 : _c.addEventListener("click", () => {
            void this.loadNextPage();
          });
        }
      } finally {
        this.isLoading = false;
        this.updateStatusDisplay();
        unclipBodyOverflow();
      }
    }
    destroy() {
      var _a, _b, _c;
      if (this.scrollHandler) {
        window.removeEventListener("scroll", this.scrollHandler);
        window.removeEventListener("touchmove", this.scrollHandler);
        window.removeEventListener("resize", this.scrollHandler);
        this.scrollHandler = null;
      }
      (_a = this.observer) == null ? void 0 : _a.disconnect();
      this.observer = null;
      (_b = this.statusContainer) == null ? void 0 : _b.remove();
      (_c = this.sentinel) == null ? void 0 : _c.remove();
      this.statusContainer = null;
      this.sentinel = null;
    }
  }
  const STORAGE_KEY = "better_rule34_settings";
  const VIEWS_STEPS = [0, 1e3, 5e3, 1e4, 25e3, 5e4, 1e5];
  class FilterBar {
    constructor(callbacks) {
      __publicField(this, "state");
      __publicField(this, "callbacks");
      __publicField(this, "fabElement");
      __publicField(this, "panelElement");
      __publicField(this, "isOpen", false);
      this.callbacks = callbacks;
      this.state = this.loadInitialState();
      this.fabElement = this.buildFab();
      this.panelElement = this.buildPanel();
      this.mount();
    }
    loadInitialState() {
      const urlFilters = parseCurrentUrlFilters(window.location.href);
      let savedSettings = {};
      try {
        const raw = localStorage.getItem(STORAGE_KEY);
        if (raw) savedSettings = JSON.parse(raw);
      } catch {
      }
      return {
        ...DEFAULT_FILTER,
        ...savedSettings,
        ...urlFilters
      };
    }
    saveSettings() {
      try {
        const toSave = {
          soundOnly: this.state.soundOnly,
          hdOnly: this.state.hdOnly,
          futaFilter: this.state.futaFilter,
          hideWatched: this.state.hideWatched,
          minRating: this.state.minRating,
          minViews: this.state.minViews,
          minYear: this.state.minYear,
          durationMinSeconds: this.state.durationMinSeconds
        };
        localStorage.setItem(STORAGE_KEY, JSON.stringify(toSave));
      } catch {
      }
    }
    getState() {
      return this.state;
    }
    mount() {
      document.body.append(this.fabElement, this.panelElement);
      this.bindEvents();
      this.updateBadge();
    }
    countActiveFilters() {
      let count = 0;
      if (this.state.query.trim()) count++;
      if (this.state.soundOnly) count++;
      if (this.state.hdOnly) count++;
      if (this.state.futaFilter !== "all") count++;
      if (this.state.hideWatched) count++;
      if (this.state.minRating > 0) count++;
      if (this.state.minViews > 0) count++;
      if (this.state.minYear > BASE_YEAR) count++;
      if (this.state.durationMinSeconds !== null && this.state.durationMinSeconds > 0) count++;
      return count;
    }
    updateBadge() {
      const count = this.countActiveFilters();
      let badge = this.fabElement.querySelector(".br34-fab-badge");
      if (count > 0) {
        if (!badge) {
          badge = document.createElement("span");
          badge.className = "br34-fab-badge";
          this.fabElement.append(badge);
        }
        badge.textContent = `[${count}]`;
      } else {
        badge == null ? void 0 : badge.remove();
      }
    }
    setCount(visible, total) {
      const counter = this.panelElement.querySelector(".br34-title-sub");
      if (counter) {
        counter.textContent = `UNITS: ${visible} / ${total}`;
      }
    }
    buildFab() {
      const fab = document.createElement("button");
      fab.type = "button";
      fab.className = "br34-fab";
      fab.title = "Open EROS Telemetry Filter";
      fab.setAttribute("aria-label", "Open EROS Telemetry Filter");
      fab.innerHTML = `
      <span class="br34-fab-dot"></span>
      <span>CTRL</span>
    `;
      return fab;
    }
    buildPanel() {
      const panel = document.createElement("div");
      panel.className = "br34-panel";
      const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
      const durMins = this.state.durationMinSeconds ? Math.round(this.state.durationMinSeconds / 60) : 0;
      panel.innerHTML = `
      <!-- Header: Title, Units & Close -->
      <div class="br34-panel-header">
        <div class="br34-title-row">
          <span class="br34-title">[ EROS // TELEMETRY ]</span>
          <span class="br34-title-sub">UNITS: LOADING...</span>
        </div>
        <button type="button" class="br34-panel-close" title="Close" aria-label="Close">[ X ]</button>
      </div>

      <!-- Body -->
      <div class="br34-panel-body">
        <!-- Live Keyword Search -->
        <div class="br34-search-box">
          <input type="text" class="br34-search-input" placeholder="SEARCH // TITLE KEYWORD" value="${this.state.query}" />
        </div>

        <!-- Tactile Sliders (Vertical Stack) -->
        <div class="br34-sliders-vertical">
          <!-- 1. Rating Slider -->
          <div class="br34-slider-card">
            <div class="br34-sect-title">
              <span>MIN RATING</span>
              <span class="br34-sect-val" id="br34-val-rating">${this.state.minRating > 0 ? `≥ ${this.state.minRating}%` : "ANY"}</span>
            </div>
            <input type="range" class="br34-range-slider" id="br34-slider-rating" min="0" max="100" step="5" value="${this.state.minRating}" />
          </div>

          <!-- 2. Views Slider -->
          <div class="br34-slider-card">
            <div class="br34-sect-title">
              <span>MIN VIEWS</span>
              <span class="br34-sect-val" id="br34-val-views">${this.formatViewsLabel(this.state.minViews)}</span>
            </div>
            <input type="range" class="br34-range-slider" id="br34-slider-views" min="0" max="6" step="1" value="${this.viewsToSliderStep(this.state.minViews)}" />
          </div>

          <!-- 3. Duration Slider -->
          <div class="br34-slider-card">
            <div class="br34-sect-title">
              <span>MIN DURATION</span>
              <span class="br34-sect-val" id="br34-val-dur">${durMins > 0 ? `≥ ${durMins}M` : "ANY"}</span>
            </div>
            <input type="range" class="br34-range-slider" id="br34-slider-dur" min="0" max="45" step="1" value="${durMins}" />
          </div>

          <!-- 4. Vintage Slider -->
          <div class="br34-slider-card">
            <div class="br34-sect-title">
              <span>MIN VINTAGE</span>
              <span class="br34-sect-val" id="br34-val-year">${this.state.minYear > BASE_YEAR ? `≥ ${this.state.minYear}` : "ALL"}</span>
            </div>
            <input type="range" class="br34-range-slider" id="br34-slider-year" min="${BASE_YEAR}" max="${currentYear}" step="1" value="${this.state.minYear}" />
          </div>
        </div>

        <!-- Tactile Toggles (Compact 2x2 grid) -->
        <div class="br34-grid-2x2">
          <button type="button" class="br34-chip ${this.state.soundOnly ? "active" : ""}" data-toggle="sound">
            SOUND
          </button>
          <button type="button" class="br34-chip ${this.state.hdOnly ? "active" : ""}" data-toggle="hd">
            HD ONLY
          </button>
          <button type="button" class="br34-chip ${this.state.futaFilter !== "all" ? "active-purple" : ""}" data-toggle="futa">
            ${this.getFutaLabel()}
          </button>
          <button type="button" class="br34-chip ${this.state.hideWatched ? "active" : ""}" data-toggle="watched">
            UNWATCHED
          </button>
        </div>
      </div>

      <!-- Footer: Reset -->
      <div class="br34-panel-footer">
        <button type="button" class="br34-btn-reset" id="br34-btn-reset">
          RESET ALL FILTERS
        </button>
      </div>
    `;
      return panel;
    }
    viewsToSliderStep(views) {
      const idx = VIEWS_STEPS.indexOf(views);
      return idx >= 0 ? idx : 0;
    }
    formatViewsLabel(views) {
      if (views <= 0) return "ANY";
      if (views >= 1e6) return `≥ ${views / 1e6}M`;
      if (views >= 1e3) return `≥ ${views / 1e3}K`;
      return `≥ ${views}`;
    }
    getFutaLabel() {
      switch (this.state.futaFilter) {
        case "hide":
          return "NO FUTA";
        case "only":
          return "FUTA ONLY";
        case "all":
        default:
          return "FUTA: ALL";
      }
    }
    cycleFuta() {
      if (this.state.futaFilter === "all") this.state.futaFilter = "hide";
      else if (this.state.futaFilter === "hide") this.state.futaFilter = "only";
      else this.state.futaFilter = "all";
      const btn = this.panelElement.querySelector('[data-toggle="futa"]');
      if (btn) {
        btn.textContent = this.getFutaLabel();
        btn.classList.toggle("active-purple", this.state.futaFilter !== "all");
      }
      this.saveSettings();
      this.updateBadge();
      this.callbacks.onFilterChange(this.state);
    }
    togglePanel(open) {
      this.isOpen = open !== void 0 ? open : !this.isOpen;
      this.panelElement.classList.toggle("open", this.isOpen);
      this.fabElement.classList.toggle("active", this.isOpen);
    }
    bindEvents() {
      var _a, _b;
      this.fabElement.addEventListener("click", (e) => {
        e.stopPropagation();
        this.togglePanel();
      });
      (_a = this.panelElement.querySelector(".br34-panel-close")) == null ? void 0 : _a.addEventListener("click", () => {
        this.togglePanel(false);
      });
      document.addEventListener("click", (e) => {
        if (this.isOpen && !this.panelElement.contains(e.target) && !this.fabElement.contains(e.target)) {
          this.togglePanel(false);
        }
      });
      let searchDebounce = 0;
      const searchInput = this.panelElement.querySelector(".br34-search-input");
      searchInput == null ? void 0 : searchInput.addEventListener("input", () => {
        window.clearTimeout(searchDebounce);
        searchDebounce = window.setTimeout(() => {
          this.state.query = searchInput.value;
          this.updateBadge();
          this.callbacks.onFilterChange(this.state);
        }, 80);
      });
      const ratingSlider = this.panelElement.querySelector("#br34-slider-rating");
      const ratingVal = this.panelElement.querySelector("#br34-val-rating");
      ratingSlider == null ? void 0 : ratingSlider.addEventListener("input", () => {
        const val = parseInt(ratingSlider.value, 10);
        this.state.minRating = val;
        if (ratingVal) {
          ratingVal.textContent = val > 0 ? `≥ ${val}%` : "ANY";
        }
        this.saveSettings();
        this.updateBadge();
        this.callbacks.onFilterChange(this.state);
      });
      const viewsSlider = this.panelElement.querySelector("#br34-slider-views");
      const viewsVal = this.panelElement.querySelector("#br34-val-views");
      viewsSlider == null ? void 0 : viewsSlider.addEventListener("input", () => {
        const step = parseInt(viewsSlider.value, 10);
        const val = VIEWS_STEPS[step] || 0;
        this.state.minViews = val;
        if (viewsVal) {
          viewsVal.textContent = this.formatViewsLabel(val);
        }
        this.saveSettings();
        this.updateBadge();
        this.callbacks.onFilterChange(this.state);
      });
      const durSlider = this.panelElement.querySelector("#br34-slider-dur");
      const durVal = this.panelElement.querySelector("#br34-val-dur");
      durSlider == null ? void 0 : durSlider.addEventListener("input", () => {
        const mins = parseInt(durSlider.value, 10);
        this.state.durationMinSeconds = mins > 0 ? mins * 60 : null;
        if (durVal) {
          durVal.textContent = mins > 0 ? `≥ ${mins}M` : "ANY";
        }
        this.saveSettings();
        this.updateBadge();
        this.callbacks.onFilterChange(this.state);
      });
      const yearSlider = this.panelElement.querySelector("#br34-slider-year");
      const yearVal = this.panelElement.querySelector("#br34-val-year");
      yearSlider == null ? void 0 : yearSlider.addEventListener("input", () => {
        const yr = parseInt(yearSlider.value, 10);
        this.state.minYear = yr;
        if (yearVal) {
          yearVal.textContent = yr > BASE_YEAR ? `≥ ${yr}` : "ALL";
        }
        this.saveSettings();
        this.updateBadge();
        this.callbacks.onFilterChange(this.state);
      });
      const soundBtn = this.panelElement.querySelector('[data-toggle="sound"]');
      soundBtn == null ? void 0 : soundBtn.addEventListener("click", () => {
        this.state.soundOnly = !this.state.soundOnly;
        soundBtn.classList.toggle("active", this.state.soundOnly);
        this.saveSettings();
        this.updateBadge();
        this.callbacks.onFilterChange(this.state);
      });
      const hdBtn = this.panelElement.querySelector('[data-toggle="hd"]');
      hdBtn == null ? void 0 : hdBtn.addEventListener("click", () => {
        this.state.hdOnly = !this.state.hdOnly;
        hdBtn.classList.toggle("active", this.state.hdOnly);
        this.saveSettings();
        this.updateBadge();
        this.callbacks.onFilterChange(this.state);
      });
      const futaBtn = this.panelElement.querySelector('[data-toggle="futa"]');
      futaBtn == null ? void 0 : futaBtn.addEventListener("click", () => {
        this.cycleFuta();
      });
      const watchedBtn = this.panelElement.querySelector('[data-toggle="watched"]');
      watchedBtn == null ? void 0 : watchedBtn.addEventListener("click", () => {
        this.state.hideWatched = !this.state.hideWatched;
        watchedBtn.classList.toggle("active", this.state.hideWatched);
        this.saveSettings();
        this.updateBadge();
        this.callbacks.onFilterChange(this.state);
      });
      (_b = this.panelElement.querySelector("#br34-btn-reset")) == null ? void 0 : _b.addEventListener("click", () => {
        this.state = {
          ...DEFAULT_FILTER
        };
        this.saveSettings();
        this.syncInputsWithState();
        this.updateBadge();
        this.callbacks.onFilterChange(this.state);
      });
    }
    syncInputsWithState() {
      const searchInput = this.panelElement.querySelector(".br34-search-input");
      if (searchInput) searchInput.value = this.state.query;
      const ratingSlider = this.panelElement.querySelector("#br34-slider-rating");
      const ratingVal = this.panelElement.querySelector("#br34-val-rating");
      if (ratingSlider) ratingSlider.value = String(this.state.minRating);
      if (ratingVal) ratingVal.textContent = this.state.minRating > 0 ? `≥ ${this.state.minRating}%` : "ANY";
      const viewsSlider = this.panelElement.querySelector("#br34-slider-views");
      const viewsVal = this.panelElement.querySelector("#br34-val-views");
      if (viewsSlider) viewsSlider.value = String(this.viewsToSliderStep(this.state.minViews));
      if (viewsVal) viewsVal.textContent = this.formatViewsLabel(this.state.minViews);
      const durSlider = this.panelElement.querySelector("#br34-slider-dur");
      const durVal = this.panelElement.querySelector("#br34-val-dur");
      const durMins = this.state.durationMinSeconds ? Math.round(this.state.durationMinSeconds / 60) : 0;
      if (durSlider) durSlider.value = String(durMins);
      if (durVal) durVal.textContent = durMins > 0 ? `≥ ${durMins}M` : "ANY";
      const yearSlider = this.panelElement.querySelector("#br34-slider-year");
      const yearVal = this.panelElement.querySelector("#br34-val-year");
      if (yearSlider) yearSlider.value = String(this.state.minYear);
      if (yearVal) yearVal.textContent = this.state.minYear > BASE_YEAR ? `≥ ${this.state.minYear}` : "ALL";
      const soundBtn = this.panelElement.querySelector('[data-toggle="sound"]');
      soundBtn == null ? void 0 : soundBtn.classList.toggle("active", this.state.soundOnly);
      const hdBtn = this.panelElement.querySelector('[data-toggle="hd"]');
      hdBtn == null ? void 0 : hdBtn.classList.toggle("active", this.state.hdOnly);
      const futaBtn = this.panelElement.querySelector('[data-toggle="futa"]');
      if (futaBtn) {
        futaBtn.textContent = this.getFutaLabel();
        futaBtn.classList.toggle("active-purple", this.state.futaFilter !== "all");
      }
      const watchedBtn = this.panelElement.querySelector('[data-toggle="watched"]');
      watchedBtn == null ? void 0 : watchedBtn.classList.toggle("active", this.state.hideWatched);
    }
    destroy() {
      this.fabElement.remove();
      this.panelElement.remove();
    }
  }
  const CSS = `
/* ==========================================================================
   Better Rule34Video - Industrial Brutalism + Erotic Latex Edition
   ========================================================================== */

/* --- SITE-WIDE THEME ENHANCEMENTS --- */
body {
  background: #08060a !important;
  color: #e5e5e9 !important;
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
}

/* Base substrate texture */
body::before {
  content: "";
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  z-index: 99999;
  background: repeating-linear-gradient(
    0deg,
    transparent,
    transparent 3px,
    rgba(255, 0, 85, 0.015) 3px,
    rgba(255, 0, 85, 0.015) 6px
  );
  opacity: 0.6;
}

/* Header & Container Cleanup */
.header {
  background: #0a070e !important;
  border-bottom: 1px solid #ff0055 !important;
  box-shadow: 0 4px 20px rgba(255, 0, 85, 0.15) !important;
}

.logo a svg {
  filter: drop-shadow(0 0 8px rgba(255, 0, 85, 0.6));
}

.headline .title {
  font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace !important;
  text-transform: uppercase !important;
  letter-spacing: 0.06em !important;
  color: #ffffff !important;
  border-left: 3px solid #ff0055;
  padding-left: 10px;
}

/* Card Enhancements - Brutalist Erotic Precision Frame */
.item.thumb {
  background: #0e0a14 !important;
  border: 1px solid rgba(255, 0, 85, 0.25) !important;
  border-radius: 4px !important;
  padding: 6px 6px 8px 6px !important;
  margin-bottom: 16px !important;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.7) !important;
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease !important;
  overflow: hidden !important;
  box-sizing: border-box !important;
}

.item.thumb:hover {
  transform: translateY(-3px) !important;
  border-color: #ff0055 !important;
  box-shadow: 0 8px 24px rgba(255, 0, 85, 0.35), 0 0 10px rgba(255, 0, 85, 0.15) !important;
}

.item.thumb a.th,
.item.thumb .th {
  display: block !important;
  text-decoration: none !important;
  color: inherit !important;
  width: 100% !important;
  outline: none !important;
}

.item.thumb .img.wrap_image,
.item.thumb .wrap_image,
.th .wrap_image {
  position: relative !important;
  border-radius: 2px !important;
  overflow: hidden !important;
  background: #050307 !important;
  border: 1px solid rgba(255, 255, 255, 0.06) !important;
  width: 100% !important;
  display: block !important;
}

.item.thumb .wrap_image img {
  width: 100% !important;
  height: auto !important;
  display: block !important;
}

.item.thumb .thumb_title {
  color: #f1f1f5 !important;
  font-family: 'Inter', system-ui, -apple-system, sans-serif !important;
  font-size: 12.5px !important;
  font-weight: 600 !important;
  line-height: 1.35 !important;
  height: 2.7em !important;
  max-height: 2.7em !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
  display: -webkit-box !important;
  -webkit-box-orient: vertical !important;
  -webkit-line-clamp: 2 !important;
  margin: 6px 0 4px 0 !important;
  padding: 0 2px !important;
  transition: color 0.15s ease !important;
}

.item.thumb:hover .thumb_title {
  color: #ff0055 !important;
}



/* Metadata Row (Clean 2-item layout: Rating on left, Views on right - NO overlap) */
.item.thumb .thumb_info,
.item.thumb .video-card-meta {
  display: flex !important;
  align-items: center !important;
  justify-content: space-between !important;
  gap: 6px !important;
  font-family: 'JetBrains Mono', monospace !important;
  font-size: 11px !important;
  color: #8c8998 !important;
  padding: 2px 2px 0 2px !important;
  margin: 0 !important;
  line-height: 1.2 !important;
  width: 100% !important;
  box-sizing: border-box !important;
}

/* Rating */
.item.thumb .thumb_info .rating,
.item.thumb .video-card-meta__rating {
  color: #00e676 !important;
  font-weight: 700 !important;
  display: inline-flex !important;
  align-items: center !important;
  gap: 3px !important;
  white-space: nowrap !important;
}

.item.thumb .thumb_info .rating svg,
.item.thumb .video-card-meta__rating svg {
  fill: #00e676 !important;
  width: 12px !important;
  height: 12px !important;
}

/* Views (Explicitly targeting video-views-count so comment count is never matched) */
.item.thumb .video-views-count,
.item.thumb .video-card-meta__views {
  color: #a5a2b3 !important;
  font-weight: 700 !important;
  display: inline-flex !important;
  align-items: center !important;
  gap: 3px !important;
  white-space: nowrap !important;
}

.item.thumb .video-views-count svg,
.item.thumb .video-card-meta__views svg {
  fill: #a5a2b3 !important;
  width: 12px !important;
  height: 12px !important;
}

/* HIDE "x min / hours ago" as requested */
.item.thumb .thumb_info .added,
.item.thumb .video-card-meta__date,
.item.thumb .added,
.thumb_info .added,
.video-card-meta__date {
  display: none !important;
  visibility: hidden !important;
  width: 0 !important;
  height: 0 !important;
  overflow: hidden !important;
  pointer-events: none !important;
}

/* HIDE comments completely */
.item.thumb .video-comments-count,
.item.thumb .video-card-meta__comments,
.item.thumb .comments,
.item.thumb .comments-count,
.item.thumb [class*="comment" i],
.item.thumb [title*="comment" i],
.item.thumb [aria-label*="comment" i],
.thumb_info .comments,
.thumb_info .video-comments-count,
#comments_box,
.video-comments-count {
  display: none !important;
  visibility: hidden !important;
  opacity: 0 !important;
  width: 0 !important;
  height: 0 !important;
  pointer-events: none !important;
}

/* Suppress native clumsy filter and ads */
.filters-panel,
.spot-thumb,
.spots,
.sidebar_ad_buttons,
.footer_spots,
ins.adsbyjuicy,
.item.thumb:has(header),
.item.thumb:has(iframe) {
  display: none !important;
}

.item.thumb[data-br34-hidden="true"] {
  display: none !important;
}

/* ==========================================================================
   Floating Brutalist FAB Button
   ========================================================================== */
.br34-fab {
  position: fixed;
  bottom: 24px;
  right: 24px;
  z-index: 2147483647;
  height: 40px;
  padding: 0 14px;
  border-radius: 8px;
  background: rgba(9, 6, 13, 0.95);
  border: 1px solid #ff0055;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.8), 0 0 14px rgba(255, 0, 85, 0.35);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: #ffffff;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-family: 'JetBrains Mono', monospace;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  outline: none;
  user-select: none;
  transition: all 0.15s cubic-bezier(0.16, 1, 0.3, 1);
}

.br34-fab:hover {
  background: #ff0055;
  color: #000000;
  box-shadow: 0 0 25px rgba(255, 0, 85, 0.8);
  transform: translateY(-2px);
}

.br34-fab:active {
  transform: translateY(0);
}

.br34-fab-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #ff0055;
  box-shadow: 0 0 8px #ff0055;
  display: inline-block;
  animation: br34-dot-blink 1.4s infinite ease-in-out;
}

.br34-fab:hover .br34-fab-dot {
  background: #000000;
  box-shadow: none;
}

@keyframes br34-dot-blink {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.3; transform: scale(0.7); }
}

.br34-fab-badge {
  background: #ff0055;
  color: #000000;
  font-size: 10px;
  font-weight: 900;
  padding: 1px 5px;
  border-radius: 4px;
  margin-left: 2px;
}

/* ==========================================================================
   Floating Brutalist Telemetry Modal (Vertical Console)
   ========================================================================== */
.br34-panel {
  position: fixed;
  bottom: 74px;
  right: 24px;
  z-index: 2147483646;
  width: 290px;
  max-width: calc(100vw - 32px);
  max-height: calc(100vh - 90px);
  background: rgba(10, 7, 14, 0.97);
  border: 1px solid rgba(255, 0, 85, 0.45);
  border-radius: 8px;
  box-shadow: 0 16px 50px rgba(0, 0, 0, 0.95), 0 0 25px rgba(255, 0, 85, 0.22);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  display: flex;
  flex-direction: column;
  color: #e5e5eb;
  font-family: 'JetBrains Mono', monospace;
  overflow: hidden;
  opacity: 0;
  transform: translateY(12px) scale(0.98);
  pointer-events: none;
  transition: opacity 0.2s cubic-bezier(0.16, 1, 0.3, 1), transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}

.br34-panel.open {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}

/* Header */
.br34-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  border-bottom: 1px solid rgba(255, 0, 85, 0.25);
  background: rgba(16, 10, 22, 0.95);
}

.br34-title-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}

.br34-title {
  font-size: 11.5px;
  font-weight: 900;
  letter-spacing: 0.08em;
  color: #ff0055;
  text-transform: uppercase;
}

.br34-title-sub {
  font-size: 9.5px;
  font-weight: 700;
  color: #9c97a8;
  letter-spacing: 0.05em;
}

.br34-panel-close {
  background: transparent;
  border: 1px solid rgba(255, 0, 85, 0.4);
  color: #ff0055;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10.5px;
  font-weight: 800;
  padding: 3px 8px;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.15s ease;
}

.br34-panel-close:hover {
  background: #ff0055;
  color: #000000;
  box-shadow: 0 0 10px rgba(255, 0, 85, 0.7);
}

/* Body */
.br34-panel-body {
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 11px;
  overflow-y: auto;
  max-height: calc(100vh - 165px);
}

/* Search input */
.br34-search-box {
  width: 100%;
  margin: 0;
}

.br34-search-input {
  width: 100%;
  height: 32px;
  background: rgba(5, 3, 7, 0.95);
  border: 1px solid rgba(255, 0, 85, 0.35);
  border-radius: 5px;
  color: #ffffff;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10.5px;
  padding: 6px 10px;
  outline: none;
  box-sizing: border-box;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.br34-search-input:focus {
  border-color: #ff0055;
  box-shadow: 0 0 10px rgba(255, 0, 85, 0.4);
}

/* Sliders Stack (Vertical orientation) */
.br34-sliders-vertical {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  margin: 0;
  padding: 0;
}

.br34-slider-card {
  display: flex;
  flex-direction: column;
}

.br34-sect-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 9.5px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #9893a6;
  margin-bottom: 2px;
}

.br34-sect-val {
  color: #ffffff;
  font-weight: 800;
  font-size: 9.5px;
  background: rgba(255, 0, 85, 0.15);
  border: 1px solid rgba(255, 0, 85, 0.6);
  border-radius: 3px;
  padding: 1px 6px;
}

.br34-range-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  border-radius: 2px;
  background: #1c1322;
  outline: none;
  cursor: pointer;
  margin: 3px 0;
  accent-color: #ff0055;
}

.br34-range-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #ff0055;
  border: 1px solid #ffffff;
  box-shadow: 0 0 8px #ff0055;
  cursor: pointer;
  transition: transform 0.1s ease;
}

.br34-range-slider::-webkit-slider-thumb:hover {
  transform: scale(1.15);
  background: #ffffff;
  border-color: #ff0055;
}

.br34-range-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #ff0055;
  border: 1px solid #ffffff;
  box-shadow: 0 0 8px #ff0055;
  cursor: pointer;
}

/* Grid 2x2 for Tactile Toggles */
.br34-grid-2x2 {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
  width: 100%;
  margin: 0;
}

/* Chips in grid */
.br34-chip {
  background: #0f0b17;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 5px;
  color: #a39eb0;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 7px 4px;
  text-align: center;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: all 0.14s ease;
  line-height: 1.2;
}

.br34-chip:hover {
  border-color: #ff0055;
  color: #ffffff;
}

.br34-chip.active {
  background: #ff0055;
  border-color: #ff0055;
  color: #000000;
  font-weight: 900;
  box-shadow: 0 0 10px rgba(255, 0, 85, 0.55);
}

.br34-chip.active-purple {
  background: #bf00ff;
  border-color: #bf00ff;
  color: #000000;
  font-weight: 900;
  box-shadow: 0 0 10px rgba(191, 0, 255, 0.55);
}

/* Footer Actions */
.br34-panel-footer {
  padding: 10px 14px;
  border-top: 1px solid rgba(255, 0, 85, 0.25);
  display: flex;
  background: rgba(16, 10, 22, 0.95);
}

.br34-btn-reset {
  width: 100%;
  background: rgba(18, 12, 24, 0.9);
  border: 1px solid rgba(255, 0, 85, 0.4);
  border-radius: 5px;
  color: #e5e5eb;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 8px 12px;
  cursor: pointer;
  transition: all 0.15s ease;
  text-align: center;
}

.br34-btn-reset:hover {
  background: #ff0055;
  border-color: #ff0055;
  color: #000000;
  box-shadow: 0 0 14px rgba(255, 0, 85, 0.6);
}

/* ==========================================================================
   Auto-Pager Telemetry Status
   ========================================================================== */
.br34-autopager-container {
  width: 100%;
  margin: 30px 0 50px;
  text-align: center;
  clear: both;
}

.br34-autopager-loading {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: rgba(9, 6, 13, 0.95);
  border: 1px solid #ff0055;
  border-radius: 6px;
  padding: 8px 18px;
  color: #ffffff;
  font-family: 'JetBrains Mono', monospace;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  box-shadow: 0 0 15px rgba(255, 0, 85, 0.35);
}

.br34-spinner {
  width: 12px;
  height: 12px;
  border: 2px solid rgba(255, 0, 85, 0.2);
  border-top-color: #ff0055;
  border-radius: 50%;
  animation: br34-spin 0.6s linear infinite;
}

@keyframes br34-spin {
  to { transform: rotate(360deg); }
}

.br34-autopager-end {
  display: inline-block;
  background: rgba(9, 6, 13, 0.95);
  border: 1px solid rgba(255, 0, 85, 0.3);
  border-radius: 6px;
  color: #8c8998;
  font-family: 'JetBrains Mono', monospace;
  padding: 8px 18px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.br34-load-more-btn {
  background: #ff0055;
  border: 1px solid #ff0055;
  border-radius: 6px;
  color: #000000;
  font-family: 'JetBrains Mono', monospace;
  padding: 8px 20px;
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: 0 0 15px rgba(255, 0, 85, 0.4);
  transition: all 0.15s ease;
}

.br34-load-more-btn:hover {
  background: #ffffff;
  border-color: #ffffff;
  color: #000000;
}

/* ==========================================================================
   Mobile Specific
   ========================================================================== */
@media (max-width: 480px) {
  .br34-fab {
    bottom: 16px;
    right: 16px;
    height: 38px;
    padding: 0 12px;
    font-size: 11px;
  }

  .br34-panel {
    right: 10px;
    bottom: 60px;
    width: calc(100vw - 20px);
    max-width: 300px;
  }

  .br34-panel-header {
    padding: 8px 12px;
  }

  .br34-panel-body {
    padding: 10px 12px;
    gap: 10px;
  }

  .br34-sliders-vertical {
    gap: 7px;
  }

  .br34-chip {
    font-size: 9.5px;
    padding: 6px 2px;
  }

  .br34-panel-footer {
    padding: 8px 12px;
  }
}
`;
  let managedCards = [];
  let filterBar = null;
  let autoPager = null;
  let currentFilter = null;
  function injectStyles() {
    if (document.getElementById("br34-styles")) return;
    const style = document.createElement("style");
    style.id = "br34-styles";
    style.textContent = CSS;
    (document.head || document.documentElement).append(style);
  }
  function scanCards() {
    const container = findVideosContainer();
    if (!container) return;
    cleanAds(container);
    const existingMap = /* @__PURE__ */ new Map();
    for (const c of managedCards) {
      existingMap.set(c.el, c);
    }
    const updatedCards = [];
    const cardElements = container.querySelectorAll(".item.thumb");
    for (const el of cardElements) {
      if (isAdCard(el)) {
        el.remove();
        continue;
      }
      const existing = existingMap.get(el);
      if (existing) {
        updatedCards.push(existing);
        continue;
      }
      const data = extractCardData(el);
      if (data) {
        updatedCards.push({ el, data });
      }
    }
    managedCards = updatedCards;
  }
  function applyFilter() {
    if (!currentFilter) return;
    let visibleCount = 0;
    for (const card of managedCards) {
      const isVisible = matchesClientFilter(card.data, currentFilter);
      card.el.dataset.br34Hidden = isVisible ? "false" : "true";
      if (isVisible) visibleCount++;
    }
    filterBar == null ? void 0 : filterBar.setCount(visibleCount, managedCards.length);
  }
  function boot() {
    injectStyles();
    unclipBodyOverflow();
    cleanAds();
    const container = findVideosContainer();
    if (!container) return;
    if (!filterBar || !filterBar.fabElement.isConnected) {
      filterBar == null ? void 0 : filterBar.destroy();
      filterBar = new FilterBar({
        onFilterChange: (state) => {
          currentFilter = state;
          applyFilter();
        }
      });
      currentFilter = filterBar.getState();
    }
    scanCards();
    if (!autoPager) {
      autoPager = new AutoPager({
        onNewCards: (newEls) => {
          for (const el of newEls) {
            const data = extractCardData(el);
            if (data) {
              managedCards.push({ el, data });
            }
          }
          applyFilter();
        },
        onPageLoaded: () => {
          cleanAds();
          unclipBodyOverflow();
        }
      });
      autoPager.init();
    }
    applyFilter();
  }
  let scheduledTimer = 0;
  function scheduleScan() {
    window.clearTimeout(scheduledTimer);
    scheduledTimer = window.setTimeout(() => {
      unclipBodyOverflow();
      cleanAds();
      scanCards();
      applyFilter();
    }, 200);
  }
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", () => {
      boot();
    });
  } else {
    boot();
  }
  window.addEventListener("load", () => {
    unclipBodyOverflow();
    boot();
  });
  const observer = new MutationObserver((mutations) => {
    var _a;
    if (autoPager == null ? void 0 : autoPager.getIsAppending()) return;
    let shouldScan = false;
    for (const m of mutations) {
      for (const node of m.addedNodes) {
        if (node instanceof HTMLElement) {
          if (node.classList.contains("item") || ((_a = node.querySelector) == null ? void 0 : _a.call(node, ".item.thumb"))) {
            shouldScan = true;
            break;
          }
        }
      }
      if (shouldScan) break;
    }
    if (shouldScan) {
      scheduleScan();
    }
  });
  observer.observe(document.body || document.documentElement, {
    childList: true,
    subtree: true
  });

})();