Gridify X

10/17/2025, 7:11:55 PM

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==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(); };
})();