// Global Console Logger - Vercel-inspired design // Include in any page: const STYLES = ` #logger-fab { position: fixed; bottom: 20px; right: 20px; width: 48px; height: 48px; border-radius: 50%; background: #000; border: 1px solid #333; color: #fff; font-size: 20px; cursor: pointer; z-index: 99998; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 14px rgba(0,0,0,0.25); transition: transform 0.15s ease, background 0.15s ease; } #logger-fab:hover { transform: scale(1.05); background: #111; } #logger-fab:active { transform: scale(0.95); } #logger-fab .badge { position: absolute; top: -4px; right: -4px; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px; background: #f00; color: #fff; font-size: 11px; font-weight: 600; display: flex; align-items: center; justify-content: center; } #logger-modal { position: fixed; inset: 0; background: rgba(0,0,0,0.6); backdrop-filter: blur(4px); z-index: 99999; display: none; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } #logger-modal.open { display: flex; align-items: center; justify-content: center; } #logger-container { width: 90%; max-width: 900px; height: 80%; max-height: 700px; background: #0a0a0a; border: 1px solid #333; border-radius: 12px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5); } #logger-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid #333; background: #0a0a0a; } #logger-header h3 { margin: 0; font-size: 14px; font-weight: 500; color: #fafafa; display: flex; align-items: center; gap: 8px; } #logger-header h3::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: #22c55e; animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } #logger-actions { display: flex; gap: 8px; } #logger-actions button { padding: 8px 12px; border-radius: 6px; border: 1px solid #333; background: transparent; color: #888; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.15s ease; display: flex; align-items: center; gap: 6px; } #logger-actions button:hover { background: #1a1a1a; color: #fafafa; border-color: #444; } #logger-actions button.primary { background: #fafafa; color: #0a0a0a; border-color: #fafafa; } #logger-actions button.primary:hover { background: #e5e5e5; } #logger-actions button.danger { color: #f87171; border-color: #7f1d1d; } #logger-actions button.danger:hover { background: #7f1d1d; color: #fafafa; } #logger-filters { display: flex; gap: 4px; padding: 12px 20px; border-bottom: 1px solid #222; background: #0a0a0a; } .filter-btn { padding: 6px 12px; border-radius: 20px; border: none; background: transparent; color: #666; font-size: 12px; font-weight: 500; cursor: pointer; transition: all 0.15s ease; } .filter-btn:hover { color: #999; } .filter-btn.active { background: #1a1a1a; color: #fafafa; } .filter-btn .count { margin-left: 4px; padding: 2px 6px; border-radius: 10px; background: #222; font-size: 11px; } #logger-content { flex: 1; overflow-y: auto; padding: 0; background: #0a0a0a; } #logger-content::-webkit-scrollbar { width: 8px; } #logger-content::-webkit-scrollbar-track { background: #0a0a0a; } #logger-content::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; } .log-entry { display: flex; padding: 10px 20px; border-bottom: 1px solid #1a1a1a; font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; font-size: 12px; line-height: 1.5; transition: background 0.1s ease; } .log-entry:hover { background: #111; } .log-entry.error { background: rgba(239, 68, 68, 0.1); border-left: 3px solid #ef4444; } .log-entry.warn { background: rgba(245, 158, 11, 0.1); border-left: 3px solid #f59e0b; } .log-entry .time { color: #666; margin-right: 12px; white-space: nowrap; min-width: 85px; } .log-entry .level { margin-right: 12px; padding: 2px 6px; border-radius: 4px; font-size: 10px; font-weight: 600; text-transform: uppercase; min-width: 50px; text-align: center; } .log-entry .level.log { background: #1e3a5f; color: #60a5fa; } .log-entry .level.info { background: #1e3a5f; color: #60a5fa; } .log-entry .level.warn { background: #422006; color: #fbbf24; } .log-entry .level.error { background: #450a0a; color: #f87171; } .log-entry .level.debug { background: #1a1a2e; color: #a78bfa; } .log-entry .msg { color: #e5e5e5; word-break: break-all; white-space: pre-wrap; flex: 1; } #logger-footer { display: flex; align-items: center; justify-content: space-between; padding: 12px 20px; border-top: 1px solid #333; background: #0a0a0a; font-size: 12px; color: #666; } #logger-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #666; font-size: 14px; } #logger-empty svg { margin-bottom: 16px; opacity: 0.5; } `; export const Logger = { logs: [], maxLogs: 2000, errorCount: 0, filter: 'all', isInitialized: false, init() { if (this.isInitialized) return; this.isInitialized = true; // Inject styles const style = document.createElement('style'); style.textContent = STYLES; document.head.appendChild(style); // Create FAB and Modal this.createUI(); // Store original console methods this.originalConsole = { log: console.log.bind(console), warn: console.warn.bind(console), error: console.error.bind(console), info: console.info.bind(console), debug: console.debug.bind(console) }; // Intercept console methods const self = this; console.log = (...args) => { self.capture('log', args); self.originalConsole.log(...args); }; console.warn = (...args) => { self.capture('warn', args); self.originalConsole.warn(...args); }; console.error = (...args) => { self.capture('error', args); self.originalConsole.error(...args); }; console.info = (...args) => { self.capture('info', args); self.originalConsole.info(...args); }; console.debug = (...args) => { self.capture('debug', args); self.originalConsole.debug(...args); }; // Capture uncaught errors window.addEventListener('error', (e) => { self.capture('error', [`Uncaught: ${e.message} at ${e.filename}:${e.lineno}`]); }); window.addEventListener('unhandledrejection', (e) => { self.capture('error', [`Promise: ${e.reason?.message || e.reason}`]); }); this.capture('info', ['🚀 Logger initialized']); }, createUI() { // FAB Button const fab = document.createElement('button'); fab.id = 'logger-fab'; fab.innerHTML = `›_`; fab.onclick = () => this.toggle(); document.body.appendChild(fab); // Modal const modal = document.createElement('div'); modal.id = 'logger-modal'; modal.innerHTML = `