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