// Professional minimalist UI components — Tormentas const { useState: useStP, useEffect: useEffP, useRef: useRefP } = React; const T = { // Core bg: "#f8fafc", card: "#ffffff", sidebar: "#0f172a", sidebarHover: "#1e293b", sidebarActive: "#1e293b", // Text text: "#0f172a", muted: "#64748b", subtle: "#94a3b8", inverse: "#ffffff", inverseMuted: "#94a3b8", // Borders border: "#e2e8f0", borderMd: "#cbd5e1", // Accent (single blue) accent: "#2563eb", accentLt: "#eff6ff", accentMd: "#bfdbfe", // Semantic green: "#16a34a", greenLt:"#f0fdf4", red: "#dc2626", redLt: "#fef2f2", yellow: "#d97706", yellowLt:"#fffbeb", // Stripe stripe: "#f8fafc", }; // ── Avatar ─────────────────────────────────────────────── function PAvatar({ initials, color, size = 36, style = {} }) { // Desaturate colors for professional look return (
{initials}
); } // ── Card ──────────────────────────────────────────────── function PCard({ children, style = {}, onClick, padding = "24px" }) { return (
{ if (onClick) e.currentTarget.style.borderColor = T.accent + "60"; }} onMouseLeave={e => { if (onClick) e.currentTarget.style.borderColor = T.border; }} >{children}
); } // ── KPI Card ──────────────────────────────────────────── function PKPICard({ label, value, delta, color = T.text, sub }) { return (
{label}
{value}
{sub &&
{sub}
}
); } // ── Section Label ──────────────────────────────────────── function PLabel({ children }) { return (
{children}
); } // ── Section Header ─────────────────────────────────────── function PSectionHeader({ title, action, onAction }) { return (
{title} {action && ( )}
); } // ── Badge ──────────────────────────────────────────────── function PBadge({ text, color = T.accent, variant = "default" }) { if (variant === "pill") return ( {text} ); return ( {text} ); } // ── Table ──────────────────────────────────────────────── function PTable({ columns, rows, onRowClick }) { const [hovered, setHovered] = React.useState(null); return (
{columns.map(col => ( ))} {rows.map((row, ri) => ( onRowClick && onRowClick(row)} onMouseEnter={() => setHovered(ri)} onMouseLeave={() => setHovered(null)} style={{ background: hovered === ri && onRowClick ? "#f1f5f9" : ri % 2 === 1 ? T.stripe : "#fff", cursor: onRowClick ? "pointer" : "default", transition: "background 0.1s" }} > {columns.map(col => ( ))} ))}
{col.label}
{row[col.key]}
); } // ── Progress ───────────────────────────────────────────── function PProgress({ value, max = 100, color = T.accent, height = 4 }) { const pct = Math.min(100, Math.round((value / max) * 100)); return (
); } // ── Back Button ────────────────────────────────────────── function PBackBtn({ onClick, label = "Atrás" }) { return ( ); } // ── Divider ────────────────────────────────────────────── function PDivider() { return
; } // ── Tab Bar ────────────────────────────────────────────── function PTabBar({ tabs, active, onChange }) { return (
{tabs.map(([id, label]) => ( ))}
); } // ── Line Chart ─────────────────────────────────────────── function PLineChart({ data, labels, color = T.accent, height = 120 }) { const ref = useRefP(null); useEffP(() => { const canvas = ref.current; if (!canvas) return; const dpr = window.devicePixelRatio || 1; const W = canvas.offsetWidth * dpr, H = height * dpr; canvas.width = W; canvas.height = H; const ctx = canvas.getContext("2d"); ctx.scale(dpr, dpr); const cW = canvas.offsetWidth, cH = height; const pad = { top: 20, right: 16, bottom: 32, left: 36 }; const iW = cW - pad.left - pad.right, iH = cH - pad.top - pad.bottom; const max = Math.max(...data, 1), n = data.length; const xOf = i => pad.left + (i / (n - 1)) * iW; const yOf = v => pad.top + iH - (v / max) * iH; // Grid [0, Math.ceil(max / 2), max].forEach(v => { const y = yOf(v); ctx.strokeStyle = T.border; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(pad.left, y); ctx.lineTo(cW - pad.right, y); ctx.stroke(); ctx.fillStyle = T.muted; ctx.font = `11px DM Sans, sans-serif`; ctx.textAlign = "right"; ctx.fillText(v, pad.left - 6, y + 4); }); // Fill const g = ctx.createLinearGradient(0, pad.top, 0, cH - pad.bottom); g.addColorStop(0, color + "28"); g.addColorStop(1, color + "00"); ctx.beginPath(); data.forEach((v, i) => i === 0 ? ctx.moveTo(xOf(i), yOf(v)) : ctx.lineTo(xOf(i), yOf(v))); ctx.lineTo(xOf(n - 1), yOf(0)); ctx.lineTo(xOf(0), yOf(0)); ctx.closePath(); ctx.fillStyle = g; ctx.fill(); // Line ctx.beginPath(); data.forEach((v, i) => i === 0 ? ctx.moveTo(xOf(i), yOf(v)) : ctx.lineTo(xOf(i), yOf(v))); ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.lineJoin = "round"; ctx.stroke(); // Dots data.forEach((v, i) => { ctx.beginPath(); ctx.arc(xOf(i), yOf(v), 3.5, 0, Math.PI * 2); ctx.fillStyle = "#fff"; ctx.fill(); ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = T.muted; ctx.font = `10px DM Sans, sans-serif`; ctx.textAlign = "center"; ctx.fillText(labels[i], xOf(i), cH - 8); }); }, [data, color, height]); return ; } // ── Bar Chart ──────────────────────────────────────────── function PBarChart({ data, labels, color = T.accent, height = 100 }) { const ref = useRefP(null); useEffP(() => { const canvas = ref.current; if (!canvas) return; const dpr = window.devicePixelRatio || 1; canvas.width = canvas.offsetWidth * dpr; canvas.height = height * dpr; const ctx = canvas.getContext("2d"); ctx.scale(dpr, dpr); const cW = canvas.offsetWidth, cH = height; const pad = { top: 16, right: 8, bottom: 28, left: 8 }; const iW = cW - pad.left - pad.right, iH = cH - pad.top - pad.bottom; const max = Math.max(...data, 1), n = data.length; const gap = iW / n, bw = gap * 0.5; data.forEach((v, i) => { const x = pad.left + gap * i + (gap - bw) / 2; const bh = (v / max) * iH, y = pad.top + iH - bh; const col = Array.isArray(color) ? color[i % color.length] : color; ctx.fillStyle = col + "cc"; ctx.beginPath(); ctx.roundRect(x, y, bw, bh, [3, 3, 0, 0]); ctx.fill(); ctx.fillStyle = T.muted; ctx.font = `10px DM Sans, sans-serif`; ctx.textAlign = "center"; ctx.fillText(labels[i], x + bw / 2, cH - 8); ctx.fillStyle = col; ctx.font = `bold 10px DM Sans, sans-serif`; if (v > 0) ctx.fillText(v, x + bw / 2, y - 4); }); }, [data, color, height]); return ; } // Pos colors — muted, professional const posColorsP = { "Base": "#2563eb", "Escolta": "#7c3aed", "Alero": "#0891b2", "Ala-Pívot": "#059669", "Pívot": "#d97706", }; Object.assign(window, { T, posColorsP, PAvatar, PCard, PKPICard, PLabel, PSectionHeader, PBadge, PTable, PProgress, PBackBtn, PDivider, PTabBar, PLineChart, PBarChart });