Last active
May 9, 2026 03:18
-
-
Save dougkusanagi/aa3b2bb8b7585f5386be8eb228d21691 to your computer and use it in GitHub Desktop.
anilist-layout-fix-tampermonkey-extension
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name AniList Better Cards | |
| // @namespace https://github.com/dougkusanagi | |
| // @version 4.0.0 | |
| // @description Melhora os cards da animelist: título 3 linhas, ep+botão acima do título, botões sempre visíveis | |
| // @match https://anilist.co/user/*/animelist* | |
| // @run-at document-idle | |
| // @grant GM_addStyle | |
| // ==/UserScript== | |
| /** | |
| * Estrutura real do card (obtida inspecionando o DOM): | |
| * | |
| * <div class="entry-card row"> | |
| * <div class="cover"> | |
| * <div class="edit">...</div> ← botão "..." está DENTRO de .cover | |
| * <div class="image" style="..."></div> | |
| * </div> | |
| * <div class="title" title="Nome"> ← filho direto de .entry-card | |
| * <a href="...">Nome do anime</a> | |
| * </div> | |
| * <div class="score">...</div> | |
| * <div class="progress"> ← filho direto de .entry-card | |
| * 5/12 | |
| * <span class="plus-progress">+</span> ← SPAN dentro de .progress, não separado | |
| * </div> | |
| * <span class="release-status RELEASING"></span> | |
| * <span class="notes">...</span> | |
| * </div> | |
| */ | |
| (function () { | |
| 'use strict'; | |
| /* ───────────────────────────────────────────────────────────────────────── | |
| CSS — apenas o que não depende de medição de layout | |
| ───────────────────────────────────────────────────────────────────────── */ | |
| GM_addStyle(` | |
| /* Ponto de status não cortado */ | |
| .entry-card { | |
| overflow: visible !important; | |
| } | |
| /* ── Botão "..." (está dentro de .cover) — sempre visível ── */ | |
| .entry-card .cover .edit { | |
| opacity: 1 !important; | |
| visibility: visible !important; | |
| } | |
| .medialist.cards .entry-card .title { | |
| padding-left: 10px; | |
| padding-right: 10px; | |
| padding-bottom: 8px; | |
| padding-top: 8px; | |
| /* height: 64px; */ | |
| } | |
| /* ── Título: 1rem, máximo 3 linhas ── */ | |
| /* O texto real fica na <a> dentro de .title */ | |
| .entry-card .title a { | |
| font-size: 1rem !important; | |
| line-height: 1.3 !important; | |
| display: -webkit-box !important; | |
| -webkit-line-clamp: 3 !important; | |
| -webkit-box-orient: vertical !important; | |
| overflow: hidden !important; | |
| white-space: normal !important; | |
| text-overflow: ellipsis !important; | |
| /* max-height como fallback para navegadores sem suporte ao clamp */ | |
| max-height: calc(1rem * 1.3 * 3) !important; | |
| } | |
| /* ── Progress: texto branco, bold, sombra de legenda ── */ | |
| /* .progress contém o texto "5/12" + a <span class="plus-progress"> */ | |
| .entry-card .progress { | |
| color: #fff !important; | |
| font-weight: 700 !important; | |
| font-size: 0.9rem !important; | |
| text-shadow: | |
| 0 1px 2px rgba(0,0,0,1), | |
| 0 2px 6px rgba(0,0,0,.9) !important; | |
| /* Alinha texto e botão + na mesma linha */ | |
| display: flex !important; | |
| align-items: center !important; | |
| gap: 6px !important; | |
| } | |
| /* Garante que filhos do progress também herdam a cor/sombra | |
| (cobre caso ep=0 onde só aparece o total com estrutura diferente) */ | |
| .entry-card .progress span:not(.plus-progress), | |
| .entry-card .progress div, | |
| .entry-card .progress a { | |
| color: #fff !important; | |
| font-weight: 700 !important; | |
| text-shadow: | |
| 0 1px 2px rgba(0,0,0,1), | |
| 0 2px 6px rgba(0,0,0,.9) !important; | |
| } | |
| /* ── Botão + (span dentro de .progress) ── */ | |
| .entry-card .progress .plus-progress { | |
| display: inline-flex !important; | |
| align-items: center !important; | |
| justify-content: center !important; | |
| flex-shrink: 0 !important; | |
| background: rgba(0, 0, 0, 0.3) !important; | |
| border-radius: 6px !important; | |
| border: 2px solid #fff !important; | |
| color: #fff !important; | |
| font-weight: 700 !important; | |
| font-size: 1rem !important; | |
| line-height: 1 !important; | |
| width: 28px !important; | |
| height: 28px !important; | |
| min-width: 28px !important; | |
| min-height: 28px !important; | |
| opacity: 1 !important; | |
| visibility: visible !important; | |
| cursor: pointer !important; | |
| text-shadow: none !important; | |
| transition: filter .15s ease !important; | |
| } | |
| .entry-card .progress .plus-progress:hover { | |
| filter: brightness(1.15) !important; | |
| } | |
| /* Mobile: always visible */ | |
| @media (hover: none) { | |
| .entry-card .cover .edit { | |
| opacity: 1 !important; | |
| visibility: visible !important; | |
| } | |
| } | |
| `); | |
| /* ───────────────────────────────────────────────────────────────────────── | |
| JavaScript — posiciona .progress acima de .title medindo as alturas reais | |
| ───────────────────────────────────────────────────────────────────────── */ | |
| function processCard(card) { | |
| if (card.dataset.bcDone === '1') return; | |
| const titleEl = card.querySelector('.title'); | |
| const progressEl = card.querySelector('.progress'); | |
| const plusEl = card.querySelector('.progress .plus-progress'); | |
| const editEl = card.querySelector('.cover .edit'); | |
| // Aguarda elementos aparecerem (SPA pode demorar) | |
| if (!titleEl || !progressEl) return; | |
| card.dataset.bcDone = '1'; | |
| /* ── Tamanho do botão + igual ao botão "..." ── */ | |
| if (editEl && plusEl) { | |
| const editW = editEl.offsetWidth || editEl.getBoundingClientRect().width; | |
| const editH = editEl.offsetHeight || editEl.getBoundingClientRect().height; | |
| const sz = Math.round(Math.max(editW || 28, editH || 28, 24)); | |
| plusEl.style.width = sz + 'px'; | |
| plusEl.style.height = sz + 'px'; | |
| plusEl.style.minWidth = sz + 'px'; | |
| plusEl.style.minHeight = sz + 'px'; | |
| } | |
| /* ── Posiciona .progress logo acima de .title ── | |
| * | |
| * AniList posiciona .title e .progress com position:absolute | |
| * em relação a .entry-card (position:relative). | |
| * | |
| * Estratégia: ler o bottom computado de .title e somá-lo | |
| * à altura real de .title + um pequeno gap para definir | |
| * o bottom do .progress. | |
| */ | |
| const titleStyle = window.getComputedStyle(titleEl); | |
| const rawBottom = titleStyle.bottom; | |
| // Se .title tiver position:absolute com bottom definido, usamos esse valor. | |
| // Caso contrário, fallback para a posição relativa ao card. | |
| let titleBottomPx = 0; | |
| if (rawBottom !== 'auto' && rawBottom !== '') { | |
| titleBottomPx = parseFloat(rawBottom) || 0; | |
| } else { | |
| // Fallback: mede via bounding rect | |
| const cardRect = card.getBoundingClientRect(); | |
| const titleRect = titleEl.getBoundingClientRect(); | |
| titleBottomPx = cardRect.bottom - titleRect.bottom; | |
| } | |
| const titleHeight = titleEl.offsetHeight; | |
| const progressBottom = titleBottomPx + titleHeight + -8; // 4px de gap | |
| progressEl.style.position = 'absolute'; | |
| progressEl.style.bottom = progressBottom + 'px'; | |
| progressEl.style.left = 'auto'; | |
| progressEl.style.right = 'auto'; | |
| progressEl.style.top = 'auto'; | |
| } | |
| function processAll() { | |
| document.querySelectorAll('.entry-card').forEach(processCard); | |
| } | |
| /* Observa mudanças no DOM (SPA / scroll infinito) */ | |
| new MutationObserver(processAll).observe(document.body, { | |
| childList: true, | |
| subtree: true, | |
| }); | |
| /* Passadas com delay para garantir que o Vue terminou de renderizar */ | |
| setTimeout(processAll, 300); | |
| setTimeout(processAll, 900); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment