Reader Mode with dark mode, translation, highlighting, editor, screenshot, PDF export + Google Translate
// ==UserScript==
// @name Reader with all features + Google Translator
// @namespace http://greasemonkey.net/
// @version 08.10.2025
// @description Reader Mode with dark mode, translation, highlighting, editor, screenshot, PDF export + Google Translate
// @author Sspuramcopideep
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @run-at document-start
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// ========== GOOGLE TRANSLATOR INTEGRATION ==========
const key = encodeURIComponent('gimmick : ready to translate ');
if (window[key]) return;
window[key] = true;
const initGoogleTranslate = function() {
// Check if already initialized
if (document.getElementById('google_translate_elm')) return;
const script = document.createElement('script');
script.src = '//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit';
script.async = true;
document.head.appendChild(script);
const div = document.createElement('div');
div.id = 'google_translate_elm';
div.setAttribute('style', 'position:fixed;bottom:2vw;left:6vw;user-select:none;z-index:2147483647;background:transparent!important');
document.documentElement.appendChild(div);
const style = document.createElement('style');
style.innerHTML = `
.skiptranslate iframe {display:none!important}
.goog-te-gadget-simple img {display:none!important}
.goog-te-gadget-icon {display: none!important}
.goog-te-gadget-simple {
background:transparent!important;
border:1px solid transparent!important;
border-radius:6px!important;
padding:6px 10px!important;
font-size:12px!important;
color:#696969!important;
cursor: pointer;
}
.goog-te-gadget-simple:hover {
background: rgba(105, 105, 105, 0.1) !important;
}
.goog-te-gadget-simple span {
color:#696969!important;
}
.goog-te-banner-frame { display: none !important; }
.goog-te-menu-frame { z-index: 1000000 !important; }
.goog-tooltip { display: none !important; }
.goog-tooltip:hover { display: none !important; }
#goog-gt- { display: none !important; }
.goog-te-balloon-frame { display: none !important; }
`;
document.head.appendChild(style);
};
window.googleTranslateElementInit = function() {
if (typeof google !== 'undefined' && google.translate) {
new google.translate.TranslateElement({
pageLanguage: 'auto',
includedLanguages: 'en,mr',
layout: google.translate.TranslateElement.InlineLayout.SIMPLE,
autoDisplay: false
}, 'google_translate_elm');
}
};
// Register menu command for Google Translate
if (typeof GM_registerMenuCommand !== 'undefined') {
GM_registerMenuCommand("Google Translate", initGoogleTranslate, "g");
}
// Auto-initialize Google Translate
initGoogleTranslate();
// ========== READER MODE ==========
// Create floating transparent box for Reader button
const readerBox = document.createElement('div');
Object.assign(readerBox.style, {
position: 'fixed',
bottom: '10px',
right: '24px',
padding: '2px 6px',
background: 'transparent',
border: '1px solid transparent',
borderRadius: '6px',
zIndex: '2147483647'
});
// Create Reader button inside the box
const readerBtn = document.createElement('button');
readerBtn.textContent = 'Reader';
Object.assign(readerBtn.style, {
border: 'none',
background: 'transparent',
color: '#696969',
fontSize: '12px',
fontWeight: 'normal',
cursor: 'pointer',
fontFamily: 'system-ui, sans-serif',
padding: '0',
margin: '0'
});
readerBtn.onmouseover = () => readerBtn.style.textDecoration = 'underline';
readerBtn.onmouseout = () => readerBtn.style.textDecoration = 'none';
readerBox.appendChild(readerBtn);
document.body.appendChild(readerBox);
readerBtn.onclick = async () => {
const loadScript = url => new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
try {
if (!window.Readability) {
await loadScript('https://cdn.jsdelivr.net/npm/@mozilla/[email protected]/Readability.js');
}
if (!window.DOMPurify) {
await loadScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js');
}
const clone = document.cloneNode(true);
const article = new Readability(clone).parse();
if (!article) {
alert('Reader Mode: No article found on this page.');
return;
}
const css = `
body {
font: 18px/1.40 system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
max-width: 48rem;
margin: 0 auto;
padding: 1rem;
background: #fff;
color: #111;
transition: background 0.3s, color 0.3s;
touch-action: pan-y;
}
h1, h2, h3, h4, h5, h6 {
font-size: 1.0em !important;
line-height: 1.20;
margin: 1rem 0 0.5rem;
font-weight: 700;
}
h1 { margin: 0 0 1rem; }
h2, h3 { margin: 1.2rem 0 0.6rem; }
img, video {
max-width: 100%;
height: auto;
}
figure {
margin: 1rem 0;
}
pre, code {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
a {
color: #0b57d0;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* Simple translation styles */
.original-sentence {
margin-bottom: 4px;
}
.translated-sentence {
margin-bottom: 16px;
color: #0000FF;
font-style: italic;
}
.highlight-yellow { background-color: #ffeb3b !important; color: #000 !important; }
.highlight-blue { background-color: #2196f3 !important; color: #fff !important; }
.highlight-green { background-color: #4caf50 !important; color: #fff !important; }
.highlight-pink { background-color: #e91e63 !important; color: #fff !important; }
/* Reader controls */
#reader-controls {
position: fixed;
bottom: 10px;
right: 10px;
background: transparent !important;
border-radius: 8px;
padding: 6px;
display: flex;
gap: 3px;
z-index: 9999;
font-family: system-ui, sans-serif;
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
border: none !important;
box-shadow: none !important;
transition: opacity 0.3s ease, transform 0.3s ease;
}
#reader-controls.hidden {
opacity: 0;
transform: translateY(20px);
pointer-events: none;
}
/* Control buttons */
#reader-controls button {
padding: 4px 6px !important;
border: none;
border-radius: 4px;
cursor: pointer;
background: rgba(255, 255, 255, 0.9) !important;
color: #0b57d0 !important;
font-size: 11px !important;
transition: all 0.2s;
min-width: 30px !important;
min-height: 30px !important;
max-width: 40px !important;
max-height: 30px !important;
white-space: nowrap;
flex-shrink: 0;
border: 1px solid rgba(11, 87, 208, 0.3) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
font-weight: bold !important;
}
#reader-controls button:hover {
background: rgba(11, 87, 208, 0.1) !important;
border-color: rgba(11, 87, 208, 0.8) !important;
transform: scale(1.1);
}
#reader-controls button:active {
transform: scale(0.95);
}
#reader-controls button:disabled {
background: rgba(11, 87, 208, 0.05) !important;
color: rgba(11, 87, 208, 0.4) !important;
cursor: not-allowed;
border-color: rgba(11, 87, 208, 0.2) !important;
transform: none !important;
}
/* Highlight controls */
.highlight-controls {
position: fixed;
top: 10px;
right: 10px;
background: rgba(255, 255, 255, 0.95) !important;
backdrop-filter: blur(10px) !important;
border-radius: 8px;
padding: 6px;
display: none;
flex-direction: row;
gap: 3px;
z-index: 10000;
border: 1px solid rgba(0, 0, 0, 0.2) !important;
flex-wrap: wrap;
transition: opacity 0.3s ease;
box-shadow: 0 2px 10px rgba(0,0,0,0.1) !important;
}
.highlight-controls button {
padding: 4px 6px !important;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 10px !important;
transition: all 0.2s;
white-space: nowrap;
border: 1px solid rgba(0, 0, 0, 0.2) !important;
min-width: 35px !important;
min-height: 25px !important;
}
.highlight-controls button:hover {
transform: scale(1.05);
border-color: rgba(0, 0, 0, 0.5) !important;
}
/* Editor styles */
.edit-mode [contenteditable="true"] {
outline: 2px dashed #0b57d0 !important;
outline-offset: 2px;
background: rgba(11, 87, 208, 0.05) !important;
border-radius: 4px;
padding: 2px 4px;
margin: -2px -4px;
transition: all 0.2s;
}
.edit-mode [contenteditable="true"]:focus {
outline: 2px solid #0b57d0 !important;
background: rgba(11, 87, 208, 0.1) !important;
}
.edit-mode img {
outline: 3px dashed #e91e63 !important;
outline-offset: 3px;
cursor: pointer;
transition: all 0.2s;
position: relative;
}
.edit-mode img:hover {
outline: 3px solid #e91e63 !important;
background: rgba(233, 30, 99, 0.1) !important;
}
.image-delete-btn {
position: absolute;
top: -8px;
right: -8px;
background: #e91e63 !important;
color: white !important;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
font-size: 12px;
font-weight: bold;
cursor: pointer;
z-index: 1000;
display: none;
}
.image-wrapper {
position: relative;
display: inline-block;
}
.dark-mode {
background: #111 !important;
color: #eee !important;
}
.dark-mode .translated-sentence {
color: #ccc;
}
/* Dark mode for controls */
.dark-mode #reader-controls button {
color: #4fc3f7 !important;
border-color: rgba(79, 195, 247, 0.4) !important;
background: rgba(0, 0, 0, 0.7) !important;
}
.dark-mode #reader-controls button:hover {
background: rgba(79, 195, 247, 0.2) !important;
border-color: rgba(79, 195, 247, 0.8) !important;
}
.dark-mode #reader-controls button:disabled {
color: rgba(79, 195, 247, 0.4) !important;
border-color: rgba(79, 195, 247, 0.2) !important;
background: rgba(0, 0, 0, 0.4) !important;
}
.dark-mode .highlight-controls {
background: rgba(40, 40, 40, 0.95) !important;
border-color: rgba(255, 255, 255, 0.3) !important;
}
.dark-mode .highlight-controls button {
border-color: rgba(255, 255, 255, 0.3) !important;
color: #fff !important;
}
.dark-mode .highlight-controls button:hover {
border-color: rgba(255, 255, 255, 0.7) !important;
}
.pdf-exporting, .screenshot-capturing {
opacity: 0.7;
pointer-events: none;
}
.pdf-loading, .screenshot-loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 20px 30px;
border-radius: 10px;
z-index: 10000;
font-size: 16px;
font-weight: bold;
text-align: center;
}
.translation-status, .screenshot-status, .editor-status {
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
background: rgba(76, 175, 80, 0.9);
color: white;
padding: 10px 20px;
border-radius: 20px;
z-index: 10000;
font-size: 14px;
font-weight: bold;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.screenshot-status {
background: rgba(156, 39, 176, 0.9);
}
.editor-status {
background: rgba(255, 152, 0, 0.9);
}
/* Selection Area Styles */
.screenshot-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
z-index: 10000;
cursor: crosshair;
touch-action: none;
}
.selection-area {
position: fixed;
border: 4px solid #ff0000;
background: rgba(255, 0, 0, 0.3);
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.4);
z-index: 10001;
pointer-events: none;
}
.selection-info {
position: fixed;
background: #000;
color: white;
padding: 10px 14px;
border-radius: 8px;
font-size: 14px;
font-weight: bold;
z-index: 10002;
pointer-events: none;
border: 2px solid #fff;
}
.screenshot-options {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
z-index: 10003;
text-align: center;
min-width: 180px;
max-width: 90%;
}
.screenshot-options h3 {
margin: 0 0 10px 0;
color: #333;
font-size: 14px;
}
.screenshot-options p {
margin: 0 0 12px 0;
color: #666;
font-size: 12px;
}
.screenshot-options button {
margin: 4px;
padding: 8px 12px;
border: none;
border-radius: 6px;
cursor: pointer;
background: #2196f3;
color: white;
font-size: 12px;
transition: background 0.3s;
min-width: 100px;
min-height: 36px;
}
.screenshot-options button:hover {
background: #1976d2;
}
.screenshot-options button.cancel {
background: #f44336;
}
.screenshot-options button.cancel:hover {
background: #d32f2f;
}
/* Three-tap indicator */
.three-tap-indicator {
position: fixed;
bottom: 60px;
right: 10px;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 8px 12px;
border-radius: 6px;
font-size: 11px;
zIndex: 9998;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease;
font-family: system-ui, sans-serif;
}
.three-tap-indicator.show {
opacity: 1;
}
/* Google Translate Styles */
.goog-te-gadget-simple {
background: rgba(255, 255, 255, 0.9) !important;
border: 1px solid rgba(11, 87, 208, 0.3) !important;
border-radius: 4px !important;
padding: 4px 6px !important;
font-size: 11px !important;
color: #0b57d0 !important;
cursor: pointer;
}
.goog-te-gadget-simple:hover {
background: rgba(11, 87, 208, 0.1) !important;
}
.goog-te-menu-frame {
z-index: 1000000 !important;
}
.goog-te-banner-frame {
display: none !important;
}
.skiptranslate iframe {
display: none !important;
}
.goog-te-gadget img {
display: none !important;
}
/* Mobile styles */
@media (max-width: 768px) {
#reader-controls {
bottom: 5px;
right: 5px;
padding: 4px;
gap: 2px;
}
#reader-controls button {
padding: 3px 5px !important;
font-size: 10px !important;
min-width: 28px !important;
min-height: 28px !important;
max-width: 35px !important;
}
.selection-area {
border: 5px solid #ff0000;
background: rgba(255, 0, 0, 0.4);
}
.selection-info {
font-size: 16px;
padding: 12px 16px;
}
.highlight-controls {
flex-direction: column;
right: 5px;
top: 5px;
}
.three-tap-indicator {
bottom: 50px;
right: 5px;
font-size: 10px;
padding: 6px 10px;
}
.image-delete-btn {
width: 24px;
height: 24px;
font-size: 14px;
}
}
`;
const html = `
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>${article.title || document.title || 'Reader Mode'}</title>
<style>${css}</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"><\/script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"><\/script>
</head>
<body>
<article id="article-content">
<h1>${article.title || ''}</h1>
${window.DOMPurify ? DOMPurify.sanitize(article.content) : article.content}
</article>
<div id="reader-controls">
<button id="toggle-dark">🌙</button>
<button id="font-up">A+</button>
<button id="font-down">A−</button>
<button id="translate-btn">en→mr</button>
<button id="highlight-btn">🖍️</button>
<button id="editor-btn">✏️</button>
<button id="screenshot-btn">📸</button>
<button id="pdf-btn">📄</button>
<button id="google-translate-btn">🌐</button>
</div>
<div id="three-tap-indicator" class="three-tap-indicator">
Triple-tap to show controls
</div>
<div id="highlight-controls" class="highlight-controls">
<button data-color="yellow" style="background:#ffeb3b;color:#000">Y</button>
<button data-color="blue" style="background:#2196f3;color:#fff">B</button>
<button data-color="green" style="background:#4caf50;color:#fff">G</button>
<button data-color="pink" style="background:#e91e63;color:#fff">P</button>
<button id="clear-highlights" style="background:#666;color:#fff">Clear</button>
<button id="hide-highlight-controls" style="background:#333;color:#fff">X</button>
</div>
<script>
// Global variables
let dark = false;
let fontSize = 32;
let isTranslated = false;
let originalContent = '';
let currentHighlightColor = 'yellow';
let isHighlightMode = false;
let isEditMode = false;
let controlsVisible = true;
let tapCount = 0;
let tapTimer = null;
let googleTranslateLoaded = false;
// Three-tap gesture detection
const initThreeTapGesture = () => {
document.addEventListener('click', (e) => {
if (!e.target.closest('#reader-controls') && !e.target.closest('#highlight-controls')) {
tapCount++;
if (tapTimer) clearTimeout(tapTimer);
tapTimer = setTimeout(() => tapCount = 0, 500);
if (tapCount === 3) {
toggleControlsVisibility();
tapCount = 0;
clearTimeout(tapTimer);
}
}
});
document.addEventListener('touchend', (e) => {
if (!e.target.closest('#reader-controls') && !e.target.closest('#highlight-controls')) {
tapCount++;
if (tapTimer) clearTimeout(tapTimer);
tapTimer = setTimeout(() => tapCount = 0, 500);
if (tapCount === 3) {
toggleControlsVisibility();
tapCount = 0;
clearTimeout(tapTimer);
e.preventDefault();
}
}
});
};
// Toggle controls visibility
const toggleControlsVisibility = () => {
const controls = document.getElementById('reader-controls');
const indicator = document.getElementById('three-tap-indicator');
controlsVisible = !controlsVisible;
if (controlsVisible) {
controls.classList.remove('hidden');
indicator.classList.remove('show');
} else {
controls.classList.add('hidden');
indicator.classList.add('show');
setTimeout(() => indicator.classList.remove('show'), 2000);
}
if (!controlsVisible) hideHighlightControls();
};
// Update font size
const updateFont = () => {
document.body.style.fontSize = fontSize + 'px';
};
// Show status message
const showStatus = (message, type = 'translation') => {
const statusDiv = document.createElement('div');
statusDiv.className = type + '-status';
statusDiv.textContent = message;
document.body.appendChild(statusDiv);
setTimeout(() => {
if (document.contains(statusDiv)) document.body.removeChild(statusDiv);
}, 3000);
};
// ========== GOOGLE TRANSLATE IN READER ==========
const initGoogleTranslateInReader = () => {
const googleTranslateBtn = document.getElementById('google-translate-btn');
googleTranslateBtn.addEventListener('click', () => {
if (googleTranslateLoaded) {
// If already loaded, just show the dropdown
const select = document.querySelector('.goog-te-combo');
if (select) {
select.focus();
select.click();
} else {
// Re-initialize if element is missing
initializeGoogleTranslate();
}
} else {
// Load Google Translate for the first time
initializeGoogleTranslate();
}
});
};
const initializeGoogleTranslate = () => {
showStatus('Loading Google Translate...');
// Create container for Google Translate
const container = document.createElement('div');
container.id = 'google_translate_reader';
container.style.cssText = 'position:fixed;bottom:10px;left:10px;z-index:696969;';
document.body.appendChild(container);
// Load Google Translate script
const script = document.createElement('script');
script.src = '//translate.google.com/translate_a/element.js?cb=googleTranslateReaderInit';
script.async = true;
script.onload = () => {
showStatus('Google Translate loaded successfully');
googleTranslateLoaded = true;
};
script.onerror = () => {
showStatus('Failed to load Google Translate', 'translation');
};
document.head.appendChild(script);
};
window.googleTranslateReaderInit = function() {
if (typeof google !== 'undefined' && google.translate && google.translate.TranslateElement) {
try {
new google.translate.TranslateElement({
pageLanguage: 'auto',
includedLanguages: 'en,mr',
layout: google.translate.TranslateElement.InlineLayout.SIMPLE,
autoDisplay: false
}, 'google_translate_reader');
// Trigger the dropdown automatically
setTimeout(() => {
const select = document.querySelector('.goog-te-combo');
if (select) {
select.focus();
select.click();
showStatus('Select language from dropdown');
}
}, 500);
} catch (error) {
showStatus('Google Translate initialization failed', 'translation');
}
} else {
showStatus('Google Translate API not available', 'translation');
}
};
// ========== BASIC EDITOR FUNCTION ==========
const toggleEditMode = () => {
isEditMode = !isEditMode;
const article = document.getElementById('article-content');
if (isEditMode) {
const editableElements = article.querySelectorAll('h1, h2, h3, h4, h5, h6, p, li, figcaption, blockquote, .original-sentence, .translated-sentence');
editableElements.forEach(el => el.setAttribute('contenteditable', 'true'));
const images = article.querySelectorAll('img');
images.forEach(img => {
if (!img.hasAttribute('data-original-parent')) {
img.setAttribute('data-original-parent', 'true');
const deleteBtn = document.createElement('button');
deleteBtn.className = 'image-delete-btn';
deleteBtn.innerHTML = '×';
deleteBtn.title = 'Delete image';
deleteBtn.onclick = (e) => {
e.stopPropagation();
if (confirm('Delete this image?')) {
img.remove();
showStatus('Image deleted', 'editor');
}
};
const wrapper = document.createElement('div');
wrapper.className = 'image-wrapper';
img.parentNode.insertBefore(wrapper, img);
wrapper.appendChild(img);
wrapper.appendChild(deleteBtn);
}
const wrapper = img.parentNode;
const deleteBtn = wrapper.querySelector('.image-delete-btn');
img.addEventListener('mouseenter', () => deleteBtn.style.display = 'block');
wrapper.addEventListener('mouseleave', () => deleteBtn.style.display = 'none');
img.addEventListener('click', (e) => {
e.stopPropagation();
deleteBtn.style.display = 'block';
setTimeout(() => {
if (!wrapper.matches(':hover')) deleteBtn.style.display = 'none';
}, 3000);
});
});
document.body.classList.add('edit-mode');
showStatus('Edit mode ON - Click text to edit, hover images to delete', 'editor');
} else {
const editableElements = article.querySelectorAll('[contenteditable="true"]');
editableElements.forEach(el => el.removeAttribute('contenteditable'));
const deleteButtons = article.querySelectorAll('.image-delete-btn');
deleteButtons.forEach(btn => btn.style.display = 'none');
document.body.classList.remove('edit-mode');
showStatus('Edit mode OFF', 'editor');
}
};
// ========== SIMPLIFIED TRANSLATION FUNCTIONS ==========
const translateText = async (text, targetLang) => {
return new Promise((resolve) => {
const url = \`https://api.mymemory.translated.net/get?q=\${encodeURIComponent(text)}&langpair=auto|\${targetLang}\`;
fetch(url)
.then(response => response.json())
.then(data => {
if (data.responseStatus === 200) {
resolve(data.responseData.translatedText);
} else {
const googleUrl = \`https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=\${targetLang}&dt=t&q=\${encodeURIComponent(text)}\`;
fetch(googleUrl)
.then(res => res.json())
.then(googleData => {
const translated = googleData[0].map(item => item[0]).join('');
resolve(translated || text);
})
.catch(() => resolve(text));
}
})
.catch(() => resolve(text));
});
};
const splitIntoSentences = (text) => {
if (!text || text.trim().length === 0) return [];
return text.split(/(?<=[.!?])\\s+/).filter(s => s.trim().length > 0).map(s => s.trim());
};
const detectLanguage = (text) => /[\\u0900-\\u097F]/.test(text) ? 'mr' : 'en';
const processParagraph = async (element, fromLang, toLang) => {
const originalText = element.textContent.trim();
if (originalText.length === 0) return '';
const sentences = splitIntoSentences(originalText);
if (sentences.length === 0) return originalText;
let translatedHTML = '';
for (let i = 0; i < sentences.length; i++) {
const originalSentence = sentences[i];
const translatedSentence = await translateText(originalSentence, toLang);
translatedHTML += '<div class="original-sentence">' + originalSentence + '</div>';
translatedHTML += '<div class="translated-sentence">' + translatedSentence + '</div>';
}
return translatedHTML;
};
const translateArticle = async (fromLang, toLang) => {
const article = document.getElementById('article-content');
const translateBtn = document.getElementById('translate-btn');
translateBtn.disabled = true;
translateBtn.textContent = 'Translating...';
try {
if (!originalContent) originalContent = article.innerHTML;
const elements = article.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, figcaption, blockquote');
let processedCount = 0;
for (const element of elements) {
const originalText = element.textContent.trim();
if (originalText.length > 0 && !element.querySelector('.translated-sentence')) {
const html = await processParagraph(element, fromLang, toLang);
if (html) {
element.innerHTML = html;
processedCount++;
}
}
}
translateBtn.textContent = toLang === 'mr' ? 'mr→en' : 'en→mr';
isTranslated = true;
showStatus(\`Bilingual translation completed! (\${processedCount} paragraphs)\`);
} catch (error) {
showStatus('Translation failed. Please try again.', 'translation');
} finally {
translateBtn.disabled = false;
}
};
const toggleTranslation = async () => {
const article = document.getElementById('article-content');
const translateBtn = document.getElementById('translate-btn');
if (!isTranslated) {
const sampleText = article.textContent.substring(0, 200);
const detectedLang = detectLanguage(sampleText);
const fromLang = detectedLang;
const toLang = detectedLang === 'en' ? 'mr' : 'en';
await translateArticle(fromLang, toLang);
} else {
if (originalContent) article.innerHTML = originalContent;
translateBtn.textContent = 'en→mr';
isTranslated = false;
showStatus('Bilingual translation reverted');
}
};
// ========== SCREENSHOT FUNCTIONS ==========
let isSelectingArea = false;
let selectionDiv = null, infoDiv = null, overlayDiv = null;
let startX = 0, startY = 0, endX = 0, endY = 0;
function startAreaSelection() {
if (isSelectingArea) {
cleanupSelection();
return;
}
isSelectingArea = true;
overlayDiv = document.createElement('div');
overlayDiv.className = 'screenshot-overlay';
selectionDiv = document.createElement('div');
selectionDiv.className = 'selection-area';
selectionDiv.style.display = 'none';
infoDiv = document.createElement('div');
infoDiv.className = 'selection-info';
infoDiv.textContent = 'Touch and drag to select area';
infoDiv.style.display = 'none';
document.body.appendChild(overlayDiv);
document.body.appendChild(selectionDiv);
document.body.appendChild(infoDiv);
overlayDiv.addEventListener('mousedown', startDrag);
overlayDiv.addEventListener('touchstart', startTouchDrag);
showStatus('Touch and drag to select any area on the page', 'screenshot');
}
function startDrag(e) {
startX = e.clientX;
startY = e.clientY;
startSelection(startX, startY);
document.addEventListener('mousemove', updateDrag);
document.addEventListener('mouseup', endDrag);
}
function startTouchDrag(e) {
e.preventDefault();
const touch = e.touches[0];
startX = touch.clientX;
startY = touch.clientY;
startSelection(startX, startY);
document.addEventListener('touchmove', updateTouchDrag, { passive: false });
document.addEventListener('touchend', endTouchDrag);
}
function startSelection(x, y) {
selectionDiv.style.left = x + 'px';
selectionDiv.style.top = y + 'px';
selectionDiv.style.width = '0px';
selectionDiv.style.height = '0px';
selectionDiv.style.display = 'block';
infoDiv.style.display = 'block';
infoDiv.style.left = (x + 10) + 'px';
infoDiv.style.top = (y - 50) + 'px';
}
function updateDrag(e) { updateSelection(e.clientX, e.clientY); }
function updateTouchDrag(e) {
e.preventDefault();
const touch = e.touches[0];
updateSelection(touch.clientX, touch.clientY);
}
function updateSelection(x, y) {
endX = x;
endY = y;
const width = Math.abs(x - startX);
const height = Math.abs(y - startY);
const left = Math.min(x, startX);
const top = Math.min(y, startY);
selectionDiv.style.left = left + 'px';
selectionDiv.style.top = top + 'px';
selectionDiv.style.width = width + 'px';
selectionDiv.style.height = height + 'px';
infoDiv.textContent = Math.round(width) + '×' + Math.round(height) + ' px';
infoDiv.style.left = (left + width/2 - 40) + 'px';
infoDiv.style.top = (top - 50) + 'px';
}
function endDrag() {
finishSelection();
document.removeEventListener('mousemove', updateDrag);
document.removeEventListener('mouseup', endDrag);
}
function endTouchDrag() {
finishSelection();
document.removeEventListener('touchmove', updateTouchDrag);
document.removeEventListener('touchend', endTouchDrag);
}
function finishSelection() {
const width = Math.abs(endX - startX);
const height = Math.abs(endY - startY);
if (width > 50 && height > 50) {
const left = Math.min(startX, endX);
const top = Math.min(startY, endY);
showCaptureOptions(left, top, width, height);
} else {
showStatus('Selection too small', 'screenshot');
cleanupSelection();
}
}
function showCaptureOptions(left, top, width, height) {
const optionsDiv = document.createElement('div');
optionsDiv.className = 'screenshot-options';
optionsDiv.innerHTML = \`
<h3>Capture Selection?</h3>
<p>Size: \${width}×\${height} px</p>
<button id="captureAreaBtn">Capture Area</button>
<button id="captureFullBtn">Full Article</button>
<button id="cancelSelectionBtn" class="cancel">Cancel</button>
\`;
document.body.appendChild(optionsDiv);
document.getElementById('captureAreaBtn').onclick = () => {
document.body.removeChild(optionsDiv);
captureSelectedArea(left, top, width, height);
};
document.getElementById('captureFullBtn').onclick = () => {
document.body.removeChild(optionsDiv);
captureFullArticle();
};
document.getElementById('cancelSelectionBtn').onclick = () => {
document.body.removeChild(optionsDiv);
cleanupSelection();
};
}
async function captureSelectedArea(left, top, width, height) {
const btn = document.getElementById('screenshot-btn');
btn.disabled = true;
const loading = document.createElement('div');
loading.className = 'screenshot-loading';
loading.textContent = 'Capturing selected area...';
document.body.appendChild(loading);
try {
cleanupAllTempElements();
const controls = document.getElementById('reader-controls');
const originalDisplay = controls.style.display;
controls.style.display = 'none';
const canvas = await html2canvas(document.body, {
x: left + window.scrollX,
y: top + window.scrollY,
width: width,
height: height,
scale: 2,
useCORS: true,
backgroundColor: '#ffffff'
});
controls.style.display = originalDisplay;
const image = canvas.toDataURL('image/png');
const link = document.createElement('a');
const title = document.querySelector('h1')?.textContent || 'screenshot';
link.download = 'Reader_Area_' + title.replace(/[^a-z0-9]/gi, '_').substring(0, 20) + '.png';
link.href = image;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showStatus('Area screenshot saved!', 'screenshot');
} catch (error) {
showStatus('Capture failed - try full screenshot', 'screenshot');
captureFullArticle();
} finally {
btn.disabled = false;
cleanupSelection();
}
}
async function captureFullArticle() {
const btn = document.getElementById('screenshot-btn');
btn.disabled = true;
const loading = document.createElement('div');
loading.className = 'screenshot-loading';
loading.textContent = 'Capturing full article...';
document.body.appendChild(loading);
try {
cleanupAllTempElements();
const controls = document.getElementById('reader-controls');
const originalDisplay = controls.style.display;
controls.style.display = 'none';
const canvas = await html2canvas(document.getElementById('article-content'), {
scale: 2,
useCORS: true,
backgroundColor: '#ffffff'
});
controls.style.display = originalDisplay;
const image = canvas.toDataURL('image/png');
const link = document.createElement('a');
const title = document.querySelector('h1')?.textContent || 'screenshot';
link.download = 'Reader_Full_' + title.replace(/[^a-z0-9]/gi, '_').substring(0, 20) + '.png';
link.href = image;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showStatus('Full screenshot saved!', 'screenshot');
} catch (error) {
alert('Screenshot failed: ' + error.message);
} finally {
btn.disabled = false;
cleanupSelection();
}
}
function cleanupAllTempElements() {
const tempElements = document.querySelectorAll('.screenshot-overlay, .selection-area, .selection-info, .screenshot-options, .screenshot-status, .screenshot-loading');
tempElements.forEach(el => {
if (document.contains(el)) document.body.removeChild(el);
});
}
function cleanupSelection() {
if (overlayDiv && document.contains(overlayDiv)) document.body.removeChild(overlayDiv);
if (selectionDiv && document.contains(selectionDiv)) document.body.removeChild(selectionDiv);
if (infoDiv && document.contains(infoDiv)) document.body.removeChild(infoDiv);
isSelectingArea = false;
}
function showScreenshotMenu() {
if (isSelectingArea) {
cleanupSelection();
return;
}
const optionsDiv = document.createElement('div');
optionsDiv.className = 'screenshot-options';
optionsDiv.innerHTML = \`
<h3>Screenshot Options</h3>
<button id="selectAreaBtn">Select Area</button>
<button id="fullScreenshotBtn">Full Article</button>
<button id="closeMenuBtn" class="cancel">Cancel</button>
\`;
document.body.appendChild(optionsDiv);
document.getElementById('selectAreaBtn').onclick = () => {
document.body.removeChild(optionsDiv);
startAreaSelection();
};
document.getElementById('fullScreenshotBtn').onclick = () => {
document.body.removeChild(optionsDiv);
captureFullArticle();
};
document.getElementById('closeMenuBtn').onclick = () => document.body.removeChild(optionsDiv);
}
// ========== HIGHLIGHT FUNCTIONS ==========
const showHighlightControls = () => {
const highlightControls = document.getElementById('highlight-controls');
highlightControls.style.display = 'flex';
isHighlightMode = true;
};
const hideHighlightControls = () => {
const highlightControls = document.getElementById('highlight-controls');
highlightControls.style.display = 'none';
isHighlightMode = false;
};
const setHighlightColor = (color) => {
currentHighlightColor = color;
document.body.style.cursor = 'text';
};
const clearHighlights = () => {
const highlights = document.querySelectorAll('.highlight-yellow, .highlight-blue, .highlight-green, .highlight-pink');
highlights.forEach(el => {
const parent = el.parentNode;
parent.replaceChild(document.createTextNode(el.textContent), el);
parent.normalize();
});
showStatus('All highlights cleared');
};
// Initialize text selection for highlighting
const initializeHighlighting = () => {
document.addEventListener('mouseup', function() {
if (!isHighlightMode) return;
const selection = window.getSelection();
if (selection.toString().length > 0) {
const range = selection.getRangeAt(0);
const selectedText = range.extractContents();
const span = document.createElement('span');
span.className = 'highlight-' + currentHighlightColor;
span.appendChild(selectedText);
range.insertNode(span);
selection.removeAllRanges();
document.body.style.cursor = 'default';
isHighlightMode = false;
hideHighlightControls();
}
});
};
// ========== PDF EXPORT ==========
const exportToPDF = async () => {
const pdfBtn = document.getElementById('pdf-btn');
const article = document.getElementById('article-content');
pdfBtn.disabled = true;
pdfBtn.textContent = '⏳';
const loading = document.createElement('div');
loading.className = 'pdf-loading';
loading.textContent = 'Generating PDF...';
document.body.appendChild(loading);
try {
const title = document.querySelector('h1')?.textContent || 'Reader Mode Export';
const content = article.cloneNode(true);
const controls = content.querySelectorAll('#reader-controls, #highlight-controls');
controls.forEach(control => control.remove());
const pdfContainer = document.createElement('div');
pdfContainer.style.padding = '20px';
pdfContainer.style.fontFamily = 'sans-serif';
pdfContainer.style.lineHeight = '1.7';
pdfContainer.style.color = '#000';
pdfContainer.style.background = 'white';
pdfContainer.appendChild(content);
const pdfStyle = document.createElement('style');
pdfStyle.textContent = \`
.highlight-yellow, .highlight-blue, .highlight-green, .highlight-pink {
padding: 2px 4px; border-radius: 2px;
}
.original-sentence { margin-bottom: 4px; }
.translated-sentence { margin-bottom: 16px; color: #666; font-style: italic; }
h1, h2, h3, h4, h5, h6 { font-size: 1.0em !important; }
h1 { margin-bottom: 16px !important; color: #2c3e50 !important; }
p { margin-bottom: 12px !important; }
img { max-width: 100% !important; height: auto !important; }
\`;
pdfContainer.appendChild(pdfStyle);
const footer = document.createElement('div');
footer.style.marginTop = '20px';
footer.style.paddingTop = '10px';
footer.style.borderTop = '1px solid #ccc';
footer.style.fontSize = '10px';
footer.style.color = '#666';
footer.style.textAlign = 'center';
footer.textContent = 'Exported from Reader Mode • ' + new Date().toLocaleDateString();
pdfContainer.appendChild(footer);
const options = {
margin: 10,
filename: 'Reader_Mode_' + title.replace(/[^a-z0-9]/gi, '_').substring(0, 30) + '.pdf',
image: { type: 'jpeg', quality: 0.8 },
html2canvas: { scale: 2, useCORS: true, backgroundColor: '#ffffff' },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
};
await html2pdf().set(options).from(pdfContainer).save();
showStatus('PDF exported successfully!');
} catch (error) {
alert('PDF generation failed. Please try again.');
} finally {
pdfBtn.disabled = false;
pdfBtn.textContent = '📄';
if (document.contains(loading)) document.body.removeChild(loading);
}
};
// ========== DARK MODE ==========
const toggleDarkMode = () => {
dark = !dark;
document.body.style.background = dark ? '#111' : '#fff';
document.body.style.color = dark ? '#eee' : '#111';
document.getElementById('toggle-dark').textContent = dark ? '☀️' : '🌙';
document.body.classList.toggle('dark-mode', dark);
};
// ========== INITIALIZE EVERYTHING ==========
const init = () => {
updateFont();
initializeHighlighting();
initThreeTapGesture();
initGoogleTranslateInReader();
// Set up event listeners
document.getElementById('toggle-dark').addEventListener('click', toggleDarkMode);
document.getElementById('font-up').addEventListener('click', () => { fontSize += 1; updateFont(); });
document.getElementById('font-down').addEventListener('click', () => { fontSize = Math.max(12, fontSize - 1); updateFont(); });
document.getElementById('translate-btn').addEventListener('click', toggleTranslation);
document.getElementById('highlight-btn').addEventListener('click', showHighlightControls);
document.getElementById('editor-btn').addEventListener('click', toggleEditMode);
document.getElementById('screenshot-btn').addEventListener('click', showScreenshotMenu);
document.getElementById('pdf-btn').addEventListener('click', exportToPDF);
// Highlight controls
document.querySelectorAll('#highlight-controls button[data-color]').forEach(button => {
button.addEventListener('click', (e) => {
setHighlightColor(e.target.getAttribute('data-color'));
});
});
document.getElementById('clear-highlights').addEventListener('click', clearHighlights);
document.getElementById('hide-highlight-controls').addEventListener('click', hideHighlightControls);
// Show initial indicator
setTimeout(() => {
const indicator = document.getElementById('three-tap-indicator');
indicator.classList.add('show');
setTimeout(() => indicator.classList.remove('show'), 3000);
}, 1000);
};
// Start the application
init();
<\/script>
</body>
</html>
`;
document.open();
document.write(html);
document.close();
} catch (e) {
alert('Reader Mode failed: ' + (e.message || e));
}
};
})();