// ==UserScript==
// @name nhentai Universal QoL Enhancer
// @namespace http://tampermonkey.net/
// @version 3.4
// @description Universal QoL: Unblurs, clicks, proxy bypass, DYNAMIC fit, preloads, no text select, INSTANT SPA CLICKS (retains scroll), keyboard navigation on nhentai.net & nhentai-xxx.
// @author nekohacker591
// @match *://nhentai.net/g/*/*
// @match *://nhentai-xxx.pornproxy.app/g/*/*
// @match *://nhentai.net/*
// @match *://nhentai-xxx.pornproxy.app/*
// @grant GM_addStyle
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
console.log('Universal nhentai QoL Script v3.4: Initializing...');
// --- Site Detection & Config ---
const isOfficialSite = window.location.hostname === 'nhentai.net';
const isProxySite = window.location.hostname.includes('nhentai-xxx.pornproxy.app');
const PRELOAD_AHEAD = 5;
const DYNAMIC_HEIGHT_PADDING = 15;
const imageProxyHost = 'image.staticox.com';
// --- Helper Functions ---
function rewriteImageUrl(imageUrl) {
if (!imageUrl || typeof imageUrl !== 'string') return null;
try {
const urlObj = new URL(imageUrl);
if (urlObj.hostname === imageProxyHost && urlObj.searchParams.has('url')) {
const originalUrlEncoded = urlObj.searchParams.get('url');
const originalUrlDecoded = decodeURIComponent(originalUrlEncoded);
if (originalUrlDecoded.startsWith('http://') || originalUrlDecoded.startsWith('https://')) {
if (originalUrlDecoded.includes('.nhentaimg.com') || originalUrlDecoded.includes('.nhentai.net')) {
return originalUrlDecoded.replace(/^http:/, 'https:');
}
return originalUrlDecoded;
}
}
} catch (e) {}
return null;
}
function processImageNodeProxy(imgNode) {
let changed = false;
const dataSrc = imgNode.dataset.src;
const currentSrc = imgNode.getAttribute('src');
const isGalleryItemThumb = imgNode.closest('.gallery_item') !== null;
const isTagThumb = imgNode.closest('.tags_thumbs .thumb') !== null;
if (dataSrc && dataSrc.includes(imageProxyHost)) {
const directUrl = rewriteImageUrl(dataSrc);
if (directUrl) {
imgNode.dataset.src = directUrl;
if (isTagThumb || isGalleryItemThumb) {
if (!currentSrc || currentSrc.startsWith('data:image') || currentSrc.includes(imageProxyHost)) {
imgNode.setAttribute('src', directUrl);
}
} else if ((imgNode.classList.contains('loaded') || imgNode.classList.contains('entered')) && currentSrc && currentSrc.includes(imageProxyHost)) {
const directSrcUrl = rewriteImageUrl(currentSrc);
if (directSrcUrl) {
imgNode.setAttribute('src', directSrcUrl);
}
}
changed = true;
}
} else if (currentSrc && currentSrc.includes(imageProxyHost)) {
const directUrl = rewriteImageUrl(currentSrc);
if (directUrl) {
imgNode.setAttribute('src', directUrl);
changed = true;
}
}
return changed;
}
function getCurrentPageFromUrl() {
const match = window.location.pathname.match(/\/g\/(\d+)\/(\d+)\/?$/);
return match ? { galleryId: match[1], pageNum: parseInt(match[2], 10) } : null;
}
// --- Preloading Logic ---
let preloadCache = {};
let currentlyPreloading = new Set();
function getPageImageUrl_Proxy(pageNumber, galleryData) {
if (!galleryData || !galleryData.g_th || !galleryData.g_th.fl || !galleryData.g_th.fl[pageNumber]) {
if (galleryData && galleryData.imageDir && galleryData.proxyGalleryId && galleryData.serverId) {
return `https://i${galleryData.serverId}.nhentaimg.com/${galleryData.imageDir}/${galleryData.proxyGalleryId}/${pageNumber}.jpg`.replace(/^http:/, 'https:');
}
return null;
}
try {
const pageInfo = galleryData.g_th.fl[pageNumber];
const typeCode = pageInfo.split(',')[0];
const ext = { 'j': 'jpg', 'p': 'png', 'g': 'gif' }[typeCode] || 'jpg';
let baseUrl = `https://i${galleryData.serverId || '1'}.nhentaimg.com/`;
if (galleryData.imageDir && galleryData.proxyGalleryId) {
baseUrl += `${galleryData.imageDir}/${galleryData.proxyGalleryId}/`;
} else {
return null;
}
return `${baseUrl}${pageNumber}.${ext}`.replace(/^http:/, 'https:');
} catch (e) {
console.error(`Proxy Preload URL Error: Page ${pageNumber}`, e);
return null;
}
}
function getPageImageUrl_Official(pageNumber, galleryData) {
if (!galleryData || !galleryData.media_id || !galleryData.images || !galleryData.images.pages || !galleryData.images.pages[pageNumber - 1]) {
return null;
}
try {
const pageInfo = galleryData.images.pages[pageNumber - 1];
const typeCode = pageInfo.t;
const ext = { 'j': 'jpg', 'p': 'png', 'g': 'gif' }[typeCode] || 'jpg';
const mediaId = galleryData.media_id;
const serverSubdomain = window._n_app?.media_server ? `i${window._n_app.media_server}` : 'i';
const url = `https://${serverSubdomain}.nhentai.net/galleries/${mediaId}/${pageNumber}.${ext}`;
return url;
} catch (e) {
console.error(`Official Preload URL Error: Page ${pageNumber}`, e);
return null;
}
}
function preloadImage(url) {
if (!url || preloadCache[url] || currentlyPreloading.has(url)) return;
currentlyPreloading.add(url);
const img = new Image();
img.onload = () => {
preloadCache[url] = img;
currentlyPreloading.delete(url);
};
img.onerror = () => {
console.warn(`Preload Failed: ${url}`);
currentlyPreloading.delete(url);
};
img.src = url;
}
function triggerPreload(galleryData) {
const currentPage = galleryData.currentPage;
const totalPages = galleryData.num_pages || galleryData.totalPages;
if (currentPage === null || !totalPages || !galleryData) return;
const startPage = currentPage + 1;
const endPage = Math.min(currentPage + PRELOAD_AHEAD, totalPages);
const urlGetter = isOfficialSite ? getPageImageUrl_Official : getPageImageUrl_Proxy;
for (let i = startPage; i <= endPage; i++) {
const imageUrl = urlGetter(i, galleryData);
preloadImage(imageUrl);
}
}
// --- Gallery Data Parsers ---
let parsedGalleryData = null;
function getGalleryData_Proxy() {
const pageInfo = getCurrentPageFromUrl();
const currentPage = pageInfo ? pageInfo.pageNum : null;
const galleryIdFromUrl = pageInfo ? pageInfo.galleryId : null;
const totalPagesStr = document.getElementById('pages')?.value;
const imageDir = document.getElementById('image_dir')?.value;
const proxyGalleryIdInput = document.getElementById('gallery_id')?.value;
const serverId = document.getElementById('server_id')?.value;
let g_th_data = null;
const scripts = document.getElementsByTagName('script');
for (let script of scripts) {
if (script.textContent.includes('var g_th = $.parseJSON')) {
try {
const jsonMatch = script.textContent.match(/var g_th = \$.parseJSON\('(.*)'\);/s);
if (jsonMatch && jsonMatch[1]) {
const jsonString = jsonMatch[1].replace(/\\'/g, "'").replace(/\\"/g, '"');
g_th_data = JSON.parse(jsonString);
break;
}
} catch (e) {
console.error("Proxy g_th parse failed.", e);
}
}
}
if (currentPage !== null && totalPagesStr && imageDir && proxyGalleryIdInput && serverId && galleryIdFromUrl) {
return {
currentPage: currentPage,
totalPages: parseInt(totalPagesStr, 10),
imageDir: imageDir,
proxyGalleryId: proxyGalleryIdInput,
serverId: serverId,
g_th: g_th_data,
navGalleryId: galleryIdFromUrl
};
}
return null;
}
function getGalleryData_Official() {
const pageInfo = getCurrentPageFromUrl();
const currentPage = pageInfo ? pageInfo.pageNum : null;
if (typeof window._gallery !== 'undefined' && window._gallery && currentPage !== null) {
return { ...window._gallery, currentPage: currentPage, navGalleryId: window._gallery.id };
}
return null;
}
function getCombinedGalleryData() {
if (parsedGalleryData) return parsedGalleryData;
if (isOfficialSite) {
parsedGalleryData = getGalleryData_Official();
} else if (isProxySite) {
parsedGalleryData = getGalleryData_Proxy();
}
return parsedGalleryData;
}
// --- Proxy Nav Link Updater ---
function updateProxyNavLinks() {
if (!isProxySite) return;
const galleryData = getCombinedGalleryData();
if (!galleryData || !galleryData.navGalleryId || !galleryData.currentPage || !galleryData.totalPages) {
return;
}
const { navGalleryId, currentPage, totalPages } = galleryData;
const baseUrl = `/g/${navGalleryId}/`;
const firstLink = document.querySelector('.reader_nav .rd_first');
const prevLink = document.querySelector('.reader_nav .rd_prev');
const nextLink = document.querySelector('.reader_nav .rd_next');
const lastLink = document.querySelector('.reader_nav .rd_last');
if (firstLink) {
if (currentPage > 1) {
firstLink.href = `${baseUrl}1/`;
firstLink.classList.remove('invisible');
} else {
firstLink.removeAttribute('href');
firstLink.classList.add('invisible');
}
}
if (prevLink) {
if (currentPage > 1) {
prevLink.href = `${baseUrl}${currentPage - 1}/`;
prevLink.classList.remove('invisible');
} else {
prevLink.removeAttribute('href');
prevLink.classList.add('invisible');
}
}
if (nextLink) {
if (currentPage < totalPages) {
nextLink.href = `${baseUrl}${currentPage + 1}/`;
nextLink.classList.remove('invisible');
} else {
nextLink.removeAttribute('href');
nextLink.classList.add('invisible');
}
}
if (lastLink) {
if (currentPage < totalPages) {
lastLink.href = `${baseUrl}${totalPages}/`;
lastLink.classList.remove('invisible');
} else {
lastLink.removeAttribute('href');
lastLink.classList.add('invisible');
}
}
}
// --- QoL Feature: Dynamic Height Adjustment ---
let lastAppliedHeight = 0;
let adjustHeightAttempts = 0;
const MAX_ADJUST_ATTEMPTS = 10;
function applyHeightStyle(imageElement, availableHeight) {
if (!imageElement) return;
if (availableHeight > 50) {
const newHeightPx = `${availableHeight.toFixed(1)}px`;
if (imageElement.style.maxHeight !== newHeightPx) {
imageElement.style.setProperty('max-height', newHeightPx, 'important');
lastAppliedHeight = availableHeight;
}
} else {
console.warn(`[QoL AdjustHeight] Calculated height (${availableHeight.toFixed(1)}px) too small. Removing JS style.`);
imageElement.style.removeProperty('max-height');
lastAppliedHeight = 0;
}
}
function performHeightAdjustment() {
let topBar, bottomBar, imageElement;
const windowHeight = window.innerHeight;
let topBarHeight = 0;
let bottomBarHeight = 0;
let elementsFound = false;
if (isOfficialSite) {
const bars = document.querySelectorAll('#content > section.reader-bar');
imageElement = document.querySelector('#image-container > a > img');
if (bars.length === 2 && imageElement) {
topBar = bars[0];
bottomBar = bars[1];
elementsFound = true;
}
} else if (isProxySite) {
const navs = document.querySelectorAll('div.reader_nav');
imageElement = document.getElementById('fimg');
if (navs.length === 2 && imageElement) {
topBar = navs[0];
bottomBar = navs[1];
elementsFound = true;
}
}
if (!elementsFound || !imageElement) {
console.warn("[QoL AdjustHeight] Required elements not found yet.");
adjustHeightAttempts++;
if (adjustHeightAttempts < MAX_ADJUST_ATTEMPTS) {
setTimeout(performHeightAdjustment, 200);
} else {
console.error("[QoL AdjustHeight] Failed to find elements after multiple attempts.");
}
return;
}
topBarHeight = topBar.getBoundingClientRect().height;
bottomBarHeight = bottomBar.getBoundingClientRect().height;
if (topBarHeight < 1 || bottomBarHeight < 1) {
console.warn(`[QoL AdjustHeight] Bar heights invalid (Top: ${topBarHeight}, Bottom: ${bottomBarHeight}). Waiting...`);
adjustHeightAttempts++;
if (adjustHeightAttempts < MAX_ADJUST_ATTEMPTS) {
setTimeout(performHeightAdjustment, 200 * (adjustHeightAttempts + 1));
} else {
console.error("[QoL AdjustHeight] Failed to get valid bar heights after multiple attempts.");
imageElement.style.removeProperty('max-height');
lastAppliedHeight = 0;
}
return;
}
const availableHeight = windowHeight - topBarHeight - bottomBarHeight - DYNAMIC_HEIGHT_PADDING;
applyHeightStyle(imageElement, availableHeight);
adjustHeightAttempts = 0;
}
function triggerHeightAdjustment() {
adjustHeightAttempts = 0;
lastAppliedHeight = 0;
requestAnimationFrame(performHeightAdjustment);
}
// --- Inject CSS ---
GM_addStyle(`
#image-container img, #fimg, .rd_fimg img {
display: block !important;
margin-left: auto !important;
margin-right: auto !important;
max-width: 98vw !important;
max-height: 95vh !important; /* <-- CSS FALLBACK */
width: auto !important;
height: auto !important;
object-fit: contain !important;
pointer-events: auto !important;
}
#fimg, .rd_fimg img {
filter: none !important;
-webkit-filter: none !important;
opacity: 1 !important;
visibility: visible !important;
}
#image-container > a, .rd_fimg .nx_nv, .rd_fimg .pr_nv, .rd_fimg .fw_img {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.reader-bar a, .reader-bar button, .reader_nav a, .reader_nav button {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.reader_overlay .alert {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
height: 0 !important;
overflow: hidden !important;
pointer-events: none !important;
}
.rd_fimg.filtered_reader {
filter: none !important;
-webkit-filter: none !important;
opacity: 1 !important;
pointer-events: auto !important;
}
.reader_overlay {
background: none !important;
position: relative !important;
}
.gallery_item img.lazyload.filtered, .cover img.lazyload.filtered, .gt_th img.filtered, img.lazyload.filtered {
filter: none !important;
-webkit-filter: none !important;
opacity: 1 !important;
visibility: visible !important;
pointer-events: auto !important;
}
.outer_thumbs > .alert.alert-warning {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
height: 0 !important;
overflow: hidden !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
pointer-events: none !important;
}
#fav_nl, #dl_nl {
pointer-events: auto !important;
cursor: pointer !important;
opacity: 1 !important;
background-color: #333 !important;
color: #fff !important;
border-color: #555 !important;
}
button.mbtn.disabled {
opacity: 1 !important;
cursor: pointer !important;
background-color: #333 !important;
color: #fff !important;
border-color: #555 !important;
}
.com_link {
pointer-events: auto !important;
cursor: pointer !important;
opacity: 1 !important;
visibility: visible !important;
color: #ccc !important;
text-decoration: underline !important;
}
#image-container > a, .rd_fimg a, .rd_fimg .pr_nv, .rd_fimg .nx_nv, .rd_fimg .fw_img {
pointer-events: auto !important;
cursor: pointer !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
.rd_fimg .pr_nv, .rd_fimg .nx_nv {
width: 50% !important;
height: 100% !important;
position: absolute !important;
top: 0 !important;
z-index: 10 !important;
}
.rd_fimg .pr_nv {
left: 0 !important;
}
.rd_fimg .nx_nv {
right: 0 !important;
}
.reader-bar .go-back, .reader-bar .first, .reader-bar .previous, .reader-bar .page-number, .reader-bar .next, .reader-bar .last, .reader_nav .back_btn, .reader_nav .rd_first, .reader_nav .rd_prev, .reader_nav .pages_btn, .reader_nav .rd_next, .reader_nav .rd_last {
pointer-events: auto !important;
cursor: pointer !important;
visibility: visible !important;
opacity: 1 !important;
color: inherit !important;
}
.reader-pagination a.invisible, .reader_nav a.invisible, .reader_nav button.invisible {
visibility: visible !important;
opacity: 1 !important;
pointer-events: auto !important;
cursor: pointer !important;
color: inherit !important;
}
.gallery a, .gallery_item a, #cover a, .cover a, .gt_th a {
pointer-events: auto !important;
cursor: pointer !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
`);
// --- MutationObserver ---
const observer = new MutationObserver(mutations => {
let mainImageChanged = false;
mutations.forEach(mutation => {
if (isProxySite) {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) {
if (node.tagName === 'IMG') {
processImageNodeProxy(node);
} else if (node.querySelectorAll) {
node.querySelectorAll('img').forEach(processImageNodeProxy);
}
}
});
}
if (mutation.type === 'attributes') {
if (mutation.target.tagName === 'IMG') {
const imgTarget = mutation.target;
if (isProxySite) {
processImageNodeProxy(imgTarget);
}
const officialImg = isOfficialSite && imgTarget.closest('#image-container');
const proxyImg = isProxySite && imgTarget.id === 'fimg';
const isMainImageSrcChange = (officialImg || proxyImg) && mutation.attributeName === 'src';
const spaTriggered = imgTarget._isSpaUpdating === true;
if (isMainImageSrcChange && !spaTriggered) {
mainImageChanged = true;
} else if (spaTriggered) {
imgTarget._isSpaUpdating = false;
}
}
}
});
if (mainImageChanged) {
parsedGalleryData = null;
const galleryData = getCombinedGalleryData();
if (galleryData) {
if (isProxySite) updateProxyNavLinks();
triggerPreload(galleryData);
triggerHeightAdjustment();
}
}
});
observer.observe(document.documentElement || document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['src', 'data-src'] });
// --- SPA Navigation Logic for Proxy ---
function handleInstantClickSPA(event, targetPageNum) {
if (!isProxySite) return;
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
const currentGalleryData = getCombinedGalleryData();
if (!currentGalleryData) return;
const { totalPages, navGalleryId } = currentGalleryData;
if (targetPageNum < 1 || targetPageNum > totalPages) return;
const targetImageUrl = getPageImageUrl_Proxy(targetPageNum, currentGalleryData);
const targetBrowserUrl = `/g/${navGalleryId}/${targetPageNum}/`;
if (!targetImageUrl) return;
const imageElement = document.getElementById('fimg');
if (!imageElement) return;
console.log(`[SPA Nav] Navigating to page ${targetPageNum}`);
// Capture current scroll position
const currentScrollY = window.scrollY;
parsedGalleryData.currentPage = targetPageNum;
history.pushState({ page: targetPageNum }, '', targetBrowserUrl);
document.querySelectorAll('.reader_nav .cr').forEach(el => {
if (el) el.textContent = targetPageNum;
});
updateProxyNavLinks();
triggerPreload(parsedGalleryData);
triggerHeightAdjustment();
imageElement._isSpaUpdating = true;
imageElement.src = targetImageUrl;
// Restore scroll position after image loads
imageElement.onload = () => {
window.scrollTo(0, currentScrollY);
imageElement.onload = null; // Clean up handler
};
}
// --- Keyboard Navigation ---
function handleKeyboardNavigation(event) {
const galleryData = getCombinedGalleryData();
if (!galleryData) return;
let direction;
switch (event.key) {
case 'ArrowRight':
direction = 'next';
break;
case 'ArrowLeft':
direction = 'prev';
break;
case 'Home':
direction = 'first';
break;
case 'End':
direction = 'last';
break;
default:
return;
}
if (isOfficialSite) {
const selector = direction === 'next' ? '.reader-bar .next:not(.invisible)' :
direction === 'prev' ? '.reader-bar .previous:not(.invisible)' :
direction === 'first' ? '.reader-bar .first:not(.invisible)' :
direction === 'last' ? '.reader-bar .last:not(.invisible)' : null;
if (selector) {
const targetLinkElement = document.querySelector(selector);
if (targetLinkElement) {
targetLinkElement.click();
}
}
} else if (isProxySite) {
let targetPageNum = galleryData.currentPage;
if (direction === 'next' && targetPageNum < galleryData.totalPages) targetPageNum++;
else if (direction === 'prev' && targetPageNum > 1) targetPageNum--;
else if (direction === 'first') targetPageNum = 1;
else if (direction === 'last') targetPageNum = galleryData.totalPages;
else return;
if (targetPageNum !== galleryData.currentPage) {
handleInstantClickSPA(event, targetPageNum);
}
}
}
// --- Instant Click Listener Setup ---
function addInstantClickListeners() {
console.log("[InstantClick] Adding listeners using 'click' event...");
const galleryData = getCombinedGalleryData();
const handleOfficialClick = (event, direction) => {
if (!isOfficialSite) return;
// Allow default behavior for official site (full page load)
};
const handleProxyClick = (event, direction) => {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
if (!isProxySite || !galleryData) return;
let targetPageNum = galleryData.currentPage;
if (direction === 'next' && targetPageNum < galleryData.totalPages) targetPageNum++;
else if (direction === 'prev' && targetPageNum > 1) targetPageNum--;
else if (direction === 'first') targetPageNum = 1;
else if (direction === 'last') targetPageNum = galleryData.totalPages;
else return;
if (targetPageNum !== galleryData.currentPage) {
handleInstantClickSPA(event, targetPageNum);
}
};
const listeners = [
{ site: 'official', target: '#image-container > a', handler: (e) => handleOfficialClick(e, 'next') },
{ site: 'official', target: '.reader-bar .next', handler: (e) => handleOfficialClick(e, 'next') },
{ site: 'official', target: '.reader-bar .last', handler: (e) => handleOfficialClick(e, 'last') },
{ site: 'official', target: '.reader-bar .previous', handler: (e) => handleOfficialClick(e, 'prev') },
{ site: 'official', target: '.reader-bar .first', handler: (e) => handleOfficialClick(e, 'first') },
{ site: 'proxy', target: '.rd_fimg .nx_nv', handler: (e) => handleProxyClick(e, 'next') },
{ site: 'proxy', target: '.rd_fimg .pr_nv', handler: (e) => handleProxyClick(e, 'prev') },
{ site: 'proxy', target: '.reader_nav .rd_next', handler: (e) => handleProxyClick(e, 'next') },
{ site: 'proxy', target: '.reader_nav .rd_last', handler: (e) => handleProxyClick(e, 'last') },
{ site: 'proxy', target: '.reader_nav .rd_prev', handler: (e) => handleProxyClick(e, 'prev') },
{ site: 'proxy', target: '.reader_nav .rd_first', handler: (e) => handleProxyClick(e, 'first') },
];
listeners.forEach(listener => {
if ((listener.site === 'official' && !isOfficialSite) || (listener.site === 'proxy' && !isProxySite)) return;
document.querySelectorAll(listener.target).forEach(element => {
const eventType = 'click';
element.removeEventListener(eventType, element._instantClickHandler, true);
element._instantClickHandler = listener.handler;
element.addEventListener(eventType, element._instantClickHandler, true);
});
});
console.log("[InstantClick] Listeners configured using 'click'.");
}
// --- Initial Setup & Event Listeners ---
let resizeTimeout;
function debounceResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(triggerHeightAdjustment, 150);
}
function runInitialSetup() {
console.log("[QoL Init] Running initial setup...");
parsedGalleryData = null;
const galleryData = getCombinedGalleryData();
const isOnReaderPage = galleryData && window.location.pathname.includes('/g/') && galleryData.currentPage !== null;
if (isProxySite) {
initialImageScanProxy();
}
if (isOnReaderPage) {
console.log("[QoL Init] Reader Page Detected. Activating reader features.");
if (isProxySite) updateProxyNavLinks();
triggerPreload(galleryData);
triggerHeightAdjustment();
addInstantClickListeners();
document.addEventListener('keydown', handleKeyboardNavigation);
window.addEventListener('resize', debounceResize);
console.log("[QoL Init] Reader features activated.");
} else {
console.log("[QoL Init] Not on reader page, skipping reader-specific setup.");
}
}
function initialImageScanProxy() {
document.querySelectorAll('img').forEach(processImageNodeProxy);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runInitialSetup);
} else {
runInitialSetup();
}
window.addEventListener('load', () => {
console.log('Universal QoL Script: Page fully loaded.');
const galleryData = getCombinedGalleryData();
const isOnReaderPage = galleryData && window.location.pathname.includes('/g/') && galleryData.currentPage !== null;
if (isOnReaderPage) {
if (isProxySite) updateProxyNavLinks();
if (lastAppliedHeight <= 0) {
setTimeout(triggerHeightAdjustment, 300);
}
addInstantClickListeners();
}
});
})();