Sleazy Fork is available in English.
Streamlined filter bar, instant client search & filtering, ad cleaner, and seamless auto next page infinite scroll for Rule34Video.
// ==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
});
})();