Sleazy Fork is available in English.
Highlight JavDB movie cards by score and number of ratings.
// ==UserScript==
// @name JavDB Film List Enhancer
// @namespace http://tampermonkey.net/
// @version 1.5.0
// @description Highlight JavDB movie cards by score and number of ratings.
// @author JHT
// @match https://javdb.com/*
// @match https://*.javdb.com/*
// @include https://javdb*.com/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
const PROCESSED_ATTR = 'data-jdb-rating-highlighted';
const REVIEW_CAP = 100;
const COLOR = [240, 0, 144];
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
function calculateBackgroundColor(score, ratedBy) {
const safeScore = clamp(Number(score) || 0, 0, 5);
const safeRatedBy = Math.max(Number(ratedBy) || 0, 0);
const scoreTransformed = Math.sqrt(safeScore / 5);
const ratedByNormalized = clamp(safeRatedBy / REVIEW_CAP, 0, 1);
const alpha = clamp(scoreTransformed * ratedByNormalized, 0, 1);
return `rgba(${COLOR[0]}, ${COLOR[1]}, ${COLOR[2]}, ${alpha.toFixed(3)})`;
}
function parseRating(text) {
const normalized = String(text || '')
.replace(/\u00a0/g, ' ')
.replace(/,/g, '')
.replace(/\s+/g, ' ')
.trim();
const zhMatch = normalized.match(/([0-5](?:\.\d+)?)\s*分\s*,?\s*由\s*(\d+)\s*人\s*(?:評價|评价)/);
if (zhMatch) {
return {
score: Number.parseFloat(zhMatch[1]),
ratedBy: Number.parseInt(zhMatch[2], 10),
};
}
const enMatch = normalized.match(/([0-5](?:\.\d+)?)\s*(?:\/\s*5)?\s*,?\s*(?:rated\s+by|by)\s*(\d+)\s*(?:people|users?|reviews?|ratings?)/i);
if (enMatch) {
return {
score: Number.parseFloat(enMatch[1]),
ratedBy: Number.parseInt(enMatch[2], 10),
};
}
return null;
}
function findMovieCard(scoreElement) {
const item = scoreElement.closest('.item');
if (!item) return null;
return (
item.querySelector('a.box[href]') ||
item.querySelector('a[href*="/v/"]') ||
item.querySelector('a[href]')
);
}
function highlightCard(card, rating) {
const backgroundColor = calculateBackgroundColor(rating.score, rating.ratedBy);
card.style.setProperty('background-color', backgroundColor, 'important');
card.style.setProperty('box-shadow', `0 0 0 2px ${backgroundColor} inset`, 'important');
card.dataset.jdbRatingScore = String(rating.score);
card.dataset.jdbRatingCount = String(rating.ratedBy);
card.setAttribute(PROCESSED_ATTR, '1');
}
function enhance(root = document) {
const scope = root instanceof Element ? root : document;
const scoreElements = scope.querySelectorAll('.movie-list .item .score .value, .item .score .value');
scoreElements.forEach((scoreElement) => {
const card = findMovieCard(scoreElement);
if (!card || card.getAttribute(PROCESSED_ATTR) === '1') return;
const rating = parseRating(scoreElement.textContent);
if (!rating) return;
highlightCard(card, rating);
});
}
function scheduleEnhance() {
window.clearTimeout(scheduleEnhance.timer);
scheduleEnhance.timer = window.setTimeout(() => enhance(), 80);
}
enhance();
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type !== 'childList' || mutation.addedNodes.length === 0) continue;
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
scheduleEnhance();
return;
}
}
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();