Skip to content

Instantly share code, notes, and snippets.

@AuroraWright
Last active June 19, 2026 04:11
Show Gist options
  • Select an option

  • Save AuroraWright/6c710b4ce64a7e26a7470aa0b845a76f to your computer and use it in GitHub Desktop.

Select an option

Save AuroraWright/6c710b4ce64a7e26a7470aa0b845a76f to your computer and use it in GitHub Desktop.
ttsuscript fork
// ==UserScript==
// @name ッツ UI Enhancements (4:3 ereader)
// @namespace http://tampermonkey.net/
// @version 21.4
// @description E-ink enhancements for ttu ebook-reader
// @author KanjiEater, Glacey
// @match https://reader.ttsu.app/*
// @homepageURL https://gist.github.com/Glacey/8c8b3cbcd40a56a6460edb29dd6f9202
// @grant GM_addStyle
// @run-at document-idle
// @noframes
// ==/UserScript==
(function() {
'use strict';
// Search "Optional" for common settings changes
// --- SECTION 1: CUSTOM CSS STYLES ---
const allCustomStyles = `
/*
* SECTION A1: MOBILE BEHAVIOR ENHANCEMENTS
*/
/* Book Manager: Disable save image popup on long tap */
.book-cover {
pointer-events: none !important;
}
/* Buttons: Remove focus ring on tap */
* { -webkit-tap-highlight-color: transparent !important; }
*:focus, *:focus-visible {
outline: none !important;
box-shadow: none !important;
}
/* Reader: Remove pull-to-refresh on Chromium browsers */
html, body {
overscroll-behavior-y: none !important;
}
.ttu-whispersync-container.side-menu .overflow-auto {
touch-action: pan-y !important;
overflow-y: scroll !important;
}
/* (Optional) Book Manager: Remove book title + transparent jacket to prevent yomitan popups and get a cleaner look at covers */
.bg-gray-800 {
display: none !important;
}
/* (Optional) Reader/Book Manager: Unhide top menu buttons. Disable this on narrow screens (causes overflow) */
div[class*="sm:flex"],
div[class*="sm\:flex"] {
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
}
div[class*="flex"][class*="sm:hidden"],
div[class*="flex"][class*="sm\:hidden"] {
display: none !important;
}
/*
* SECTION A2: E-INK BEHAVIOR ENHANCEMENTS
*/
/* Whispersync highlight -> Underline */
span[class^="ttu-whispersync-line-highlight-"].active,
span[class^="ttu-whispersync-line-highlight-"].menu-open {
background: none !important; padding: 0 !important; margin: 0 !important; color: inherit !important;
text-decoration-line: underline !important; text-decoration-style: solid !important;
text-decoration-thickness: 2px !important; text-decoration-color: #333 !important;
text-underline-position: left !important; text-underline-offset: 4px !important;
}
rt span[class^="ttu-whispersync-line-highlight-"].active,
rt span[class^="ttu-whispersync-line-highlight-"].menu-open {
text-decoration: none !important;
}
/* Remove cloud sync icon pulse */
#ttu-page-footer .animate-pulse {
animation: none !important;
}
/* Top Bar / Footer: Disable transitions */
.elevation-4,
div.bg-gray-700,
#ttu-page-footer {
transition: none !important;
animation: none !important;
}
/* Top Bar: Add a border */
.elevation-4 {
border-bottom: 2px solid #e5e5e5 !important;
}
/* Top Bar: Change background to white */
div.bg-gray-700 {
background-color: #ffffff !important;
}
/* Top Bar: Change text to black */
div.bg-gray-700,
div.bg-gray-700 .text-white {
color: #000000;
}
/* Top Bar: Lighter gray text for white background */
.text-gray-500 {
color: rgba(229, 231, 235, 1) !important;
}
/* Remove ripple circle animation click */
.v-ripple__container,
span.absolute.inset-0.h-full.w-full {
display: none !important;
}
/* General: Disable native swipe scrolling, handled by userscript */
.overflow-auto,
.mdc-drawer-app-content,
body {
touch-action: none !important;
}
/* Settings: Change settings highlight to gray */
div.bg-gray-900,
button.bg-gray-900 {
background-color: #e5e5e5 !important; /* Light gray selection */
color: #000000 !important; /* Force text to black */
}
/* Settings: Change hover color */
div[class*="hover:bg-gray-"]:hover,
button[class*="hover:bg-gray-"]:hover {
background-color: #cccccc !important;
}
/* Popups: Gray border */
section.mdc-elevation--z24 {
border: 2px solid #e5e5e5 !important;
}
/* Remove drop shadows (Book Manager selection, Offline Notification).*/
.mdc-elevation--z1,
.mdc-elevation--z2,
.mdc-elevation--z4,
.mdc-elevation--z24,
.box-shadow,
[class*="shadow"] {
box-shadow: none !important;
-webkit-box-shadow: none !important;
}
/* Reader: Popup dark backdrop removal (Tracker menu, Offline notification) */
div[class*="bg-black/[.32]"] {
background-color: transparent !important;
}
/* Reader/Image Gallery: Disable opening animation */
[style*="__svelte"] {
animation: none !important;
}
/* Reader: Eink friendly loading spinner */
@keyframes eink-dot1 {
0%,100% { background: #000; border-color: #000; }
50% { background: none; border-color: #000; }
}
@keyframes eink-dot2 {
0%,100% { background: none; border-color: #000; }
50% { background: #000; border-color: #000; }
}
.fixed.inset-0.flex.h-full.items-center.justify-center.text-7xl svg {
display: none !important;
}
.fixed.inset-0.flex.h-full.items-center.justify-center.text-7xl {
flex-direction: row !important;
writing-mode: horizontal-tb !important;
gap: 14px !important;
}
.fixed.inset-0.flex.h-full.items-center.justify-center.text-7xl::before,
.fixed.inset-0.flex.h-full.items-center.justify-center.text-7xl::after {
content: "" !important;
width: 14px !important;
height: 14px !important;
border-radius: 50% !important;
border: 3px solid #000 !important;
display: block !important;
box-sizing: border-box !important;
flex-shrink: 0 !important;
}
.fixed.inset-0.flex.h-full.items-center.justify-center.text-7xl::before {
animation: eink-dot1 2.4s steps(1) infinite !important;
}
.fixed.inset-0.flex.h-full.items-center.justify-center.text-7xl::after {
animation: eink-dot2 2.4s steps(1) infinite !important;
}
/* Whispersync: Eink friendly loading spinner */
.ttu-whispersync-container .backdrop .spinner {
animation:ttu-whispersync-spinner-rotation 8s steps(8) infinite !important;
}
/* Whispersync: Dark backdrop removal */
.ttu-whispersync-container .backdrop {
background-color: transparent !important;
}
/* Book Manager: Black/gray transparent progress bar, no rounded edges */
.bg-gray-400 {
background-color: rgba(229, 231, 235, 0.8);
}
.to-red-900 {
--tw-gradient-to: rgba(0, 0, 0, 0.8) var(--tw-gradient-to-position) !important;
}
.from-red-600 {
--tw-gradient-from: rgba(0, 0, 0, 0.8) var(--tw-gradient-from-position) !important;
--tw-gradient-to: rgba(0, 0, 0, 0.8) var(--tw-gradient-to-position) !important;
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to) !important;
}
.rounded {
border-radius: 0rem;
}
/* Book Manager: Change background to white */
:root {
--background-color: #ffffff;
}
/* Book Manager: Change selected book highlight color */
div[title="Book selected"] {
background-color: rgba(0,0,0,0.35) !important;
}
/* Book Manager: Change book selected button to white */
div[title="Book selected"] svg {
color: white !important;
}
/* Book Manager: Change Book Selection untapped color to gray */
div[title="Enable Book Selection"] svg {
color: #777 !important;
}
/* Book Manager: Increase greyed out option visibility */
.opacity-5 {
opacity: .3 !important;
}
/*
* SECTION B: BOOK MANAGER
*/
/* Book Manager: Increase top icon clickbox widths, adjust positioning fit narrow screens */
div[title="Enable Book Selection"] svg {
width: 200px !important;
position: absolute;
left: -75px;
}
div[title="Disable Book Selection"] svg {
width: 200px !important;
position: absolute;
left: -75px;
}
div[title="Select all Books"] svg {
width: 100px !important;
position: absolute !important;;
left: -110px;
}
div[title="Back to Book"] svg {
width: 100px !important;
position: absolute !important;
left: -140px;
}
/*
* SECTION C: TRACKER PANEL
*/
.mb-7 div[title='Toggle Tracker'],
.mb-7 div[title='Update Position'],
.mb-7 div[title='Toggle Freeze Position'] {
/* Tracker panel button size */
width: 60px !important;
height: 60px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
//z-index: 11 !important; Prioritize over close button
}
.mb-7 div[title='Toggle Tracker'] svg,
.mb-7 div[title='Update Position'] svg,
.mb-7 div[title='Toggle Freeze Position'] svg {
width: 2.2rem !important;
height: 2.2rem !important;
}
/* Reduce tracker panel width so it's easier to click out */
.max-w-xl {
max-width:25rem;
}
/* Remove tracker panel header and close button */
div[class*="min-h-"][class*="60px"] {
min-height: 0px !important;
height: 0px !important;
}
div[role="button"][title="Close Tracker Menu"] svg {
width: 2.2rem !important;
height: 2.2rem !important;
}
/* Increase close button clickbox */
div[role="button"][title="Close Tracker Menu"] {
width: 200px !important;
height: 200px !important;
justify-content: center !important;
}
/* Pin close button to the bottom of the screen to prevent accidental yomitan popups after closing */
div[title="Close Tracker Menu"] {
position: absolute !important;
bottom: -75px !important;
right: -75px !important;
//z-index: 10 !important; 1 lower than tracker buttons (no daily goal)
}
/* Remove margin between "Remaining Time Left" and tracker buttons for better visual balance + help content fit the screen */
.mb-6 {
margin-bottom: 0rem !important;
}
/* Reduce top margin to help content fit on screen without scrolling */
div.mt-4:nth-child(3){
margin-top: 1rem !important;
}
/* Remove bottom margin to help content fit on screen without scrolling */
div.mb-7:last-child {
margin-bottom: 0rem !important;
}
/*
* SECTION D: FOOTER
*/
#ttu-page-footer {
height: auto !important;
pointer-events: none !important; /* Uncomment this to disable footer taps from toggling the progress bar */
}
#ttu-page-footer > div:first-child {
flex-wrap: wrap !important;
width: auto !important;
}
.book-content--writing-vertical-rl .main,
.book-content--writing-vertical-rl .ttu-book-body-wrapper:not(:has(.main)) {
padding-bottom: 30px !important; /* Padding between the footer and text */
box-sizing: border-box !important;
}
#ttu-page-footer button,
#ttu-page-footer div[role="button"]:not([title="Click to copy Progress"]) {
display: flex !important;
align-items: center !important;
justify-content: center !important;
flex-shrink: 0;
height: 34px !important; /* Clickbox height from bottom margin */
margin-bottom: 5px !important; /* Additional bottom margin for lowering clickbox */
margin-inline: 13px !important; /* Footer icon spacing */
}
/* Footer icon size */
#ttu-page-footer svg {
width: 1.2rem !important;
height: 1.2rem !important;
}
/* Progress tracker visual changes */
div[title="Click to copy Progress"] {
padding: 6px 6px !important;
}
/*
* SECTION E: WHISPERSYNC FLYOUT MENU
*/
/* Whispersync menu icon size */
#ttu-page-footer .ttu-whispersync-container svg {
width: 1.4rem !important;
height: 1.4rem !important;
}
/* Whispersync menu icon spacing fix */
#ttu-page-footer .ttu-whispersync-container button,
#ttu-page-footer .ttu-whispersync-container div[role="button"] {
width: auto !important; height: auto !important;
margin: 0 !important;
}
/* Whispersync menu icon wrapping fix */
.ttu-whispersync-container.side-menu .flex.items-center.w-full.m-t-b {
display: flex !important;
flex-wrap: wrap !important;
justify-content: center !important;
}
#ttu-page-footer .ttu-whispersync-container.side-menu .m-t-b button {
width: 60px !important; /* Flyout player button spacing */
height: 60px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
/* Flyout player button size */
#ttu-page-footer .ttu-whispersync-container.side-menu .m-t-b button svg {
width: 2.2rem !important;
height: 2.2rem !important;
}
/* Speed change flex menu fix */
.ttu-whispersync-container .playback-display {
align-items: center !important;
}
.ttu-whispersync-container .playback-rates {
width:fit-content !important;
}
.ttu-whispersync-container .m-x-s {
margin-left:0rem;
margin-right:0rem;
width:35px;
text-align: center !important;
}
/* Move speed change button before mute to accodomate the larger popup */
.ttu-whispersync-container .flex.items-center.w-full.m-t-b {
button[title="Toggle mute"] { order: 6 !important; }
}
/* SECTION F: IMAGE GALLERY EINK REWORK */
/* 1. Spoiler label */
button.spoiler-label.svelte-17iwc5x {
/* ネタバレ text */
color: rgba(0, 0, 0, 0.6) !important;
font-size: 32px !important;
/* Fullscreen clickbox */
background-color: transparent !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
/* Remove site defaults that cause misalignment */
transform: none !important;
margin: 0 !important;
border: none !important;
z-index: 5 !important;
cursor: pointer !important;
}
.spoiler.svelte-17iwc5x img.svelte-17iwc5x{
filter: blur(10px); /* Reduced image blur for E-Ink */
}
/* 2. Close gallery button */
div.sticky.top-0.flex.justify-between.p-2.z-10 {
background-color: transparent !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
pointer-events: none !important;
z-index: 100 !important;
}
button[title="Close Image Gallery"] {
/* Clickbox settings */
pointer-events: auto !important;
width: 70px !important;
height: 80px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
button[title="Close Image Gallery"] svg {
/* Icon Settings */
width: 32px !important;
height: 32px !important;
color: rgba(0, 0, 0, 0.6) !important;
}
/* 3. Gallery layout */
.overflow-auto.p-2 {
display: flex !important;
flex-direction: column !important;
overflow-y: scroll !important;
height: 100vh !important;
padding: 0 !important;
-ms-overflow-style: none !important;
scrollbar-width: none !important;
scroll-snap-type: none !important;
scroll-behavior: auto !important;
touch-action: none !important;
}
.overflow-auto.p-2 > button {
min-height: 100vh !important;
height: 100vh !important;
width: 100vw !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
margin: 0 !important;
flex-shrink: 0 !important;
}
.overflow-auto.p-2 img {
max-height: 100vh !important;
max-width: 100vw !important;
object-fit: contain !important;
}
/* 4. Custom Gallery Counter */
#ttu-gallery-counter {
position: fixed !important;
bottom: 10px !important;
left: 10px !important;
background-color: rgba(0, 0, 0, 0.6) !important;
color: #ffffff !important;
padding: 3px 7px !important;
border-radius: 3px !important;
font-size: 12px !important;
z-index: 1000 !important;
writing-mode: horizontal-tb !important;
pointer-events: none !important;
}
`;
GM_addStyle(allCustomStyles);
// --- SECTION 2: JAVASCRIPT PAGE TURN FUNCTIONS ---
/* Settings */
const DISABLE_READER_SWIPE = true; // (Optional) Disables vertical swipe page turning while reading. Vertical swipe page turns fix the page turn buttons on iReader devices, not necessary on others
const SWIPE_THRESHOLD = 30;
const SCROLL_FACTOR_SETTINGS = 0.75;
const SCROLL_FACTOR_LIBRARY = 0.72; // Scroll 2 books at a time. Adjust for other devices
/* -------- */
let isProcessing = false;
let touchstartY = 0;
let galleryIndex = 0;
let currentCtx = 'READER';
// 1. Context Detection
const getAppContext = () => {
const url = window.location.href.toLowerCase();
if (document.querySelector('.overflow-auto.p-2')) return 'GALLERY';
else if (url.includes('manage')) return 'LIBRARY';
else if (url.includes('settings')) return 'SETTINGS';
else if (document.querySelector('.ttu-whispersync-container.side-menu:not(.hidden)')) return 'WHISPERSYNC';
else return 'READER';
};
// 2. Key Press Function
const sendKey = (keyName) => {
const keyCode = keyName === 'PageUp' ? 33 : 34;
const opts = { key: keyName, code: keyName, keyCode, which: keyCode, bubbles: true };
document.dispatchEvent(new KeyboardEvent('keydown', opts));
document.dispatchEvent(new KeyboardEvent('keyup', opts));
};
// 3. Touch Interceptor (Swipe -> Key)
document.addEventListener('touchstart', e => {
currentCtx = getAppContext();
touchstartY = e.changedTouches[0].screenY;
//console.log('Context:', currentCtx);
isProcessing = false;
}, { capture: true, passive: true });
document.addEventListener('touchmove', e => {
if (currentCtx === 'READER' && DISABLE_READER_SWIPE) {
return;
}
else if (currentCtx !== 'READER') {
if (e.cancelable) e.preventDefault();
e.stopImmediatePropagation();
}
if (isProcessing) return;
const diffY = e.changedTouches[0].screenY - touchstartY;
if (Math.abs(diffY) > SWIPE_THRESHOLD) {
isProcessing = true;
sendKey(diffY < 0 ? 'PageDown' : 'PageUp');
}
}, { capture: true, passive: false });
// 4. Reduced Motion Scrolling
window.addEventListener('keydown', (e) => {
const ctx = getAppContext();
const isDown = e.key === 'PageDown' || e.key === 'ArrowDown';
const isUp = e.key === 'PageUp' || e.key === 'ArrowUp';
if (!isDown && !isUp) return;
// Gallery Scrolling
if (ctx === 'GALLERY') {
e.preventDefault();
e.stopImmediatePropagation();
const gallery = document.querySelector('.overflow-auto.p-2');
const images = getImageButtons();
if (images.length === 0) return;
const oldIndex = galleryIndex;
if (isDown && galleryIndex < images.length - 1) galleryIndex++;
else if (isUp && galleryIndex > 0) galleryIndex--;
if (oldIndex !== galleryIndex) {
gallery.scrollTo({
top: galleryIndex * window.innerHeight,
behavior: 'auto'
});
updateGalleryCounter();
}
}
// Book Manager + Settings Scrolling
else if (ctx === 'LIBRARY' || ctx === 'SETTINGS') {
e.preventDefault();
e.stopImmediatePropagation();
const factor = (ctx === 'SETTINGS') ? SCROLL_FACTOR_SETTINGS : SCROLL_FACTOR_LIBRARY;
//console.log(`Scroll Context: ${ctx} | Applied Factor: ${factor}`);
const distance = factor * window.innerHeight;
window.scrollBy({ top: isDown ? distance : -distance, behavior: 'auto' });
}
// Whispersync Menu Scrolling
else if (ctx === 'WHISPERSYNC') {
e.preventDefault();
e.stopImmediatePropagation();
const panels = document.querySelectorAll('.ttu-whispersync-container.side-menu .overflow-auto');
const settingsPanel = panels[panels.length - 1];
const subtitlePanel = document.querySelector('.ttu-whispersync-container.subtitle-container');
const panel = (subtitlePanel && subtitlePanel.scrollHeight > subtitlePanel.clientHeight)
? subtitlePanel
: settingsPanel;
if (!panel) return;
const distance = 0.75 * panel.clientHeight;
panel.scrollTop += isDown ? distance : -distance;
}
}, { capture: true });
// 5. Custom Gallery Functions
const getImageButtons = () => {
const gallery = document.querySelector('.overflow-auto.p-2');
if (!gallery) return [];
return Array.from(gallery.querySelectorAll(':scope > button')).filter(btn =>
btn.querySelector('img') || btn.hasAttribute('data-image-index')
);
};
const updateGalleryCounter = () => {
const images = getImageButtons();
let counter = document.getElementById('ttu-gallery-counter');
if (images.length === 0) { if (counter) counter.remove(); return; }
if (!counter) {
counter = document.createElement('div');
counter.id = 'ttu-gallery-counter';
document.body.appendChild(counter);
}
counter.innerText = `${galleryIndex + 1} / ${images.length}`;
};
const galleryObs = new MutationObserver(() => {
const g = document.querySelector('.overflow-auto.p-2');
const counter = document.getElementById('ttu-gallery-counter');
if (g) {
if (!g.dataset.jumpAttached) {
g.dataset.jumpAttached = "true";
galleryIndex = 0;
g.scrollTop = 0;
setTimeout(updateGalleryCounter, 50);
}
} else {
if (counter) counter.remove();
}
});
galleryObs.observe(document.body, { childList: true, subtree: true });
// --- (OPTIONAL): OFFLINE POPUP REMOVER ---
const uiObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes.length) {
// Task 1: Hide Load Error
const h2 = document.querySelector('h2.weight-medium');
if (h2 && h2.innerText === "Load Error") {
h2.closest('div.z-50').style.display = 'none';
}
}
}
});
uiObserver.observe(document.body, { childList: true, subtree: true });
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment