/* ============================================================ FLOWSHIELD — Consola de scripts Python (on-premise) Repositorio de scripts + terminal con salida en vivo (SSE), prompt estilo zsh y render de color ANSI (incl. 256 colores). ============================================================ */ /* ---- Render de secuencias ANSI (SGR) a con color ---- */ const ANSI_FG = { 30: '#2e3440', 31: '#ff5c66', 32: '#2ee6a6', 33: '#ffb627', 34: '#4d7cff', 35: '#b66bff', 36: '#29e0ff', 37: '#c7d0e0', 90: '#6b7892', 91: '#ff8a92', 92: '#5cf0bf', 93: '#ffd27a', 94: '#86a5ff', 95: '#d0a3ff', 96: '#8af0ff', 97: '#eaf1ff', }; function xterm256(n) { const base = ['#000000', '#cd3131', '#2ee6a6', '#e5c07b', '#4d7cff', '#c678dd', '#29e0ff', '#c7d0e0', '#6b7892', '#ff5c66', '#5cf0bf', '#ffd27a', '#86a5ff', '#d0a3ff', '#8af0ff', '#ffffff']; if (n < 16) return base[n] || '#c7d0e0'; if (n < 232) { n -= 16; const l = [0, 95, 135, 175, 215, 255]; return `rgb(${l[Math.floor(n / 36) % 6]},${l[Math.floor(n / 6) % 6]},${l[n % 6]})`; } const v = 8 + (n - 232) * 10; return `rgb(${v},${v},${v})`; } function renderAnsi(text, state) { const st = { fg: state.fg || null, bold: !!state.bold }; const parts = text.split(/\x1b\[([0-9;]*)m/); const nodes = []; for (let i = 0; i < parts.length; i++) { if (i % 2 === 0) { if (parts[i]) nodes.push({ t: parts[i], fg: st.fg, bold: st.bold }); continue; } const codes = parts[i].split(';').map(x => parseInt(x || '0', 10)); for (let j = 0; j < codes.length; j++) { const c = codes[j]; if (c === 0) { st.fg = null; st.bold = false; } else if (c === 1) st.bold = true; else if (c === 22) st.bold = false; else if (c === 39) st.fg = null; else if ((c >= 30 && c <= 37) || (c >= 90 && c <= 97)) st.fg = ANSI_FG[c]; else if (c === 38) { if (codes[j + 1] === 5) { st.fg = xterm256(codes[j + 2]); j += 2; } else if (codes[j + 1] === 2) { st.fg = `rgb(${codes[j + 2]},${codes[j + 3]},${codes[j + 4]})`; j += 4; } } } } return { nodes, state: { fg: st.fg, bold: st.bold } }; } /* ---- Editor de código ligero: gutter de líneas + Tab a 2 espacios ---- */ function CodeArea({ value, onChange, placeholder, minHeight = 220 }) { const taRef = useRefL(null); const gutRef = useRefL(null); const n = Math.max(1, (value || '').split('\n').length); const nums = Array.from({ length: n }, (_, i) => i + 1).join('\n'); const sync = () => { if (gutRef.current && taRef.current) gutRef.current.scrollTop = taRef.current.scrollTop; }; const onKeyDown = (e) => { if (e.key === 'Tab') { e.preventDefault(); const ta = e.target, s = ta.selectionStart, en = ta.selectionEnd; onChange(value.slice(0, s) + ' ' + value.slice(en)); requestAnimationFrame(() => { try { ta.selectionStart = ta.selectionEnd = s + 2; } catch (x) {} }); } }; const base = { fontSize: 12, lineHeight: 1.5, fontFamily: 'var(--font-mono)' }; return (