10/17/2025, 7:11:55 PM
// ==UserScript==
// @name Gridify X
// @namespace Violentmonkey Scripts
// @match https://wtr-lab.com/en/library*
// @grant none
// @version 1.3
// @author -
// @description 10/17/2025, 7:11:55 PM
// ==/UserScript==
(function () {
/* ---------- config ---------- */
const TARGET_CARD_WIDTH_PX = 121;
const AFTER_LOADMORE_RESTYLE_DELAY = 600;
const POLL_INTERVAL_MS = 800;
const POLL_TIMEOUT_MS = 30000;
const q = (s, r = document) => r.querySelector(s);
const qa = (s, r = document) => Array.from(r.querySelectorAll(s));
let compactMode = true;
// Global CSS rules designed to allow cards to stretch dynamically per row
const style = document.createElement('style');
style.textContent = `
/* --- DESKTOP/DEFAULT LAYOUT --- */
.compact-series-grid {
display: grid !important;
grid-template-columns: repeat(auto-fill, ${TARGET_CARD_WIDTH_PX}px) !important;
/* 💡 FIXED: Removed grid-auto-rows: 1fr so rows calculate heights independently */
gap: 16px 10px !important;
justify-content: center !important;
width: 100% !important;
max-width: 1200px !important;
margin: 0 auto !important;
padding: 0 8px !important;
}
/* The outer container wrapper */
.compact-card-fixed {
width: ${TARGET_CARD_WIDTH_PX}px !important;
height: 100% !important; /* 💡 THE CURE: Grid items with height: 100% automatically stretch to match their specific row's height */
min-height: 285px !important;
max-width: ${TARGET_CARD_WIDTH_PX}px !important;
margin: 0 !important;
padding: 0 !important;
box-sizing: border-box !important;
border: 1px solid #222 !important;
background: #111 !important;
display: flex !important;
flex-direction: column !important;
overflow: hidden !important;
position: relative !important;
}
/* Outer wrapper containing cover image + text details */
.compact-card-fixed .serie-item {
display: flex !important;
flex-direction: column !important;
border: none !important;
box-shadow: none !important;
width: 100% !important;
max-width: 100% !important;
padding: 0 !important;
margin: 0 !important;
flex-grow: 1 !important;
}
/* 💡 FIXED: Adjusted the image frame dimensions to maintain natural light novel/manga vertical proportions */
.compact-card-fixed .image-wrap,
.compact-card-fixed .image-wrap img {
width: 100% !important;
height: 165px !important; /* Increased slightly from 140px to fit rectangular standard cover ratios */
object-fit: cover !important; /* Keeps background filled cleanly without distortions */
object-position: center !important;
flex-shrink: 0 !important;
}
/* Fluid flex layout inside the info block */
.compact-card-fixed .detail-wrap {
padding: 6px 5px !important;
display: flex !important;
flex-direction: column !important;
height: auto !important;
max-height: none !important;
flex-grow: 1 !important;
gap: 6px !important;
}
/* Dedicated layout container section */
.compact-title-container {
display: block !important;
width: 100% !important;
height: auto !important;
max-height: none !important;
overflow: visible !important;
margin-bottom: auto !important; /* Pushes the progress section down layout-wise */
}
/* Overwrite framework capping constraints to force absolute block space expansion */
.compact-card-fixed .compact-title-container a.title {
display: block !important;
font-size: 11px !important;
font-weight: 600 !important;
line-height: 1.35 !important;
color: #fff !important;
white-space: normal !important;
word-break: break-word !important;
overflow: visible !important;
height: auto !important;
max-height: none !important;
text-overflow: clip !important;
-webkit-line-clamp: unset !important;
}
/* Progress details sub-row wrapper block */
.compact-card-fixed .details {
margin-top: auto !important;
padding: 0 !important;
width: 100% !important;
height: auto !important;
flex-shrink: 0 !important;
}
.compact-card-fixed .info-line {
font-size: 10px !important;
color: #aaa !important;
display: flex !important;
flex-direction: column !important;
gap: 2px !important;
}
.compact-card-fixed .info-line span:first-child {
display: none !important;
}
/* Stacked action buttons row container locked to the card floor */
.compact-card-fixed div.flex.w-full.h-9.border-t-border,
.compact-card-fixed .border-t {
margin-top: 4px !important;
height: 48px !important;
min-height: 48px !important;
max-height: 48px !important;
width: 100% !important;
border-top: 1px solid #222 !important;
display: flex !important;
flex-direction: column !important;
flex-shrink: 0 !important;
}
.compact-card-fixed div.flex.w-full.h-9.border-t-border > div {
height: 24px !important;
min-height: 24px !important;
width: 100% !important;
border-left: none !important;
}
.compact-card-fixed div.flex.w-full.h-9.border-t-border > div + div {
border-top: 1px solid #222 !important;
}
/* Fixed button styling constraints */
.compact-card-fixed .border-t > div > a,
.compact-card-fixed .border-t > div > button,
.compact-card-fixed .border-t > a,
.compact-card-fixed .border-t > button {
font-size: 10px !important;
padding: 0 4px !important;
height: 24px !important;
width: 100% !important;
line-height: 24px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
border-radius: 0 !important;
}
/* Hide inline SVGs on native collection buttons */
.compact-card-fixed .border-t > div > a svg,
.compact-card-fixed .border-t > div > button svg,
.compact-card-fixed .border-t > a svg,
.compact-card-fixed .border-t > button svg {
display: none !important;
}
/* Forces the dynamic collections popup to grow past the 121px anchor constraint */
div[data-slot="dropdown-menu-content"] {
width: auto !important;
min-width: 160px !important;
max-width: 240px !important;
}
/* Floating collection badge counter asset mapping overlay */
.compact-card-fixed .rounded-full.bg-secondary,
.compact-card-fixed [class*="text-secondary-foreground"] {
position: absolute !important;
top: 6px !important;
left: 6px !important;
z-index: 10 !important;
background: rgba(30, 41, 59, 0.85) !important;
color: #38bdf8 !important;
font-weight: 700 !important;
font-size: 10px !important;
padding: 2px 5px !important;
border: 1px solid rgba(255,255,255,0.1) !important;
margin: 0 !important;
}
/* --- MOBILE OVERRIDES (Phones) --- */
@media (max-width: 450px) {
.compact-series-grid {
grid-template-columns: repeat(3, 1fr) !important;
/* 💡 FIXED: Removed grid-auto-rows here as well */
gap: 14px 4px !important;
padding: 0 4px !important;
}
.compact-card-fixed {
width: 100% !important;
max-width: 100% !important;
height: 100% !important;
min-height: 285px !important;
}
/* 💡 FIXED MOBILE IMAGE FRAME RATIO */
.compact-card-fixed .image-wrap,
.compact-card-fixed .image-wrap img {
height: 135px !important;
}
.compact-card-fixed .detail-wrap {
padding: 6px 4px !important;
display: flex !important;
flex-direction: column !important;
max-height: none !important;
gap: 6px !important;
}
.compact-card-fixed .compact-title-container a.title {
font-size: 13px !important;
line-height: 1.4 !important;
}
.compact-card-fixed .border-t > div > a,
.compact-card-fixed .border-t > div > button,
.compact-card-fixed .border-t > a,
.compact-card-fixed .border-t > button {
font-size: 13px !important;
padding: 0 2px !important;
}
}
`;
document.head.appendChild(style);
function transformItem(item) {
if (!item) return;
if (!compactMode && item.dataset.compact === '1') {
const titleDiv = item.querySelector('.compact-title-container');
const origTitle = item.querySelector('a.title');
const detailWrap = item.querySelector('.detail-wrap');
if (titleDiv && origTitle && detailWrap) {
detailWrap.insertBefore(origTitle, titleDiv);
titleDiv.remove();
}
item.className = item.dataset.oldClass || '';
item.dataset.compact = '';
return;
}
// Live node translocation
if (!item.querySelector('.compact-title-container')) {
const origTitle = item.querySelector('a.title');
const detailWrap = item.querySelector('.detail-wrap');
if (origTitle && detailWrap) {
const titleDiv = document.createElement('div');
titleDiv.className = 'compact-title-container';
detailWrap.insertBefore(titleDiv, origTitle);
titleDiv.appendChild(origTitle);
}
}
if (item.dataset.compact === '1') return;
if (!item.dataset.oldClass) item.dataset.oldClass = item.className;
item.dataset.compact = '1';
item.className = "compact-card-fixed";
}
function ensureGridContainer(exampleItem) {
if (!exampleItem) return null;
const listParent = exampleItem.parentElement;
if (listParent.classList.contains('compact-series-grid')) return listParent;
let grid = document.querySelector('.compact-series-grid');
if (!grid) {
grid = document.createElement('div');
grid.className = 'compact-series-grid';
listParent.insertBefore(grid, listParent.firstChild);
}
return grid;
}
function restyleAll() {
const items = document.querySelectorAll('div.shrink-0.rounded-md.bg-card, .compact-card-fixed');
if (!items.length) return;
const grid = ensureGridContainer(items[0]);
if (!grid) return;
items.forEach(item => {
transformItem(item);
if (compactMode && item.parentElement !== grid) {
grid.appendChild(item);
}
});
}
function installFolderSwitchListener() {
document.addEventListener('click', (e) => {
if (e.target.closest('.folder-btn')) {
setTimeout(restyleAll, 500);
setTimeout(restyleAll, 1200);
}
}, { passive: true });
}
function initCompactGrid() {
compactMode = localStorage.getItem('compactMode') === '0' ? false : true;
restyleAll();
window.addEventListener('resize', restyleAll);
}
function waitForLibraryAndInit() {
const start = Date.now();
const interval = setInterval(() => {
const found = document.querySelector('div.shrink-0.rounded-md.bg-card');
if (found) {
clearInterval(interval);
initCompactGrid();
setTimeout(restyleAll, 500);
} else if (Date.now() - start > POLL_TIMEOUT_MS) {
clearInterval(interval);
}
}, POLL_INTERVAL_MS);
}
function installLoadMoreCatchAll() {
document.addEventListener('click', (e) => {
const lm = e.target.closest('.load-more button, button[data-slot="load-more"]');
if (lm) {
setTimeout(restyleAll, AFTER_LOADMORE_RESTYLE_DELAY);
setTimeout(restyleAll, AFTER_LOADMORE_RESTYLE_DELAY + 800);
}
}, { passive: true });
}
let lastLoadTime = 0;
function autoClickLoadMoreOnScroll() {
window.addEventListener('scroll', () => {
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const windowHeight = window.innerHeight;
const docHeight = document.documentElement.scrollHeight;
const scrollPercent = (scrollTop + windowHeight) / docHeight;
if (scrollPercent > 0.7 && Date.now() - lastLoadTime > 1500) {
const loadMoreBtn = document.querySelector('.load-more button, button[data-slot="load-more"], .d-flex.w-100.load-more button');
if (loadMoreBtn) {
loadMoreBtn.click();
lastLoadTime = Date.now();
}
}
}, { passive: true });
}
autoClickLoadMoreOnScroll();
waitForLibraryAndInit();
installLoadMoreCatchAll();
installFolderSwitchListener();
window.reflowCompactLibrary = function () { restyleAll(); };
})();