Orion iOS Eruda Console Toggle

Persistent, stronger Eruda launcher for Orion iOS with always-on mode and blocker-resilient loading fallbacks.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Advertisement:

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

Advertisement:

// ==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();
})();