Gridify X

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

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