Gridify X

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

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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