Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save dougkusanagi/aa3b2bb8b7585f5386be8eb228d21691 to your computer and use it in GitHub Desktop.

Select an option

Save dougkusanagi/aa3b2bb8b7585f5386be8eb228d21691 to your computer and use it in GitHub Desktop.
anilist-layout-fix-tampermonkey-extension
// ==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