// ─── Shared UI Primitives ─────────────────────────────────────────────────── // Exports: Panel, HexKPI, StatusBadge, SCPButton, Modal, DataTable, // CornerFrame, GlowDot, SectionLabel, InputField, Toggle, CodeEditor const uiStyles = ` .panel { background: rgba(0, 15, 35, 0.85); border: 1px solid rgba(0, 180, 255, 0.2); position: relative; overflow: hidden; } .panel::before { content: ''; position: absolute; inset: 0; background: linear-gradient(135deg, rgba(0,200,255,0.03) 0%, transparent 60%); pointer-events: none; } .panel-corner { position: absolute; width: 10px; height: 10px; border-color: rgba(0,200,255,0.7); border-style: solid; } .panel-corner.tl { top: 0; left: 0; border-width: 2px 0 0 2px; } .panel-corner.tr { top: 0; right: 0; border-width: 2px 2px 0 0; } .panel-corner.bl { bottom: 0; left: 0; border-width: 0 0 2px 2px; } .panel-corner.br { bottom: 0; right: 0; border-width: 0 2px 2px 0; } .scp-btn { background: transparent; border: 1px solid currentColor; color: inherit; font-family: 'Share Tech Mono', monospace; font-size: 14px; letter-spacing: 0.12em; text-transform: uppercase; cursor: pointer; padding: 6px 13px; transition: all 0.15s; position: relative; overflow: hidden; } .scp-btn::before { content: ''; position: absolute; inset: 0; background: currentColor; opacity: 0; transition: opacity 0.15s; } .scp-btn:hover::before { opacity: 0.12; } .scp-btn:active::before { opacity: 0.25; } .scp-btn:disabled { opacity: 0.35; cursor: not-allowed; } .modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); backdrop-filter: blur(4px); z-index: 1000; display: flex; align-items: center; justify-content: center; } .modal-box { background: #020c1e; border: 1px solid rgba(0,200,255,0.4); box-shadow: 0 0 40px rgba(0,200,255,0.15), 0 0 80px rgba(0,0,0,0.8); min-width: 500px; max-width: 90vw; max-height: 85vh; display: flex; flex-direction: column; position: relative; } .modal-header { padding: 14px 20px; border-bottom: 1px solid rgba(0,200,255,0.2); display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; font-size: 14px; } .modal-body { padding: 20px; overflow-y: auto; flex: 1; } .code-editor { width: 100%; background: rgba(0,5,15,0.9); border: 1px solid rgba(0,200,255,0.2); color: #00ff88; font-family: 'Share Tech Mono', monospace; font-size: 14px; line-height: 1.6; padding: 12px; resize: vertical; outline: none; box-sizing: border-box; } .code-editor:focus { border-color: rgba(0,200,255,0.5); } .scp-input { background: rgba(0,10,25,0.8); border: 1px solid rgba(0,180,255,0.25); color: #e0f4ff; font-family: 'Share Tech Mono', monospace; font-size: 14px; padding: 8px 10px; outline: none; width: 100%; box-sizing: border-box; transition: border-color 0.2s; } .scp-input:focus { border-color: rgba(0,200,255,0.6); } .scp-select { background: rgba(0,10,25,0.8); border: 1px solid rgba(0,180,255,0.25); color: #e0f4ff; font-family: 'Share Tech Mono', monospace; font-size: 14px; padding: 8px 10px; outline: none; width: 100%; box-sizing: border-box; cursor: pointer; } .data-table { width: 100%; border-collapse: collapse; } .data-table th { font-size: 14px; letter-spacing: 0.12em; color: rgba(0,200,255,0.6); text-transform: uppercase; padding: 7px 10px; border-bottom: 1px solid rgba(0,200,255,0.15); text-align: left; font-weight: 400; } .data-table td { font-size: 14px; padding: 9px 10px; border-bottom: 1px solid rgba(0,200,255,0.06); color: rgba(220,240,255,0.85); font-family: 'Share Tech Mono', monospace; } .data-table tr:hover td { background: rgba(0,200,255,0.04); } .toggle-switch { display: flex; align-items: center; gap: 8px; cursor: pointer; } .toggle-track { width: 32px; height: 16px; border-radius: 8px; border: 1px solid rgba(0,200,255,0.4); position: relative; background: rgba(0,10,25,0.8); transition: all 0.2s; flex-shrink: 0; } .toggle-track.on { background: rgba(0,200,100,0.25); border-color: rgba(0,255,140,0.6); } .toggle-thumb { position: absolute; top: 2px; left: 2px; width: 10px; height: 10px; border-radius: 50%; background: rgba(0,200,255,0.5); transition: all 0.2s; } .toggle-track { width: 36px; height: 18px; border-radius: 9px; border: 1px solid rgba(0,200,255,0.4); position: relative; background: rgba(0,10,25,0.8); transition: all 0.2s; flex-shrink: 0; } .toggle-track.on { background: rgba(0,200,100,0.25); border-color: rgba(0,255,140,0.6); } .toggle-thumb { position: absolute; top: 3px; left: 3px; width: 10px; height: 10px; border-radius: 50%; background: rgba(0,200,255,0.5); transition: all 0.2s; } .toggle-track.on .toggle-thumb { left: 21px; background: #00ff88; box-shadow: 0 0 6px #00ff88; } .section-label { font-size: 14px; letter-spacing: 0.15em; text-transform: uppercase; color: rgba(0,200,255,0.5); font-family: 'Share Tech Mono', monospace; margin-bottom: 2px; } .glow-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; } @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.3} } .blink { animation: blink 1.4s infinite; } @keyframes scanline { 0% { transform: translateY(-100%); } 100% { transform: translateY(100vh); } } `; // Inject styles once if (!document.getElementById('scp-ui-styles')) { const s = document.createElement('style'); s.id = 'scp-ui-styles'; s.textContent = uiStyles; document.head.appendChild(s); } // ─── Panel ────────────────────────────────────────────────────────────────── function Panel({ children, style, className = '', title, accent = '#00c8ff', headerRight }) { return (
{title && (
{title} {headerRight}
)} {children}
); } // ─── HexKPI ───────────────────────────────────────────────────────────────── function HexKPI({ value, label, color = '#00c8ff', size = 90, sublabel }) { const s = size; const h = s * 1.1547; return (
70 ? 20 : 14, fontWeight: 700, color, fontFamily: "'Share Tech Mono',monospace", lineHeight: 1, textShadow: `0 0 8px ${color}` }}>{value}
{sublabel &&
{sublabel}
}
{label}
); } // ─── StatusBadge ───────────────────────────────────────────────────────────── function StatusBadge({ status }) { const map = { running: { color: '#00ff88', label: 'RUNNING' }, stopped: { color: '#ff2244', label: 'STOPPED' }, paused: { color: '#ffd700', label: 'PAUSED' }, error: { color: '#ff6600', label: 'ERROR' }, unknown: { color: '#888', label: 'UNKNOWN' }, active: { color: '#00ff88', label: 'ACTIVE' }, inactive: { color: '#ff2244', label: 'INACTIVE'}, warning: { color: '#ffd700', label: 'WARNING' }, }; const { color, label } = map[status] || map.unknown; return ( {label} ); } // ─── Button ────────────────────────────────────────────────────────────────── function SCPButton({ children, onClick, color = '#00c8ff', style, disabled, small }) { return ( ); } // ─── Modal ─────────────────────────────────────────────────────────────────── function Modal({ open, onClose, title, children, width = 600 }) { if (!open) return null; return (
e.target === e.currentTarget && onClose()}>
{title}
{children}
); } // ─── DataTable ──────────────────────────────────────────────────────────────── function DataTable({ columns, rows }) { return ( {columns.map(c => )}{rows.map((r, i) => ( {columns.map(c => )} ))}
{c.label}
{c.render ? c.render(r) : r[c.key]}
); } // ─── CodeEditor ────────────────────────────────────────────────────────────── function CodeEditor({ value, onChange, rows = 20, placeholder }) { return (