Eye protection, WASD scrolling, Tab navigation, and Horizontal Position Memory for Split View.
// ==UserScript==
// @name Better Webtoons
// @namespace https://miku.us.kg/
// @version 1.2
// @description Eye protection, WASD scrolling, Tab navigation, and Horizontal Position Memory for Split View.
// @author @redsus.vn on Discord
// @match *://*.webtoons.com/*
// @match *://*.naver.com/*
// @grant none
// @run-at document-start
// @license CC BY-NC 4.0
// ==/UserScript==
(function() {
'use strict';
const STORAGE_KEY = 'wt_eye_protection_yellow_mode';
const OFFSET_STORAGE_KEY = 'wt_viewer_horizontal_offset';
const MODES = [
'transparent',
'rgba(255, 215, 0, 0.2)',
'rgba(255, 140, 0, 0.45)'
];
let currentMode = 0;
let currentOffset = 0;
try {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved !== null) {
currentMode = parseInt(saved);
}
const savedOffset = localStorage.getItem(OFFSET_STORAGE_KEY);
if (savedOffset !== null) {
currentOffset = parseInt(savedOffset);
}
} catch (e) {
console.log("Storage access blocked");
}
if (currentMode >= MODES.length || isNaN(currentMode)) currentMode = 0;
if (isNaN(currentOffset)) currentOffset = 0;
function applyOverlay() {
let overlay = document.getElementById('yellow-filter-overlay');
if (!overlay) {
const parent = document.documentElement || document.body;
if (!parent) return;
overlay = document.createElement('div');
overlay.id = 'yellow-filter-overlay';
overlay.style.cssText = `
position: fixed;
top: 0; left: 0;
width: 100vw; height: 100vh;
pointer-events: none;
z-index: 2147483647;
mix-blend-mode: multiply;
transition: background-color 0.2s ease;
`;
parent.appendChild(overlay);
}
overlay.style.backgroundColor = MODES[currentMode];
}
function applyOffset() {
const target = document.getElementById('wrap') || document.body;
if (!target) return;
// Automatically disable offset if window is wide (normal tab view)
// Checks if window width is > 90% of available screen width
const isWide = window.innerWidth > (window.screen.availWidth || window.innerWidth) * 0.9;
const effectiveOffset = isWide ? 0 : currentOffset;
if (target === document.body) {
if (effectiveOffset !== 0) {
target.style.marginLeft = effectiveOffset + 'px';
target.style.overflowX = 'hidden';
} else {
target.style.marginLeft = '';
target.style.overflowX = '';
}
} else {
target.style.transform = effectiveOffset !== 0 ? `translateX(${effectiveOffset}px)` : '';
}
}
function createOffsetControl() {
if (document.getElementById('wt-offset-slider')) return;
if (!document.body) return;
const container = document.createElement('div');
container.style.cssText = `
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 20px;
z-index: 2147483648;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.05);
opacity: 0;
transition: opacity 0.3s;
`;
container.onmouseenter = () => container.style.opacity = '1';
container.onmouseleave = () => container.style.opacity = '0';
const slider = document.createElement('input');
slider.type = 'range';
slider.id = 'wt-offset-slider';
slider.min = -2000;
slider.max = 2000;
slider.value = currentOffset;
slider.style.cssText = `
width: 90%;
height: 6px;
cursor: pointer;
border-radius: 3px;
`;
slider.oninput = function() {
currentOffset = parseInt(this.value);
applyOffset();
};
slider.onchange = function() {
localStorage.setItem(OFFSET_STORAGE_KEY, currentOffset);
this.blur();
};
container.appendChild(slider);
document.body.appendChild(container);
}
function createButton() {
if (document.getElementById('yellow-mode-btn')) return;
if (!document.body) return;
// Inject CSS for the smooth hover animation
const style = document.createElement('style');
style.innerHTML = `
#yellow-mode-btn {
position: fixed;
bottom: 120px; /* Positioned slightly above the scroll-to-top button */
right: 0;
width: 8px;
height: 60px;
background: #222;
border-radius: 4px 0 0 4px;
cursor: pointer;
z-index: 2147483648;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
box-shadow: -2px 2px 5px rgba(0,0,0,0.2);
overflow: hidden;
}
#yellow-mode-btn:hover {
width: 60px;
background: #fff;
border: 3px solid #000;
border-radius: 12px 0 0 12px;
}
#yellow-mode-btn span {
opacity: 0;
font-size: 28px;
transform: scale(0.5);
transition: all 0.3s ease;
}
#yellow-mode-btn:hover span {
opacity: 1;
transform: scale(1);
}
`;
document.head.appendChild(style);
const btn = document.createElement('div');
btn.id = 'yellow-mode-btn';
btn.innerHTML = '<span>👁️</span>';
btn.title = "Toggle Yellow Mode";
btn.onclick = function() {
currentMode++;
if (currentMode >= MODES.length) currentMode = 0;
localStorage.setItem(STORAGE_KEY, currentMode);
applyOverlay();
};
document.body.appendChild(btn);
}
function setupKeyboardControls() {
window.addEventListener('keydown', (e) => {
const tag = document.activeElement.tagName;
if (tag === 'INPUT' || tag === 'TEXTAREA' || document.activeElement.isContentEditable) {
return;
}
const key = e.key.toLowerCase();
const scrollAmount = window.innerHeight * 0.85;
if (['arrowdown', 'arrowright', 's', 'd'].includes(key)) {
e.preventDefault();
window.scrollBy({ top: scrollAmount, behavior: 'smooth' });
}
if (['arrowup', 'arrowleft', 'w', 'a'].includes(key)) {
e.preventDefault();
window.scrollBy({ top: -scrollAmount, behavior: 'smooth' });
}
if (e.key === 'Tab') {
e.preventDefault();
if (e.shiftKey) {
const prevBtn = document.querySelector('.pg_prev._prevEpisode');
if (prevBtn) {
prevBtn.click();
} else {
console.log("No previous chapter found.");
}
} else {
const nextBtn = document.querySelector('.pg_next._nextEpisode');
if (nextBtn) {
nextBtn.click();
} else {
console.log("No next chapter found.");
}
}
}
});
}
// Add listener to reset offset when window is resized (e.g. going to full screen)
window.addEventListener('resize', applyOffset);
let controlsSetup = false;
const startLoop = setInterval(() => {
if (document.documentElement) applyOverlay();
applyOffset();
if (!controlsSetup) {
setupKeyboardControls();
controlsSetup = true;
}
if (document.body) {
createButton();
createOffsetControl();
if (document.getElementById('yellow-filter-overlay') &&
document.getElementById('yellow-mode-btn') &&
document.getElementById('wt-offset-slider')) {
clearInterval(startLoop);
}
}
}, 50);
})();