Gridify X

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

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

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