Compact manual egg detector for pages you manually visit
// ==UserScript==
// @name Torn Egg Helper Compact
// @namespace jez.torn.egg.compact
// @version 3.0
// @description Compact manual egg detector for pages you manually visit
// @match https://www.torn.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'jez_torn_egg_compact_v3';
const PANEL_ID = 'jez-egg-compact-panel';
let lastUrl = '';
let observerStarted = false;
let checkTimer = null;
let collapsed = true;
function loadData() {
try {
const raw = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
return {
checked: raw.checked || {},
found: raw.found || {}
};
} catch (e) {
return { checked: {}, found: {} };
}
}
function saveData(data) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
}
function getPageKey() {
const u = new URL(window.location.href);
return u.pathname + u.search;
}
function isVisible(el) {
if (!el) return false;
const s = getComputedStyle(el);
if (s.display === 'none' || s.visibility === 'hidden' || s.opacity === '0') return false;
const r = el.getBoundingClientRect();
return r.width > 0 && r.height > 0;
}
function looksLikeEggText(str) {
const s = String(str || '').toLowerCase();
return s.includes('egg') || s.includes('easter') || s.includes('hunt');
}
function detectEggCandidates() {
const nodes = document.querySelectorAll('img, div, span, a, button, svg, [class], [id], [title], [aria-label], [alt]');
const candidates = [];
nodes.forEach(el => {
const parts = [
el.id || '',
typeof el.className === 'string' ? el.className : '',
el.getAttribute?.('title') || '',
el.getAttribute?.('aria-label') || '',
el.getAttribute?.('alt') || '',
el.getAttribute?.('src') || '',
el.textContent ? el.textContent.trim().slice(0, 60) : ''
];
const joined = parts.join(' | ');
if (looksLikeEggText(joined) && isVisible(el)) {
candidates.push({ el, text: joined });
}
});
return candidates;
}
function detectEgg() {
const candidates = detectEggCandidates();
return {
found: candidates.length > 0,
candidates
};
}
function flash(msg, good) {
const old = document.getElementById('jez-egg-flash');
if (old) old.remove();
const n = document.createElement('div');
n.id = 'jez-egg-flash';
n.textContent = msg;
n.style.position = 'fixed';
n.style.top = '10px';
n.style.right = '10px';
n.style.zIndex = '99999999';
n.style.padding = '8px 10px';
n.style.borderRadius = '8px';
n.style.fontSize = '12px';
n.style.fontWeight = '700';
n.style.color = '#fff';
n.style.background = good ? '#1f8b24' : '#444';
n.style.boxShadow = '0 4px 12px rgba(0,0,0,0.35)';
document.body.appendChild(n);
setTimeout(() => n.remove(), 1800);
}
function removePanel() {
const old = document.getElementById(PANEL_ID);
if (old) old.remove();
}
function highlightCandidate(el) {
try {
el.style.outline = '3px solid lime';
el.style.outlineOffset = '2px';
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
} catch (e) {}
}
function makeButton(text, onClick) {
const btn = document.createElement('button');
btn.textContent = text;
btn.style.border = 'none';
btn.style.borderRadius = '6px';
btn.style.padding = '5px 7px';
btn.style.fontSize = '11px';
btn.style.fontWeight = '700';
btn.style.cursor = 'pointer';
btn.style.marginRight = '6px';
btn.style.marginTop = '6px';
btn.onclick = onClick;
return btn;
}
function drawPanel(result) {
removePanel();
const data = loadData();
const pageKey = getPageKey();
const panel = document.createElement('div');
panel.id = PANEL_ID;
panel.style.position = 'fixed';
panel.style.right = '8px';
panel.style.bottom = '8px';
panel.style.zIndex = '99999998';
panel.style.background = 'rgba(0,0,0,0.92)';
panel.style.color = '#fff';
panel.style.border = '1px solid rgba(255,255,255,0.14)';
panel.style.borderRadius = '10px';
panel.style.boxShadow = '0 6px 16px rgba(0,0,0,0.35)';
panel.style.fontSize = '11px';
panel.style.lineHeight = '1.25';
panel.style.width = collapsed ? '160px' : '260px';
panel.style.maxHeight = collapsed ? 'unset' : '40vh';
panel.style.overflow = 'auto';
panel.style.padding = '8px';
const titleRow = document.createElement('div');
titleRow.style.display = 'flex';
titleRow.style.justifyContent = 'space-between';
titleRow.style.alignItems = 'center';
const title = document.createElement('div');
title.textContent = 'Egg Helper';
title.style.fontWeight = '800';
title.style.fontSize = '12px';
titleRow.appendChild(title);
const toggle = document.createElement('button');
toggle.textContent = collapsed ? '+' : '–';
toggle.style.border = 'none';
toggle.style.borderRadius = '5px';
toggle.style.width = '22px';
toggle.style.height = '22px';
toggle.style.cursor = 'pointer';
toggle.onclick = () => {
collapsed = !collapsed;
drawPanel(result);
};
titleRow.appendChild(toggle);
panel.appendChild(titleRow);
const status = document.createElement('div');
status.style.marginTop = '6px';
status.style.fontWeight = '700';
status.style.color = result.found ? '#7dff7d' : '#ddd';
status.textContent = result.found ? 'Egg: possible match' : 'Egg: none seen';
panel.appendChild(status);
const stats = document.createElement('div');
stats.style.marginTop = '4px';
stats.textContent = `Checked ${Object.keys(data.checked).length} | Flagged ${Object.keys(data.found).length}`;
panel.appendChild(stats);
if (!collapsed) {
const page = document.createElement('div');
page.style.marginTop = '6px';
page.style.wordBreak = 'break-word';
page.textContent = pageKey;
panel.appendChild(page);
const btnRow = document.createElement('div');
btnRow.appendChild(makeButton('Recheck', () => runCheck(true)));
btnRow.appendChild(makeButton('Clear', () => {
localStorage.removeItem(STORAGE_KEY);
flash('Log cleared', true);
runCheck(true);
}));
btnRow.appendChild(makeButton('Copy', async () => {
const txt = Object.keys(loadData().found).join('\n') || 'No flagged pages yet.';
try {
await navigator.clipboard.writeText(txt);
flash('Copied', true);
} catch (e) {
flash('Copy failed', false);
}
}));
if (result.candidates[0]) {
btnRow.appendChild(makeButton('Jump', () => highlightCandidate(result.candidates[0].el)));
}
panel.appendChild(btnRow);
const found = Object.keys(data.found).reverse().slice(0, 8);
const header = document.createElement('div');
header.style.marginTop = '8px';
header.style.fontWeight = '800';
header.textContent = 'Flagged pages';
panel.appendChild(header);
if (!found.length) {
const none = document.createElement('div');
none.style.opacity = '0.8';
none.style.marginTop = '4px';
none.textContent = 'None yet';
panel.appendChild(none);
} else {
found.forEach(p => {
const row = document.createElement('div');
row.style.marginTop = '3px';
row.style.wordBreak = 'break-word';
row.textContent = p;
panel.appendChild(row);
});
}
}
document.body.appendChild(panel);
}
function runCheck(showFlash) {
const data = loadData();
const pageKey = getPageKey();
const result = detectEgg();
data.checked[pageKey] = { at: new Date().toISOString() };
if (result.found) {
data.found[pageKey] = { at: new Date().toISOString() };
} else {
delete data.found[pageKey];
}
saveData(data);
drawPanel(result);
if (showFlash) {
flash(result.found ? 'Possible egg found' : 'Checked page', result.found);
}
}
function scheduleCheck(showFlash) {
clearTimeout(checkTimer);
checkTimer = setTimeout(() => runCheck(showFlash), 500);
}
function startObserver() {
if (observerStarted) return;
observerStarted = true;
const obs = new MutationObserver(() => {
scheduleCheck(false);
});
obs.observe(document.documentElement || document.body, {
childList: true,
subtree: true,
attributes: true
});
}
function boot() {
lastUrl = location.href;
runCheck(true);
startObserver();
setInterval(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
scheduleCheck(true);
}
}, 1500);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();