const { useState, useEffect, useCallback } = React;
// ─── CONSTANTS ──────────────────────────────────────────────────────────────
const USER_EMAIL = "juanmejia88@gmail.com";
const STORAGE_KEY = "radar-prefs-juan";
const ORGS = [
{ name: "BCW Global / Burson", cat: "Consultora PA", priority: 5, twitter: "@BCW_Global", web: "bcwglobal.com" },
{ name: "SEGIB", cat: "Org. Internacional", priority: 5, twitter: "@segib", web: "segib.org" },
{ name: "FIIAPP", cat: "Organismo Público", priority: 5, twitter: "@fiiapp", web: "fiap.gob.es" },
{ name: "Real Instituto Elcano", cat: "Think Tank", priority: 5, twitter: "@rielcano", web: "realinstitutoelcano.org" },
{ name: "Club de Madrid", cat: "Org. Internacional", priority: 5, twitter: "@ClubdeMadrid", web: "clubmadrid.org" },
{ name: "Comisión Europea España", cat: "Institución UE", priority: 5, twitter: "@ComisionEuropea", web: "spain.representation.ec.europa.eu" },
{ name: "LLYC", cat: "Consultora PA", priority: 5, twitter: "@llorenteycuenca", web: "llyc.global" },
{ name: "Kreab Madrid", cat: "Consultora PA", priority: 5, twitter: "@KreabES", web: "kreab.com/espana" },
{ name: "Harmon Corporate Affairs", cat: "Consultora PA", priority: 5, twitter: "@harmonCA", web: "harmon.es" },
{ name: "Fundación Carolina", cat: "Think Tank", priority: 5, twitter: "@fcarolina_es", web: "fundacioncarolina.es" },
{ name: "Embajada de EE.UU. (Madrid)", cat: "Embajada", priority: 5, twitter: "@USembassyMadrid", web: "es.usembassy.gov" },
{ name: "APRI", cat: "Asociación Prof.", priority: 5, twitter: "@Lobby_ES", web: "relacionesinstitucionales.es" },
{ name: "AmCham Spain", cat: "Cámara / Red", priority: 5, twitter: "@AmChamSpain", web: "amchamspain.com" },
{ name: "Civio", cat: "ONG / Media", priority: 5, twitter: "@civio", web: "civio.es" },
{ name: "FTI Consulting Madrid", cat: "Consultora PA", priority: 5, twitter: "@FTIConsulting", web: "fticonsulting.com/en/spain" },
{ name: "ECFR Madrid", cat: "Think Tank", priority: 4, twitter: "@ecfr", web: "ecfr.eu/madrid" },
{ name: "Casa de América", cat: "Inst. Público", priority: 4, twitter: "@casamerica", web: "casamerica.es" },
{ name: "ACOP", cat: "Asociación Prof.", priority: 4, twitter: "@compolitica", web: "compolitica.com" },
{ name: "Newtral", cat: "Media", priority: 4, twitter: "@newtral_media", web: "newtral.es" },
{ name: "Agencia EFE", cat: "Media", priority: 4, twitter: "@EFEnoticias", web: "efe.com" },
{ name: "AECID", cat: "Organismo Público", priority: 4, twitter: "@AECID_es", web: "aecid.es" },
{ name: "Atrevia", cat: "Consultora PA", priority: 4, twitter: "@atrevia", web: "atrevia.com" },
{ name: "beBartlet", cat: "Consultora PA", priority: 4, twitter: "@beBartlet", web: "bebartlet.com" },
{ name: "NITID Corporate Affairs", cat: "Consultora PA", priority: 4, twitter: "@NITIDcorp", web: "masconsulting.es" },
{ name: "Gobe Studio", cat: "GovTech", priority: 4, twitter: "@gobestudio", web: "gobe.studio" },
{ name: "Maldita.es", cat: "Media", priority: 4, twitter: "@malditaes", web: "maldita.es" },
{ name: "DIRCOM", cat: "Asociación Prof.", priority: 3, twitter: "@DIRCOM_ES", web: "dircom.org" },
{ name: "IE School of Politics", cat: "Universidad", priority: 3, twitter: "@IE_SPEGA", web: "ie.edu" },
{ name: "Casa Árabe", cat: "Inst. Público", priority: 3, twitter: "@casaarabe", web: "casaarabe.es" },
];
const CATS = ["Todas", ...Array.from(new Set(ORGS.map(o => o.cat))).sort()];
const CAT_COLOR = {
"Consultora PA": "#B8860B",
"Org. Internacional": "#0B7A80",
"Organismo Público": "#1A7A4A",
"Think Tank": "#7B2D8B",
"Institución UE": "#1B3A6B",
"Embajada": "#2E4A8B",
"Asociación Prof.": "#16A085",
"Cámara / Red": "#8E44AD",
"ONG / Media": "#C0392B",
"Media": "#A93226",
"Inst. Público": "#2E6DB4",
"GovTech": "#117A65",
"Universidad": "#566573",
};
// ─── EMAIL HTML BUILDER ─────────────────────────────────────────────────────
function buildEmailHtml(results, orgs, searchType, dateStr) {
const typeLabels = {
eventos: "Eventos & Actos Próximos",
vacantes: "Vacantes Activas",
noticias: "Noticias Recientes",
};
const typeIcons = { eventos: "📅", vacantes: "💼", noticias: "📰" };
return `
Radar Organizaciones · ${typeLabels[searchType]}
Juan Mejía Saravia · Madrid
Radar de Organizaciones
${typeIcons[searchType]} ${typeLabels[searchType]} · ${dateStr}
Organizaciones escaneadas
${orgs.join(" · ")}
${results}
Radar · Plan Estratégico 2026
Generado automáticamente · juanmejiasaravia.com
`;
}
function textToEmailHtml(text) {
if (!text) return "No se encontraron resultados para esta búsqueda.
";
const lines = text.split("\n").filter(l => l.trim());
let html = "";
for (const line of lines) {
const t = line.trim();
if (t.startsWith("##") || t.startsWith("###")) {
const content = t.replace(/^#+\s*/, "").replace(/\*\*/g, "");
html += `${content} `;
} else if (t.startsWith("**") && t.endsWith("**")) {
html += `${t.replace(/\*\*/g, "")}
`;
} else if (t.startsWith("- ") || t.startsWith("• ")) {
html += `
${t.replace(/^[-•]\s*/, "").replace(/\*\*(.+?)\*\*/g, "$1 ")}
`;
} else if (t) {
html += `
${t.replace(/\*\*(.+?)\*\*/g, "$1 ")}
`;
}
}
return html;
}
// ─── MAIN COMPONENT ─────────────────────────────────────────────────────────
function RadarAlertas() {
const [tab, setTab] = useState("radar");
const [selected, setSelected] = useState([]);
const [catFilter, setCatFilter] = useState("Todas");
const [searchType, setSearchType] = useState("eventos");
const [status, setStatus] = useState("idle"); // idle | searching | sending | done | error
const [rawText, setRawText] = useState("");
const [searchQueries, setSearchQueries] = useState([]);
const [lastSent, setLastSent] = useState(null);
const [history, setHistory] = useState([]);
const [toast, setToast] = useState(null);
const [previewMode, setPreviewMode] = useState(false);
// ── Persistent storage ──────────────────────────────────────────────────
useEffect(() => {
try {
const res = localStorage.getItem(STORAGE_KEY);
if (res) {
const saved = JSON.parse(res);
if (saved.selected?.length) setSelected(saved.selected);
if (saved.searchType) setSearchType(saved.searchType);
if (saved.history) setHistory(saved.history);
if (saved.lastSent) setLastSent(saved.lastSent);
}
} catch (_) {
console.log("No saved prefs yet or localstorage blocked");
}
}, []);
const savePrefs = useCallback((sel, type, hist, sent) => {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({
selected: sel, searchType: type, history: hist, lastSent: sent,
}));
} catch (_) { }
}, []);
const showToast = (msg, type = "ok") => {
setToast({ msg, type });
setTimeout(() => setToast(null), 4000);
};
const toggleOrg = (name) => {
const next = selected.includes(name) ? selected.filter(n => n !== name) : [...selected, name];
setSelected(next);
savePrefs(next, searchType, history, lastSent);
};
const filtered = catFilter === "Todas" ? ORGS : ORGS.filter(o => o.cat === catFilter);
const selectedOrgs = ORGS.filter(o => selected.includes(o.name));
// ── SEARCH ──────────────────────────────────────────────────────────────
const PROMPTS = {
eventos: (orgs) => `Busca eventos, conferencias, seminarios, foros, webinars y actos públicos próximos (marzo-mayo 2026) organizados por o relacionados con estas organizaciones en España/Madrid: ${orgs.join(", ")}.
Para cada evento encontrado indica: nombre del evento, organizador, fecha aproximada, formato (presencial/online/híbrido), ubicación si aplica, y URL o fuente donde inscribirse.
Si no hay eventos confirmados aún, menciona los eventos recurrentes que estas organizaciones suelen organizar y cómo recibir avisos (newsletter, LinkedIn).
Responde en español, estructura por organización con secciones claras.`,
vacantes: (orgs) => `Busca vacantes de empleo activas (enero-marzo 2026) en estas organizaciones de Madrid/España: ${orgs.join(", ")}.
Enfócate en puestos de: comunicación estratégica, asuntos públicos, relaciones institucionales, proyectos UE, cooperación internacional.
Para cada vacante indica: título, organización, requisitos clave, y URL de la oferta.
Responde en español, estructura por organización.`,
noticias: (orgs) => `Busca las noticias y publicaciones más relevantes de enero-febrero 2026 de estas organizaciones: ${orgs.join(", ")}.
Para cada una resume el anuncio más importante y su relevancia para un profesional de comunicación estratégica y asuntos públicos en Madrid.
Responde en español.`,
};
const runSearch = async () => {
if (!selected.length) return;
setStatus("searching");
setRawText("");
setSearchQueries([]);
setPreviewMode(false);
const orgNames = selected.map(n => ORGS.find(o => o.name === n)?.name).filter(Boolean);
try {
// Connect to our secure PHP backend instead of direct API call
const resp = await fetch("radar_api.php?action=search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
prompt: PROMPTS[searchType](orgNames)
}),
});
const data = await resp.json();
if (!data.success) throw new Error(data.message || "Error en la búsqueda");
let text = data.text || "";
let queries = data.queries || [];
setRawText(text.trim());
setSearchQueries(queries);
setStatus("idle");
setPreviewMode(true);
showToast("Búsqueda completada", "ok");
} catch (e) {
console.error(e);
setStatus("error");
showToast("Error en búsqueda: " + e.message, "error");
setStatus("idle");
}
};
// ── SEND EMAIL ────────────────────────────────
const sendDigest = async () => {
if (!rawText) return;
setStatus("sending");
const dateStr = new Date().toLocaleDateString("es-ES", { day: "numeric", month: "long", year: "numeric" });
const typeLabel = { eventos: "📅 Eventos Próximos", vacantes: "💼 Vacantes Activas", noticias: "📰 Noticias Recientes" };
const subjectLine = `Radar Organizaciones · ${typeLabel[searchType]} · ${dateStr}`;
const emailHtml = buildEmailHtml(textToEmailHtml(rawText), selected, searchType, dateStr);
const plainText = `Radar de Organizaciones · ${typeLabel[searchType]} · ${dateStr}\n\nOrganizaciones escaneadas: ${selected.join(", ")}\n\n${rawText}`;
try {
// Connect to our secure PHP backend for email
const resp = await fetch("radar_api.php?action=email", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
to: USER_EMAIL,
subject: subjectLine,
html: emailHtml,
text: plainText
}),
});
const data = await resp.json();
if (data.success) {
const newEntry = {
id: Date.now(),
date: dateStr,
type: searchType,
orgs: [...selected],
preview: rawText.slice(0, 200) + "…",
};
const newHistory = [newEntry, ...history].slice(0, 10);
setHistory(newHistory);
setLastSent(dateStr);
savePrefs(selected, searchType, newHistory, dateStr);
setStatus("idle");
showToast(`✉️ Digest enviado a ${USER_EMAIL}`, "ok");
} else {
throw new Error(data.message || "Error al enviar");
}
} catch (e) {
console.error(e);
setStatus("idle");
showToast("Error al enviar: " + e.message, "error");
}
};
// ─── UI ──────────────────────────────────────────────────────────────────
const isBusy = status === "searching" || status === "sending";
return (
{/* Animated background */}
{/* TOAST */}
{toast && (
{toast.msg}
)}
{/* HEADER */}
{/* TABS */}
{[
{ id: "radar", label: "🔍 Buscar & Enviar" },
{ id: "config", label: "⚙️ Mis Organizaciones" },
{ id: "history", label: `📋 Historial (${history.length})` },
{ id: "auto", label: "⚡ Automatización" },
].map(t => (
setTab(t.id)} style={{ ...S.tab, ...(tab === t.id ? S.tabActive : {}) }}>
{t.label}
))}
{/* ══ TAB: RADAR ══════════════════════════════════════════════════════ */}
{tab === "radar" && (
{/* LEFT: controls */}
{/* Search type */}
Tipo de búsqueda
{[
{ k: "eventos", icon: "📅", label: "Eventos próximos", desc: "Conferencias, foros, seminarios" },
{ k: "vacantes", icon: "💼", label: "Vacantes activas", desc: "Comunicación, PA, proyectos UE" },
{ k: "noticias", icon: "📰", label: "Noticias recientes", desc: "Anuncios y publicaciones clave" },
].map(({ k, icon, label, desc }) => (
{ setSearchType(k); savePrefs(selected, k, history, lastSent); }}
style={{ ...S.typeBtn, ...(searchType === k ? S.typeBtnActive : {}) }}
>
{icon}
))}
{/* Orgs summary */}
Organizaciones seleccionadas
0 ? "#7ECFD4" : "#567" }}>
{selected.length} de {ORGS.length}
{selected.length === 0 ? (
Ve a "Mis Organizaciones" para configurar tu watchlist.
) : (
{selectedOrgs.slice(0, 10).map(o => (
{o.name}
))}
{selected.length > 10 && (
+{selected.length - 10} más
)}
)}
{/* Action buttons */}
{status === "searching" ? "🔍 Buscando en la web…" : "🔍 Buscar ahora"}
{rawText && !isBusy && (
{status === "sending" ? "📧 Enviando a Gmail…" : `📧 Enviar digest a ${USER_EMAIL}`}
)}
{searchQueries && searchQueries.length > 0 && (
Búsquedas web realizadas
{searchQueries.map((q, i) => (
🔍 {q}
))}
)}
{/* RIGHT: results */}
{status === "searching" && (
🛰️
Escaneando {selected.length} organizaciones…
Buscando con Anthropic en forma segura
)}
{status === "sending" && (
📧
Enviando digest a tu Gmail…
)}
{!isBusy && rawText && (
Resultados de búsqueda
setPreviewMode(!previewMode)}
style={S.btnSmall}
>
{previewMode ? "Ver texto en la página" : "Vista previa email"}
{previewMode ? (
) : (
{rawText.split("\n").map((line, i) => {
const t = line.trim();
if (!t) return
;
if (t.startsWith("##")) return
{t.replace(/^#+\s*/, "")} ;
if (t.startsWith("**") && t.endsWith("**")) return
{t.replace(/\*\*/g, "")}
;
if (t.startsWith("- ") || t.startsWith("• ")) return (
▸
{t.replace(/^[-•]\s*/, "").replace(/\*\*(.+?)\*\*/g, "$1 ")}
);
// replace bold inside paragraph
const pContent = t.replace(/\*\*(.*?)\*\*/g, "
$1 ");
return
;
})}
)}
)}
{!isBusy && !rawText && (
📡
Lanza una búsqueda para ver resultados
1. Selecciona el tipo de alerta
2. Pulsa "Buscar ahora"
3. Revisa los resultados
4. Envía el digest a tu correo seguro
)}
)}
{/* ══ TAB: CONFIG ═════════════════════════════════════════════════════ */}
{tab === "config" && (
setCatFilter(e.target.value)}
style={S.select}
>
{CATS.map(c => {c} )}
{ const next = filtered.map(o => o.name); const merged = Array.from(new Set([...selected, ...next])); setSelected(merged); savePrefs(merged, searchType, history, lastSent); }} style={S.btnSmall}>
Seleccionar visibles
{ setSelected([]); savePrefs([], searchType, history, lastSent); }} style={{ ...S.btnSmall, borderColor: "#B22222", color: "#F1948A" }}>
Limpiar todo
{selected.length} seleccionadas · Guardado en tu equipo
{filtered.map(org => {
const isSel = selected.includes(org.name);
const cc = CAT_COLOR[org.cat] || "#555";
return (
toggleOrg(org.name)}
style={{
...S.orgCard,
background: isSel ? `rgba(${hexRgb(cc)},0.12)` : "rgba(255,255,255,0.03)",
borderColor: isSel ? cc : "rgba(255,255,255,0.08)",
}}
>
{isSel && ✓ }
{org.name}
{"⭐".repeat(org.priority)} · {org.cat}
{org.twitter} · {org.web}
);
})}
)}
{/* ══ TAB: HISTORY ════════════════════════════════════════════════════ */}
{tab === "history" && (
{history.length === 0 ? (
📋
Aún no has enviado ningún digest
Los digests enviados aparecerán aquí
) : (
{history.map(entry => (
{{ eventos: "📅", vacantes: "💼", noticias: "📰" }[entry.type]} {" "}
{{ eventos: "Eventos próximos", vacantes: "Vacantes activas", noticias: "Noticias recientes" }[entry.type]}
{entry.date}
{entry.orgs.length} organizaciones: {entry.orgs.slice(0, 4).join(", ")}{entry.orgs.length > 4 ? `…` : ""}
Enviado ✓
{entry.preview}
))}
)}
)}
{/* ══ TAB: AUTOMATIZACIÓN ═════════════════════════════════════════════ */}
{tab === "auto" && (
⚡
Automatización real: Google Apps Script
Para recibir el digest automáticamente cada semana sin abrir nada, usa este script gratuito de Google.
{[
{ step: "1", title: "Ir a Google Apps Script", desc: "Abre script.google.com con tu cuenta de Google (juanmejia88@gmail.com)." },
{ step: "2", title: "Crear nuevo proyecto", desc: 'Haz clic en "Nuevo proyecto" y dale un nombre: "Radar Organizaciones Madrid".' },
{ step: "3", title: "Pegar el código", desc: 'Copia el código de abajo en el editor y reemplaza TU_API_KEY_ANTHROPIC con tu clave de la API de Anthropic (console.anthropic.com).' },
{ step: "4", title: "Configurar el trigger semanal", desc: 'Ve a Triggers (reloj) → "+ Añadir trigger" → función "enviarRadar" → Accionado por tiempo → Semana → Lunes 08:00.' },
{ step: "5", title: "Autorizar y activar", desc: "Ejecuta la función una vez manualmente para autorizar los permisos de Gmail. Desde ese momento llegará automáticamente cada semana." },
].map(({ step, title, desc }) => (
))}
💡 Alternativas sin código
{[
{ app: "Google Alerts", desc: "Crea alertas gratuitas en alerts.google.com con el nombre de cada organización. Llegan a tu Gmail automáticamente.", url: "alerts.google.com" },
{ app: "Make.com (ex-Integromat)", desc: "Conecta una búsqueda periódica con Gmail usando su interfaz visual. Sin código.", url: "make.com" },
{ app: "Zapier", desc: "Monitoriza páginas web y envía alertas a Gmail cuando detecta cambios (plan gratuito disponible).", url: "zapier.com" },
{ app: "LinkedIn Alertas", desc: "Sigue las páginas de las organizaciones en LinkedIn y activa notificaciones de publicaciones nuevas.", url: "linkedin.com" },
].map(({ app, desc, url }) => (
))}
)}
{/* Global CSS for the internal component */}
);
}
// ─── STYLES ─────────────────────────────────────────────────────────────────
const S = {
root: {
fontFamily: "'Crimson Pro', 'Georgia', serif",
background: "#0A1628",
color: "#E8EDF2",
position: "relative",
overflow: "hidden",
borderRadius: "12px",
display: "flex",
flexDirection: "column",
height: "100%", /* Para que quepa bien dentro del #radar-root */
minHeight: "750px",
},
bgGrid: {
position: "absolute", inset: 0, zIndex: 0, pointerEvents: "none",
backgroundImage: "linear-gradient(rgba(11,122,128,0.04) 1px,transparent 1px),linear-gradient(90deg,rgba(11,122,128,0.04) 1px,transparent 1px)",
backgroundSize: "40px 40px",
},
toast: {
position: "absolute", bottom: 24, right: 24, zIndex: 999,
padding: "12px 20px", borderRadius: 8, fontSize: 14, fontWeight: 600,
color: "#fff", boxShadow: "0 4px 20px rgba(0,0,0,0.4)",
animation: "fadeIn 0.3s ease",
},
header: {
position: "relative", zIndex: 10,
background: "linear-gradient(135deg,#0A1628 0%,#1B3A6B 60%,#0B7A80 100%)",
padding: "24px 32px 20px",
borderBottom: "1px solid rgba(11,122,128,0.3)",
display: "flex", alignItems: "flex-end", justifyContent: "space-between", flexWrap: "wrap", gap: 12,
},
headerEyebrow: { fontSize: 10, letterSpacing: 3, color: "#0B7A80", textTransform: "uppercase", marginBottom: 4 },
headerTitle: { margin: 0, fontSize: 26, fontWeight: 700, color: "#F0F4F8", letterSpacing: -0.5 },
headerMeta: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4 },
emailBadge: { fontSize: 12, color: "#7ECFD4", background: "rgba(11,122,128,0.15)", padding: "4px 12px", borderRadius: 20, border: "1px solid rgba(11,122,128,0.3)" },
lastSentBadge: { fontSize: 11, color: "#567", fontStyle: "italic" },
tabBar: {
position: "relative", zIndex: 10,
display: "flex", borderBottom: "1px solid rgba(255,255,255,0.08)",
background: "rgba(10,22,40,0.9)",
padding: "0 32px",
flexWrap: "wrap",
},
tab: {
padding: "14px 20px", fontSize: 13, cursor: "pointer",
background: "transparent", border: "none", color: "#567",
fontFamily: "inherit", borderBottom: "2px solid transparent",
transition: "all 0.2s",
},
tabActive: { color: "#7ECFD4", borderBottomColor: "#0B7A80", fontWeight: 700 },
main: { position: "relative", zIndex: 10, padding: "24px 32px", overflowY: "auto", flex: 1 },
twoCol: { display: "grid", gridTemplateColumns: "1fr", gap: 20, height: "100%", "@media(min-width: 768px)": { gridTemplateColumns: "300px 1fr" } }, /* Necesita CSS Modules/styled-components real para media queries, implementaremos una aproximación o fallback */
leftPanel: { display: "flex", flexDirection: "column", gap: 12, minWidth: "300px" },
rightPanel: {
background: "rgba(255,255,255,0.02)", border: "1px solid rgba(255,255,255,0.07)",
borderRadius: 10, overflow: "auto", padding: 24, minHeight: 400, flex: 1,
},
card: {
background: "rgba(255,255,255,0.04)", border: "1px solid rgba(255,255,255,0.09)",
borderRadius: 10, padding: "16px",
},
cardTitle: { fontSize: 10, letterSpacing: 2, color: "#567", textTransform: "uppercase", marginBottom: 12, display: "flex", alignItems: "center" },
typeBtn: {
width: "100%", display: "flex", gap: 12, alignItems: "center",
padding: "10px 12px", borderRadius: 8, border: "1px solid rgba(255,255,255,0.08)",
background: "transparent", cursor: "pointer", marginBottom: 6,
fontFamily: "inherit", textAlign: "left", transition: "all 0.15s",
},
typeBtnActive: { background: "rgba(11,122,128,0.2)", borderColor: "#0B7A80" },
orgChip: {
fontSize: 11, padding: "2px 8px", borderRadius: 20,
border: "1px solid", color: "#C5D5E5",
background: "rgba(255,255,255,0.04)",
},
btnPrimary: {
padding: "13px 24px", borderRadius: 8, border: "none", cursor: "pointer",
background: "linear-gradient(135deg,#0B7A80,#1B3A6B)",
color: "#fff", fontSize: 14, fontFamily: "inherit", fontWeight: 700,
boxShadow: "0 4px 20px rgba(11,122,128,0.35)", transition: "all 0.2s",
letterSpacing: 0.3,
},
btnSend: {
padding: "12px 24px", borderRadius: 8, cursor: "pointer",
background: "rgba(26,122,74,0.25)", border: "1px solid #1A7A4A",
color: "#7ECFD4", fontSize: 13, fontFamily: "inherit", fontWeight: 600,
transition: "all 0.2s",
width: "100%",
},
btnSmall: {
padding: "6px 14px", borderRadius: 6, cursor: "pointer",
background: "transparent", border: "1px solid rgba(255,255,255,0.15)",
color: "#A0B4C5", fontSize: 12, fontFamily: "inherit", transition: "all 0.15s",
},
select: {
background: "rgba(255,255,255,0.06)", color: "#C5D5E5",
border: "1px solid rgba(255,255,255,0.15)", borderRadius: 6,
padding: "7px 12px", fontSize: 12, fontFamily: "inherit", cursor: "pointer",
},
orgCard: {
padding: 12, borderRadius: 8, border: "1px solid",
cursor: "pointer", transition: "all 0.15s",
},
loadingState: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", minHeight: 300, textAlign: "center" },
progressBar: { marginTop: 24, height: 3, background: "rgba(255,255,255,0.05)", borderRadius: 3, overflow: "hidden", width: 200 },
progressFill: { height: "100%", width: "40%", background: "linear-gradient(90deg,#0B7A80,#7ECFD4)", borderRadius: 3, animation: "scanLine 1.8s ease-in-out infinite" },
emptyState: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", minHeight: 300, textAlign: "center" },
resultsText: { animation: "fadeIn 0.4s ease" },
resH3: { margin: "20px 0 8px", fontSize: 15, color: "#7ECFD4", borderBottom: "1px solid rgba(11,122,128,0.3)", paddingBottom: 6 },
resStrong: { margin: "14px 0 4px", fontSize: 14, fontWeight: 700, color: "#E8EDF2" },
resBullet: { margin: "4px 0", fontSize: 13, color: "#B0C4D4", lineHeight: 1.7, display: "flex", gap: 6 },
resPara: { margin: "5px 0", fontSize: 13, color: "#A0B8CC", lineHeight: 1.8 },
historyCard: {
background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.08)",
borderRadius: 10, padding: 20, animation: "fadeIn 0.3s ease",
},
};
function hexRgb(hex) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `${r},${g},${b}`;
}
// ─── INIT ─────────────────────────────────────────────────────────
const rootElement = document.getElementById("radar-root");
if (rootElement) {
const root = ReactDOM.createRoot(rootElement);
root.render( );
}