JavLibrary - Enlarge Thumbnails

Replace DMM thumbnails with their respective full cover images

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

// ==UserScript==
// @license MIT
// @name         JavLibrary - Enlarge Thumbnails
// @namespace    https://sleazyfork.org/en/scripts/584159-javlibrary-enlarge-thumbnails
// @version      1.5
// @description  Replace DMM thumbnails with their respective full cover images
// @match        *://www.javlibrary.com/*
// @match        *://javlibrary.com/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {
  'use strict';

  // ====== CONFIG ======
  // Minimum card width (px). Number of columns is calculated automatically based on screen width.
  // Larger value = bigger cards & fewer columns, and vice versa.
  const MIN_CARD_WIDTH = 560;
  const GAP = 12; // gap between cards (px)

  // Card text font size. Leave null to AUTO-match the sidebar's font-size.
  // Set manually (e.g. '15px') to force a specific size.
  const FONT_SIZE = null;
  const FONT_SIZE_FALLBACK = '16px'; // used as fallback if sidebar font cannot be detected

  // Maximum number of title lines shown (rest is clipped with ellipsis).
  const TITLE_LINES = 2;
  // ====================

  /* ---------- 1. Swap image ps.jpg -> pl.jpg ---------- */
  function upgradeImg(img) {
    if (img.dataset.jlUpgraded) return;

    const src = img.currentSrc || img.src || img.getAttribute('src') || '';
    if (!/pics\.dmm\.co\.jp/i.test(src)) return;

    const upgraded = src.replace(/ps(\.jpe?g)(\?.*)?$/i, 'pl$1$2');
    if (upgraded === src) return; // not a ps.jpg thumbnail, skip

    img.dataset.jlUpgraded = '1';
    img.dataset.jlOriginal = src;

    img.removeAttribute('width');
    img.removeAttribute('height');
    img.style.setProperty('width', '100%', 'important');
    img.style.setProperty('height', 'auto', 'important');
    img.style.setProperty('max-width', 'none', 'important');
    img.style.setProperty('display', 'block', 'important');

    // fallback: if pl.jpg returns 404, revert to original thumbnail
    img.addEventListener('error', function onErr() {
      if (img.dataset.jlReverted) return;
      img.dataset.jlReverted = '1';
      img.removeEventListener('error', onErr);
      img.src = img.dataset.jlOriginal;
      img.style.setProperty('width', 'auto', 'important');
      img.style.setProperty('max-width', '100%', 'important');
    });

    img.src = upgraded;
  }

  /* ---------- 2. Layout: responsive grid + remove hardcoded heights ---------- */
  function findContainer(cell) {
    // find the ancestor whose direct children include >= 2 .video cards (that is the grid)
    let el = cell.parentElement;
    while (el && el.querySelectorAll(':scope > .video').length < 2) {
      el = el.parentElement;
    }
    return el || cell.parentElement;
  }

  function fixLayout() {
    const cells = document.querySelectorAll('.video');
    if (!cells.length) return;

    const container = findContainer(cells[0]);
    if (container && !container.dataset.jlGrid) {
      container.dataset.jlGrid = '1';
      container.style.setProperty('display', 'grid', 'important');
      container.style.setProperty(
        'grid-template-columns',
        `repeat(auto-fill, minmax(min(${MIN_CARD_WIDTH}px, 100%), 1fr))`,
        'important'
      );
      container.style.setProperty('gap', GAP + 'px', 'important');
      container.style.setProperty('align-items', 'start', 'important');
      container.style.setProperty('height', 'auto', 'important');
      container.style.setProperty('max-height', 'none', 'important');
      container.style.setProperty('overflow', 'visible', 'important');

      // remove hardcoded heights from ancestor elements
      let p = container.parentElement;
      for (let i = 0; i < 3 && p; i++, p = p.parentElement) {
        p.style.setProperty('height', 'auto', 'important');
        p.style.setProperty('max-height', 'none', 'important');
        p.style.setProperty('overflow', 'visible', 'important');
      }
    }

    cells.forEach((cell) => {
      if (cell.dataset.jlCell) return;
      cell.dataset.jlCell = '1';
      cell.style.setProperty('float', 'none', 'important');
      cell.style.setProperty('width', 'auto', 'important');
      cell.style.setProperty('height', 'auto', 'important');
      cell.style.setProperty('margin', '0', 'important');
      cell.style.setProperty('overflow', 'visible', 'important');
    });
  }

  /* ---------- 3. Font size + text width matching the image ---------- */
  function detectSidebarFontSize() {
    const labels = ['Home', 'New Comments', 'New Releases', 'New Entries',
                    'Most Wanted', 'Best Rated', 'Categories', 'Ranking',
                    'Directory', 'Best Reviews', 'Forum'];
    for (const a of document.querySelectorAll('a')) {
      if (labels.includes((a.textContent || '').trim())) {
        return getComputedStyle(a).fontSize;
      }
    }
    return null;
  }

  function applyTextStyle() {
    const size = FONT_SIZE || detectSidebarFontSize() || FONT_SIZE_FALLBACK;
    let st = document.getElementById('jl-font-style');
    if (!st) {
      st = document.createElement('style');
      st.id = 'jl-font-style';
      (document.head || document.documentElement).appendChild(st);
    }
    st.textContent = `
      /* Card: flex column so text stretches to full image width */
      div.video {
        display: flex !important;
        flex-direction: column !important;
        align-items: stretch !important;
      }

      /* Text divs: fill the full card width (= image width) */
      div.video .title,
      div.video .id {
        width: 100% !important;
        max-width: 100% !important;
        box-sizing: border-box !important;
      }

      /* Film ID: single line, ellipsis if too long */
      div.video .id, div.video .id a {
        font-size:     ${size} !important;
        line-height:   1.4 !important;
        white-space:   nowrap !important;
        overflow:      hidden !important;
        text-overflow: ellipsis !important;
        display:       block !important;
      }

      /* Title: max ${TITLE_LINES} lines, rest clipped */
      div.video .title, div.video .title a {
        font-size:          ${size} !important;
        line-height:        1.4 !important;
        display:            -webkit-box !important;
        -webkit-line-clamp: ${TITLE_LINES} !important;
        -webkit-box-orient: vertical !important;
        overflow:           hidden !important;
        white-space:        normal !important;
        text-overflow:      ellipsis !important;
      }
    `;
  }

  /* ---------- 4. Run ---------- */
  function run(root) {
    root.querySelectorAll('img').forEach(upgradeImg);
    fixLayout();
    applyTextStyle();
  }

  run(document);

  let scheduled = false;
  const mo = new MutationObserver((mutations) => {
    for (const m of mutations) {
      m.addedNodes.forEach((node) => {
        if (node.nodeType !== 1) return;
        if (node.tagName === 'IMG') upgradeImg(node);
        else if (node.querySelectorAll) node.querySelectorAll('img').forEach(upgradeImg);
      });
    }
    if (!scheduled) {
      scheduled = true;
      requestAnimationFrame(() => { scheduled = false; fixLayout(); applyTextStyle(); });
    }
  });
  mo.observe(document.documentElement, { childList: true, subtree: true });
})();