Persistent, stronger Eruda launcher for Orion iOS with always-on mode and blocker-resilient loading fallbacks.
// ==UserScript== // @name Orion iOS Eruda Console Toggle // @namespace https://littlelooney.example/orion-eruda // @version 2026-05-24.2 // @description Persistent, stronger Eruda launcher for Orion iOS with always-on mode and blocker-resilient loading fallbacks. // @author LittleLooney + OpenAI // @match *://*/* // @require https://cdn.jsdelivr.net/npm/[email protected]/eruda.min.js // @grant none // @run-at document-start // ==/UserScript== (() => { 'use strict'; const STORAGE_KEY = 'orion-eruda-toggle-position'; const AUTO_KEY = 'orion-eruda-auto-open'; const BTN_ID = 'orion-eruda-toggle-btn'; const CDN_FALLBACKS = [ 'https://cdn.jsdelivr.net/npm/[email protected]/eruda.min.js', 'https://unpkg.com/[email protected]/eruda.min.js' ]; let erudaReady = false; let erudaVisible = false; function loadPosition() { try { const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); if (typeof saved.left === 'number' && typeof saved.top === 'number') return saved; } catch {} return { left: 14, top: 86 }; } function savePosition(left, top) { localStorage.setItem(STORAGE_KEY, JSON.stringify({ left, top })); } function isAutoOpenEnabled() { return localStorage.getItem(AUTO_KEY) !== '0'; } function setAutoOpenEnabled(enabled) { localStorage.setItem(AUTO_KEY, enabled ? '1' : '0'); } function clamp(value, min, max) { return Math.min(Math.max(value, min), max); } function updateButtonStyle(btn, pos) { const maxLeft = Math.max(8, window.innerWidth - btn.offsetWidth - 8); const maxTop = Math.max(8, window.innerHeight - btn.offsetHeight - 8); const left = clamp(pos.left, 8, maxLeft); const top = clamp(pos.top, 8, maxTop); btn.style.left = `${left}px`; btn.style.top = `${top}px`; savePosition(left, top); } function loadScript(src) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = src; script.async = true; script.onload = resolve; script.onerror = () => reject(new Error(`Failed to load ${src}`)); (document.head || document.documentElement).appendChild(script); }); } async function ensureEruda() { if (!window.eruda) { for (const src of CDN_FALLBACKS) { try { await loadScript(src); if (window.eruda) break; } catch {} } } if (!window.eruda) throw new Error('Eruda unavailable (possibly blocked by network/content blocker).'); if (!erudaReady) { window.eruda.init({ useShadowDom: false, autoScale: true }); erudaReady = true; } } function setButtonState(btn, text) { btn.textContent = text; btn.setAttribute('aria-label', text); } async function openEruda(btn) { await ensureEruda(); window.eruda.show(); erudaVisible = true; setButtonState(btn, isAutoOpenEnabled() ? 'Console: ON • AUTO' : 'Console: ON'); } function closeEruda(btn) { if (window.eruda) window.eruda.hide(); erudaVisible = false; setButtonState(btn, isAutoOpenEnabled() ? 'Console: OFF • AUTO' : 'Console: OFF'); } async function toggleEruda(btn) { btn.disabled = true; try { if (erudaVisible) closeEruda(btn); else await openEruda(btn); } catch (err) { console.error('[Orion Eruda]', err); setButtonState(btn, 'Console: ERR'); setTimeout(() => setButtonState(btn, erudaVisible ? 'Console: ON' : 'Console: OFF'), 1800); } finally { btn.disabled = false; } } function addAutoToggle(btn) { btn.addEventListener('contextmenu', (e) => { e.preventDefault(); const next = !isAutoOpenEnabled(); setAutoOpenEnabled(next); if (erudaVisible) setButtonState(btn, next ? 'Console: ON • AUTO' : 'Console: ON'); else setButtonState(btn, next ? 'Console: OFF • AUTO' : 'Console: OFF'); }); } function makeDraggable(btn) { let dragging = false, moved = false, startX = 0, startY = 0, originLeft = 0, originTop = 0; const onMove = (x, y) => { if (!dragging) return; const dx = x - startX, dy = y - startY; moved = moved || Math.abs(dx) > 4 || Math.abs(dy) > 4; updateButtonStyle(btn, { left: originLeft + dx, top: originTop + dy }); }; btn.addEventListener('touchstart', (e) => { const t = e.touches[0]; if (!t) return; dragging = true; moved = false; startX = t.clientX; startY = t.clientY; originLeft = parseFloat(btn.style.left) || 0; originTop = parseFloat(btn.style.top) || 0; }, { passive: true }); btn.addEventListener('touchmove', (e) => { const t = e.touches[0]; if (t) onMove(t.clientX, t.clientY); }, { passive: true }); btn.addEventListener('touchend', () => { dragging = false; }, { passive: true }); btn.addEventListener('mousedown', (e) => { dragging = true; moved = false; startX = e.clientX; startY = e.clientY; originLeft = parseFloat(btn.style.left) || 0; originTop = parseFloat(btn.style.top) || 0; e.preventDefault(); }); window.addEventListener('mousemove', (e) => onMove(e.clientX, e.clientY)); window.addEventListener('mouseup', () => { dragging = false; }); btn.addEventListener('click', (e) => { if (moved) { e.preventDefault(); e.stopPropagation(); moved = false; return; } toggleEruda(btn); }); } function attachKeyboardShortcut(btn) { window.addEventListener('keydown', (e) => { if (e.altKey && e.key.toLowerCase() === 'd') { e.preventDefault(); toggleEruda(btn); } }); } function injectUi() { if (document.getElementById(BTN_ID)) return document.getElementById(BTN_ID); const btn = document.createElement('button'); btn.id = BTN_ID; btn.type = 'button'; setButtonState(btn, isAutoOpenEnabled() ? 'Console: OFF • AUTO' : 'Console: OFF'); btn.title = 'Tap: toggle console. Drag: move. Long-press/right-click: toggle AUTO mode.'; Object.assign(btn.style, { position:'fixed', zIndex:'2147483647', padding:'10px 12px', borderRadius:'12px', border:'1px solid rgba(255,255,255,0.25)', background:'rgba(16,16,20,0.85)', color:'#f5f5f5', fontFamily:'-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif', fontSize:'13px', fontWeight:'600', boxShadow:'0 6px 24px rgba(0,0,0,0.25)', cursor:'pointer', userSelect:'none', WebkitUserSelect:'none', opacity:'0.95' }); (document.documentElement || document.body).appendChild(btn); updateButtonStyle(btn, loadPosition()); makeDraggable(btn); attachKeyboardShortcut(btn); addAutoToggle(btn); window.addEventListener('resize', () => updateButtonStyle(btn, { left: parseFloat(btn.style.left) || 8, top: parseFloat(btn.style.top) || 8 })); return btn; } async function boot() { const btn = injectUi(); if (isAutoOpenEnabled()) { try { await openEruda(btn); } catch (e) { console.warn('[Orion Eruda] Auto-open failed', e); } } } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true }); else boot(); })();