class DebugPanel { constructor() { this.commands = {}; this.terminal = null; this._logs = []; this._original = {}; this._panel = null; this._body = null; this._visible = false; this.MAX_LOGS = 500; } requirements() { return { met: true, missing: [] }; } getHelpInfo() { return { 'debug': 'debug — Toggle the debug console panel' }; } registerCommands() { this.commands['debug'] = () => this.toggle(); } seed() { this._intercept(); } init() {} _intercept() { const self = this; // Console for (const m of ['log','warn','error','info']) { this._original[m] = console[m]; console[m] = function(...args) { self._add(m, args); self._original[m].apply(console, args); }; } // window.onerror this._original.onerror = window.onerror; window.onerror = function(msg, url, line, col, err) { self._add('error', [`${msg} (${url}:${line}:${col})`]); if (self._original.onerror) self._original.onerror(msg, url, line, col, err); }; // Unhandled rejections window.addEventListener('unhandledrejection', function(e) { self._add('error', ['Unhandled Promise: ' + (e.reason?.message || e.reason)]); }); // Fetch this._original.fetch = window.fetch; window.fetch = function(...args) { const url = typeof args[0] === 'string' ? args[0] : args[0]?.url || '?'; const method = (args[1]?.method || 'GET'); const start = performance.now(); return self._original.fetch.apply(window, args) .then(function(resp) { const ms = (performance.now() - start).toFixed(0); const lvl = resp.status >= 400 ? 'error' : resp.status >= 300 ? 'warn' : 'log'; self._add(lvl, ['🌐 ' + method + ' ' + url + ' → ' + resp.status + ' (' + ms + 'ms)']); return resp; }) .catch(function(err) { const ms = (performance.now() - start).toFixed(0); self._add('error', ['🌐 ' + method + ' ' + url + ' FAIL (' + ms + 'ms): ' + err.message]); throw err; }); }; } _add(level, args) { const text = args.map(function(a) { if (typeof a === 'object') try { return JSON.stringify(a, null, 2); } catch(e) { return String(a); } return String(a); }).join(' '); this._logs.push({ level, text, time: new Date().toLocaleTimeString() }); if (this._logs.length > this.MAX_LOGS) this._logs.shift(); if (this._visible) this._appendEntry(this._logs[this._logs.length - 1]); } toggle() { if (!this._panel) this._build(); this._visible = !this._visible; this._panel.style.display = this._visible ? 'flex' : 'none'; if (this._visible) { this._body.innerHTML = ''; for (const e of this._logs) this._appendEntry(e); this._body.scrollTop = this._body.scrollHeight; } } _build() { const p = document.createElement('div'); p.id = 'debug-panel'; p.style.cssText = 'position:fixed;bottom:0;left:0;right:0;height:40vh;background:#0d0d12;border-top:2px solid #1e1e2a;z-index:9999;display:none;flex-direction:column;font-family:monospace;font-size:12px;'; // Header const h = document.createElement('div'); h.style.cssText = 'display:flex;align-items:center;padding:4px 10px;background:#16161e;border-bottom:1px solid #1e1e2a;gap:6px;flex-shrink:0;'; h.innerHTML = '🐞 Debug Console'; // Copy All const ca = document.createElement('button'); ca.textContent = '📋 Copy All'; ca.onclick = () => this._copyAll(); ca.style.cssText = 'background:#1e1e2a;color:#e8e8f0;border:1px solid #2a2a3a;padding:2px 10px;border-radius:6px;cursor:pointer;font-size:11px;font-family:inherit;'; // Clear const cl = document.createElement('button'); cl.textContent = '🗑 Clear'; cl.onclick = () => { this._logs = []; if (this._body) this._body.innerHTML = ''; }; cl.style.cssText = ca.style.cssText; // Close const cx = document.createElement('button'); cx.textContent = '✕'; cx.onclick = () => this.toggle(); cx.style.cssText = 'background:none;color:#8888a0;border:none;cursor:pointer;margin-left:auto;font-size:18px;padding:0 4px;'; h.appendChild(ca); h.appendChild(cl); h.appendChild(cx); p.appendChild(h); // Body this._body = document.createElement('div'); this._body.style.cssText = 'flex:1;overflow-y:auto;padding:4px 10px;background:#111118;'; p.appendChild(this._body); document.body.appendChild(p); this._panel = p; } _appendEntry(e) { const colors = { error: '#ff6bcb', warn: '#ffa500', log: '#00e5c8', info: '#7c6bff' }; const line = document.createElement('div'); line.style.cssText = 'padding:3px 4px;border-bottom:1px solid #1a1a26;display:flex;align-items:flex-start;gap:6px;'; // Badge const b = document.createElement('span'); b.textContent = e.level.toUpperCase(); b.style.cssText = 'color:' + (colors[e.level] || '#888') + ';font-weight:700;font-size:10px;width:40px;flex-shrink:0;'; // Timestamp const ts = document.createElement('span'); ts.textContent = e.time; ts.style.cssText = 'color:#555;font-size:10px;width:70px;flex-shrink:0;'; // Message const m = document.createElement('span'); m.textContent = e.text; m.style.cssText = 'color:#c8c8d0;flex:1;word-break:break-all;white-space:pre-wrap;'; // Copy const cp = document.createElement('button'); cp.textContent = 'Copy'; cp.onclick = function() { var ta = document.createElement('textarea'); ta.value = e.text; ta.style.cssText = 'position:fixed;left:-999px;top:0;'; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); cp.textContent = '✓'; setTimeout(function() { cp.textContent = 'Copy'; }, 1500); }; cp.style.cssText = 'background:#1e1e2a;color:#8888a0;border:1px solid #2a2a3a;border-radius:4px;cursor:pointer;font-size:10px;padding:1px 6px;flex-shrink:0;font-family:inherit;'; line.appendChild(b); line.appendChild(ts); line.appendChild(m); line.appendChild(cp); this._body.appendChild(line); this._body.scrollTop = this._body.scrollHeight; } _copyAll() { const text = this._logs.map(function(e) { return '[' + e.level.toUpperCase() + '] ' + e.time + ' ' + e.text; }).join('\n'); var ta = document.createElement('textarea'); ta.value = text; ta.style.cssText = 'position:fixed;left:-999px;top:0;'; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); if (this.terminal) this.terminal.print('📋 Debug log copied (' + this._logs.length + ' entries)', 'success'); } }