// ==UserScript== // @name AniList Tier Labels // @namespace http://tampermonkey.net/ // @version 2.3 // @description Adds a tier badge next to ratings on Anilist, including Mean Score. Supports different scoring systems and colors the score number according to the tier. // @match *://anilist.co/* // @grant none // @downloadURL none // ==/UserScript== (function () { 'use strict'; // Tier definitions with colors const tiers = [ { min: 95, max: 100, label: 'S+', color: '#FFD700', textColor: '#000000' }, { min: 85, max: 94.9, label: 'S', color: '#ff7f00', textColor: '#FFFFFF' }, { min: 75, max: 84.9, label: 'A', color: '#aa00ff', textColor: '#FFFFFF' }, { min: 65, max: 74.9, label: 'B', color: '#007fff', textColor: '#FFFFFF' }, { min: 55, max: 64.9, label: 'C', color: '#00aa00', textColor: '#FFFFFF' }, { min: 41, max: 54.9, label: 'D', color: '#aaaaaa', textColor: '#FFFFFF' }, { min: 0, max: 40.9, label: 'F', color: '#666666', textColor: '#FFFFFF' } ]; function getTier(rating) { if (rating === 0) return null; // Skip if the score is 0 return tiers.find(tier => rating >= tier.min && rating <= tier.max) || null; } function createBadge(tier, isBlockView = false) { let badge = document.createElement('span'); badge.textContent = tier.label; badge.style.cssText = ` background-color: ${tier.color}; color: ${tier.textColor}; font-size: ${isBlockView ? '10px' : '12px'}; font-weight: bold; padding: ${isBlockView ? '1px 4px' : '2px 6px'}; border-radius: 4px; display: inline-block; margin-left: 5px; vertical-align: middle; white-space: nowrap; `; return badge; } function getScoreSystem() { const container = document.querySelector('.content.container'); if (container) { if (container.querySelector('.medialist.table.POINT_100')) return 'POINT_100'; if (container.querySelector('.medialist.table.POINT_10_DECIMAL')) return 'POINT_10'; if (container.querySelector('.medialist.table.POINT_5')) return 'POINT_5'; } return 'UNKNOWN'; } function normalizeScore(score, scoreSystem, isPercentage = false) { const numericScore = parseFloat(score); if (isNaN(numericScore)) return null; // If it's already a 0-100 percentage, just return if (isPercentage) { return numericScore; } // Otherwise, convert based on the scoring system switch (scoreSystem) { case 'POINT_100': return numericScore; case 'POINT_10': return numericScore * 10; case 'POINT_5': return numericScore * 20; default: return numericScore * 10; } } function processScoreElement(el, isPercentage = false, isBlockView = false) { if (el.dataset.tierModified) return; el.dataset.tierModified = "true"; const scoreSystem = getScoreSystem(); let ratingText = el.getAttribute('score') || el.innerText.trim().replace('%', ''); let normalizedRating = normalizeScore(ratingText, scoreSystem, isPercentage); if (normalizedRating === null) return; let tier = getTier(normalizedRating); if (tier) { // Build a small inline container const container = document.createElement('div'); container.style.cssText = ` display: inline-flex; align-items: center; gap: 4px; ${isBlockView ? 'background-color: rgba(0, 0, 0, 0.5); padding: 2px 6px; border-radius: 4px; overflow: hidden;' : ''} `; const scoreEl = document.createElement('span'); scoreEl.textContent = isPercentage ? `${ratingText}%` : ratingText; scoreEl.style.color = tier.color; // Color the score number with the tier's color container.appendChild(scoreEl); container.appendChild(createBadge(tier, isBlockView)); // Clear out the original text and append the container el.textContent = ''; el.appendChild(container); } } function addTierIndicators() { // 1) List View (Decimal Scores) document.querySelectorAll('.score:not(.media-card .score)').forEach(el => { processScoreElement(el, false, false); }); // 2) Block View (Media Cards) document.querySelectorAll('.entry-card .score').forEach(el => { processScoreElement(el, false, true); }); // 3) Average / Mean Score (Profile Stats, etc.) document.querySelectorAll('.data-set').forEach(dataSet => { const label = dataSet.querySelector('.type'); const value = dataSet.querySelector('.value'); if ( label && value && !value.dataset.tierModified && (label.innerText.includes('Average Score') || label.innerText.includes('Mean Score')) ) { processScoreElement(value, true, false); } }); // 4) Top 100 View document.querySelectorAll('.row.score').forEach(row => { // We'll look for a .percentage that isn't purely .popularity const percentageEl = row.querySelector('.percentage'); if (!percentageEl || percentageEl.classList.contains('popularity') || percentageEl.dataset.tierModified) { return; } percentageEl.dataset.tierModified = "true"; // The "X users" sub-row is typically a child: