const { useState, useEffect, useRef } = React;
const C = () => window.EDISIK_CONTENT;
function useReveal(threshold = 0.12) {
const ref = useRef(null);
const [vis, setVis] = useState(false);
useEffect(() => {
const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setVis(true); obs.disconnect(); } }, { threshold });
if (ref.current) obs.observe(ref.current);
return () => obs.disconnect();
}, []);
return [ref, vis];
}
/* ─── Shared UI ──────────────────────────────────────────────── */
function Eyebrow({ children, dark }) {
return {children} ;
}
function Btn({ children, variant, onClick, type, disabled }) {
const [h, setH] = useState(false);
const v = variant || "primary";
const base = { fontFamily: "'Inter', sans-serif", fontSize: 14, fontWeight: 600, letterSpacing: "0.03em", padding: "13px 28px", borderRadius: 3, cursor: disabled ? "not-allowed" : "pointer", border: "none", transition: "all 0.18s ease", display: "inline-block", whiteSpace: "nowrap", opacity: disabled ? 0.6 : 1 };
const s = {
primary: { background: h ? "#1e3f6e" : "#2B5797", color: "#fff" },
white: { background: h ? "#dce7f5" : "#fff", color: "#2B5797" },
ghost: { background: "transparent", color: "#fff", border: `1.5px solid ${h ? "#fff" : "rgba(255,255,255,0.45)"}` },
outline: { background: h ? "#2B5797" : "transparent", color: h ? "#fff" : "#2B5797", border: "1.5px solid #2B5797" }
};
return setH(true)} onMouseLeave={() => setH(false)}>{children} ;
}
function ServicePlaceholder({ label }) {
const id = "p" + label.replace(/\W/g, "");
return (
);
}
/* ─── NAV ────────────────────────────────────────────────────── */
function EdisikNav({ lang, setLang, navigate, page }) {
const [scrolled, setScrolled] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const t = C()[lang].nav;
const isHome = page === "home";
useEffect(() => {
const fn = () => setScrolled(window.scrollY > 40);
window.addEventListener("scroll", fn, { passive: true });
return () => window.removeEventListener("scroll", fn);
}, []);
const solid = scrolled || !isHome;
const tc = solid ? "#111827" : "#fff";
const burgerColor = menuOpen ? "#fff" : (solid ? "#111827" : "#fff");
useEffect(() => {
document.body.style.overflow = menuOpen ? "hidden" : "";
return () => { document.body.style.overflow = ""; };
}, [menuOpen]);
const go = (p) => { setMenuOpen(false); navigate(p); };
return (
go("home")} style={{ background: "none", border: "none", cursor: "pointer", padding: 0 }}>
{[["capabilities","capabilities"],["partner","partner"],["insights","insights"],["contact","contact"]].map(([key, pg]) => {
const active = page === pg;
const accent = pg === "partner";
return (
navigate(pg)} style={{
background: "none", border: "none", cursor: "pointer", fontFamily: "'Inter', sans-serif",
fontSize: 14, fontWeight: active || accent ? 600 : 400,
color: active ? (solid ? "#2B5797" : "#5B8FD9") : (accent && !active ? (solid ? "#2B5797" : "#93B8E8") : tc),
borderBottom: active ? `2px solid ${solid ? "#2B5797" : "#5B8FD9"}` : "2px solid transparent",
paddingBottom: 2, transition: "color 0.2s"
}}>{t[key]}
);
})}
{["es","en"].map(l => (
setLang(l)} style={{ background: lang === l ? (solid ? "#2B5797" : "#fff") : "transparent", color: lang === l ? (solid ? "#fff" : "#2B5797") : tc, border: "none", cursor: "pointer", borderRadius: 16, padding: "4px 12px", fontFamily: "'Inter', sans-serif", fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.06em", transition: "all 0.2s" }}>{l}
))}
navigate("contact")}>{t.cta}
setMenuOpen(!menuOpen)}
aria-label="Menu"
aria-expanded={menuOpen}
style={{ display: "none", background: "none", border: "none", cursor: "pointer", width: 40, height: 40, alignItems: "center", justifyContent: "center", padding: 0 }}
>
{menuOpen && (
{[["capabilities","capabilities"],["partner","partner"],["insights","insights"],["contact","contact"]].map(([key, pg]) => {
const active = page === pg;
return (
go(pg)} style={{
background: "none", border: "none", borderBottom: "1px solid rgba(255,255,255,0.08)",
cursor: "pointer", padding: "20px 4px", textAlign: "left",
color: active ? "#5B8FD9" : "#fff", fontFamily: "var(--font-display)",
fontSize: 22, fontWeight: 700, letterSpacing: "-0.01em"
}}>{t[key]}
);
})}
{["es","en"].map(l => (
setLang(l)} style={{
background: lang === l ? "#fff" : "transparent",
color: lang === l ? "#2B5797" : "#fff",
border: "1px solid rgba(255,255,255,0.2)",
borderRadius: 18, padding: "8px 18px",
fontFamily: "'Inter', sans-serif", fontSize: 11, fontWeight: 700,
textTransform: "uppercase", letterSpacing: "0.08em", cursor: "pointer"
}}>{l}
))}
go("contact")}>{t.cta}
)}
);
}
/* ─── FOOTER ─────────────────────────────────────────────────── */
function EdisikFooter({ lang, navigate }) {
const t = C()[lang].footer;
const compDests = ["partner","insights","contact"];
return (
);
}
/* ─── HERO PARTICLE NETWORK ──────────────────────────────────── */
function HeroParticles() {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
let raf;
const resize = () => { canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; };
resize();
window.addEventListener("resize", resize);
const N = 42;
const nodes = Array.from({ length: N }, () => ({
x: Math.random() * canvas.width, y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 0.38, vy: (Math.random() - 0.5) * 0.38,
r: Math.random() * 1.8 + 0.8, pulse: Math.random() * Math.PI * 2,
}));
const LINK_DIST = 140;
const draw = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const n of nodes) {
n.x += n.vx; n.y += n.vy;
if (n.x < 0 || n.x > canvas.width) n.vx *= -1;
if (n.y < 0 || n.y > canvas.height) n.vy *= -1;
n.pulse += 0.018;
}
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
const a = nodes[i], b = nodes[j];
const dx = a.x - b.x, dy = a.y - b.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < LINK_DIST) {
ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y);
ctx.strokeStyle = `rgba(91,143,217,${(1 - dist / LINK_DIST) * 0.28})`; ctx.lineWidth = 0.8; ctx.stroke();
}
}
}
for (const n of nodes) {
const glow = 0.55 + 0.35 * Math.sin(n.pulse);
ctx.beginPath(); ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
ctx.fillStyle = `rgba(91,143,217,${glow})`; ctx.fill();
if (n.r > 2) {
ctx.beginPath(); ctx.arc(n.x, n.y, n.r * 3.5, 0, Math.PI * 2);
ctx.fillStyle = `rgba(91,143,217,${glow * 0.07})`; ctx.fill();
}
}
raf = requestAnimationFrame(draw);
};
draw();
return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", resize); };
}, []);
return (
);
}
/* ─── HERO SHARED BG ─────────────────────────────────────────── */
function HeroBg() {
return (
<>
>
);
}
/* Layout – Nexora: compact headline + team photo below */
function HeroLayoutNexora({ h, v, navigate }) {
return (
{h.eyebrow}
{v.line1} {v.line2} {v.line3}
{h.sub}
navigate("contact")}>{h.cta}
navigate("capabilities")}>{h.ctaSub}
);
}
/* ─── HERO SECTION ───────────────────────────────────────────── */
function HeroSection({ t, navigate }) {
const h = t.hero;
const v = h.variants.find(x => x.key === "competitive") || h.variants[0];
return (
);
}
/* ─── TEAM PHOTO ─────────────────────────────────────────────── */
// function TeamPhoto() {
// return (
//
//
//
// );
// }
function StatsSection({ t }) {
const [ref, vis] = useReveal();
return (
{t.stats.map((s, i) => (
))}
);
}
/* ─── HOME SERVICES TEASER ──────────────────────────────────── */
const AI_BLURBS = {
es: {
web: "Generamos código, automatizamos testing y usamos IA para optimizar rendimiento y UX en tiempo real.",
mobile: "Integramos LLMs, visión computacional y personalización inteligente directamente en la app.",
cloud: "Monitoreo predictivo, auto-scaling inteligente y detección de anomalías antes de que afecten tu operación.",
enterprise: "Automatizamos flujos con LLMs, copilots internos y extracción de datos no estructurados.",
data: "Pipelines con IA, modelos predictivos y análisis en tiempo real integrados en tu arquitectura.",
security: "Detección de amenazas en tiempo real y compliance automatizado con modelos de seguridad avanzados.",
},
en: {
web: "We generate code, automate testing and use AI to optimize performance and UX in real time.",
mobile: "We integrate LLMs, computer vision and intelligent personalization directly into the app.",
cloud: "Predictive monitoring, intelligent auto-scaling and anomaly detection before they affect your operations.",
enterprise: "We automate workflows with LLMs, internal copilots and unstructured data extraction.",
data: "AI pipelines, predictive models and real-time analytics integrated into your architecture.",
security: "Real-time threat detection and automated compliance with advanced security models.",
}
};
const SERVICE_IMAGES = {
web: "/public/services/web.png",
mobile: "/public/services/app.png",
cloud: "/public/services/cloud.png",
enterprise: "/public/services/software.png",
data: "/public/services/ux.png",
};
const ARTICLE_IMAGES = [
"/public/article/custom-web-application-cost.jpg",
"/public/article/mvp-development-what-to-build.jpg",
"/public/article/hiring-software-development-agency-mistakes.jpg",
];
const ARTICLE_URLS = [
"/articles/custom-web-application-cost",
"/articles/mvp-development-what-to-build",
"/articles/hiring-software-development-agency-mistakes",
];
const SERVICE_ICONS = {
web: (
),
mobile: (
),
cloud: (
),
enterprise: (
),
data: (
),
security: (
),
};
const IS_TOUCH = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(hover: none)").matches;
function CapabilityCard({ item, aiBlurb, navigate, vis, idx }) {
const [hov, setHov] = useState(false);
const open = IS_TOUCH || hov;
return (
setHov(true)}
onMouseLeave={() => setHov(false)}
onClick={() => navigate("capabilities")}
style={{
border: "1px solid #E5E7EB",
padding: "28px 28px 24px",
cursor: "pointer",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
minHeight: 180,
background: open ? "#0A1628" : "#fff",
transition: "background 0.22s ease",
opacity: vis ? 1 : 0,
transform: vis ? "none" : "translateY(16px)",
transitionDelay: `${idx * 0.06}s`,
transitionProperty: "background, opacity, transform",
transitionDuration: "0.22s, 0.5s, 0.5s",
}}
>
{!open ? (
{item.title}
) : (
<>
{IS_TOUCH && (
{item.title}
)}
{aiBlurb}
>
)}
{SERVICE_ICONS[item.id] || null}
→
);
}
function ServicesTeaser({ t, lang, navigate }) {
const [ref, vis] = useReveal();
const sv = t.services;
const blurbs = AI_BLURBS[lang] || AI_BLURBS["es"];
return (
{sv.eyebrow}
{lang === "es" ? <>La IA aplicada a cada solución.> : <>AI applied to every solution.>}
{sv.intro}
navigate("capabilities")} style={{ background: "none", border: "none", cursor: "pointer", fontFamily: "'Inter', sans-serif", fontSize: 13, fontWeight: 600, color: "#2B5797", textAlign: "left", padding: 0 }}>{sv.viewAll} →
{sv.items.map((item, i) => (
))}
);
}
/* ─── ACCORDION (Capabilities page) ─────────────────────────── */
function ServiceAccordion({ items }) {
const [open, setOpen] = useState(0);
return (
{items.map((s, i) => {
const isOpen = open === i;
return (
setOpen(isOpen ? -1 : i)} style={{
width: "100%", background: isOpen ? "#F7F8FC" : "#fff", border: "none", cursor: "pointer",
padding: "32px 40px", display: "flex", alignItems: "flex-start", gap: 28, textAlign: "left",
borderLeft: `4px solid ${isOpen ? "#2B5797" : "transparent"}`, transition: "all 0.2s ease"
}}>
{String(i + 1).padStart(2, "0")}
+
{isOpen && (
{s.solution}
{s.outcomes.map((o, oi) => (
→
{o}
))}
{SERVICE_IMAGES[s.id] ? (
) : (
)}
)}
);
})}
);
}
/* ─── TECH PARTNER TEASER ────────────────────────────────────── */
const PARTNER_MODES = {
es: [
{ num: "01", mode: "Consultoría técnica", desc: "Revisamos tu arquitectura, stack y procesos. Te decimos lo que ves, lo que no ves y lo que deberías cambiar — con propuestas concretas.", tag: "Ideal para: diagnósticos y auditorías",
icon: ( ) },
{ num: "02", mode: "Desarrollo a medida", desc: "Construimos lo que necesitas. Una feature, una app, un sistema completo. Sin código genérico. Sin excusas. Entregamos.", tag: "Ideal para: proyectos con alcance definido",
icon: ( ) },
{ num: "03", mode: "Equipo externo integrado", desc: "Nos sumamos a tu operación. Standup, sprint, retrospectiva. Somos tu equipo de desarrollo sin la carga de tenerlos en nómina.", tag: "Ideal para: empresas sin equipo técnico propio",
icon: ( ) },
{ num: "04", mode: "Dirección técnica", desc: "Lideramos o gestionamos tu equipo de desarrollo. Definimos la estrategia tecnológica, priorizamos y marcamos el ritmo.", tag: "Ideal para: empresas con equipo que necesita dirección",
icon: ( ) },
],
en: [
{ num: "01", mode: "Tech consulting", desc: "We review your architecture, stack and processes. We tell you what you see, what you don't, and what you should change — with concrete proposals.", tag: "Best for: diagnostics and audits",
icon: ( ) },
{ num: "02", mode: "Custom development", desc: "We build what you need. A feature, an app, a full system. No generic code. No excuses. We deliver.", tag: "Best for: scoped projects",
icon: ( ) },
{ num: "03", mode: "Embedded external team", desc: "We join your operation. Standup, sprint, retrospective. We are your dev team without the overhead of payroll.", tag: "Best for: companies without in-house tech",
icon: ( ) },
{ num: "04", mode: "Tech leadership", desc: "We lead or manage your development team. We define the tech strategy, prioritize and set the pace.", tag: "Best for: teams that need direction",
icon: ( ) },
],
};
function IndustriesSection({ t, lang, navigate }) {
const [ref, vis] = useReveal();
const l = lang || "es";
const modes = PARTNER_MODES[l] || PARTNER_MODES["es"];
const tp = t.techPartner;
const isEs = l === "es";
return (
{tp.eyebrow}
{isEs ? "No un proveedor.\nTu equipo técnico externo." : "Not a vendor.\nYour external tech team."}
{isEs ? "No queremos ser parte de tu empresa — queremos ser el equipo que hace que tu empresa funcione mejor. Sin nómina, sin oficina, sin overhead. Solo expertise, velocidad y tecnología que resuelve." : "We don't want to be part of your company — we want to be the team that makes your company work better. No payroll, no office, no overhead. Just expertise, speed and technology that solves."}
{navigate &&
navigate("partner")} style={{ background: "none", border: "none", cursor: "pointer", fontFamily: "'Inter', sans-serif", fontSize: 13, fontWeight: 600, color: "#5B8FD9", textAlign: "left", padding: 0 }}>
{isEs ? "Ver cómo trabajamos →" : "See how we work →"}
}
{isEs ? "Alcance del servicio" : "Service scope"}
{isEs ? "Ligero → Profundo" : "Light → Deep"}
{modes.map((m, i) => {
const [hov, setHov] = useState(false);
return (
setHov(true)}
onMouseLeave={() => setHov(false)}
style={{
borderRight: i < 3 ? "1px solid rgba(255,255,255,0.07)" : "none",
padding: "40px 36px 48px",
background: hov ? "rgba(91,143,217,0.07)" : "transparent",
transition: "background 0.2s",
opacity: vis ? 1 : 0,
transform: vis ? "none" : "translateY(24px)",
transitionProperty: "background, opacity, transform",
transitionDuration: "0.2s, 0.5s, 0.5s",
transitionDelay: `0s, ${i * 0.08}s, ${i * 0.08}s`,
cursor: navigate ? "pointer" : "default",
}}
onClick={() => navigate && navigate("partner")}
>
{m.num}
{m.mode}
{m.desc}
{m.tag}
{m.icon}
);
})}
{isEs ? '"El talento técnico más caro no es el que contratas. Es el que necesitabas y no tenías."' : '"The most expensive tech talent isn\'t the one you hire. It\'s the one you needed and didn\'t have."'}
{navigate &&
navigate("partner")} style={{ background: "none", border: "1px solid rgba(255,255,255,0.15)", borderRadius: 2, cursor: "pointer", fontFamily: "'Inter', sans-serif", fontSize: 13, fontWeight: 600, color: "rgba(255,255,255,0.6)", padding: "10px 20px", whiteSpace: "nowrap", flexShrink: 0 }}>
{isEs ? "Cómo trabajamos →" : "How we work →"}
}
);
}
/* ─── INSIGHTS PREVIEW ───────────────────────────────────────── */
function InsightsPreview({ t, navigate }) {
const [ref, vis] = useReveal();
const ins = t.insights;
return (
{ins.eyebrow}
{ins.title}
navigate("insights")} style={{ background: "none", border: "none", cursor: "pointer", fontFamily: "'Inter', sans-serif", fontSize: 14, fontWeight: 600, color: "#2B5797", whiteSpace: "nowrap" }}>{ins.cta} →
{ins.items.map((a, i) =>
)}
);
}
function ArticleCard({ a, i, vis }) {
const [h, setH] = useState(false);
const img = ARTICLE_IMAGES[i];
return (
setH(true)}
onMouseLeave={() => setH(false)}
onClick={() => { window.location.href = ARTICLE_URLS[i]; }}
style={{
background: "#fff", borderRadius: 4, cursor: "pointer", overflow: "hidden",
borderBottom: `3px solid ${h ? "#2B5797" : "transparent"}`,
opacity: vis ? 1 : 0, transform: vis ? "none" : "translateY(20px)",
transition: `opacity 0.5s ease ${i * 0.1}s, transform 0.5s ease ${i * 0.1}s, border-color 0.2s`
}}
>
{img && (
)}
{a.tag}
{a.date}
{a.title}
);
}
/* ─── CTA BANNER ─────────────────────────────────────────────── */
function CTABanner({ t, navigate }) {
const cta = t.ctaBanner;
const [ref, vis] = useReveal();
return (
{cta.title}
{cta.sub}
navigate("contact")}>{cta.cta}
);
}
/* ─── PAGE HERO ──────────────────────────────────────────────── */
function PageHero({ eyebrow, title, sub, badge }) {
return (
{badge &&
{badge}
}
{eyebrow}
{title}
{sub}
);
}
/* ─── PAGES ──────────────────────────────────────────────────── */
function HomePage({ lang, navigate }) {
const t = C()[lang];
return (
{/* */}
);
}
/* ─── CAPABILITIES PAGE ──────────────────────────────────────── */
function ServiceChapter({ s, i, lang, blurbs, isEs }) {
const [sRef, sVis] = useReveal();
const dark = i % 2 !== 0;
const bg = dark ? "#0A1628" : "#F7F8FC";
const col = dark ? "#fff" : "#111827";
const sub = dark ? "rgba(255,255,255,0.52)" : "#374151";
const dim = dark ? "rgba(255,255,255,0.35)" : "#6B7280";
const accent = dark ? "#5B8FD9" : "#2B5797";
return (
{String(i + 1).padStart(2, "0")}
{SERVICE_ICONS[s.id]}
{s.title}
{s.problem}
{s.solution}
{s.outcomes.map((o, oi) => (
→
{o}
))}
AI
{isEs ? "Con Inteligencia Artificial" : "With AI"}
{blurbs[s.id]}
{SERVICE_IMAGES[s.id] ? (
) : (
)}
);
}
function CapabilitiesPage({ lang, navigate }) {
const t = C()[lang];
const cp = t.capabilitiesPage;
const sv = t.services;
const blurbs = AI_BLURBS[lang] || AI_BLURBS["es"];
const isEs = lang === "es";
const [gridRef, gridVis] = useReveal();
return (
{cp.eyebrow}
{cp.title}
{cp.sub}
{sv.items.map((s, i) => (
{ e.currentTarget.style.background = "rgba(91,143,217,0.18)"; e.currentTarget.style.color = "#fff"; }}
onMouseLeave={e => { e.currentTarget.style.background = "rgba(255,255,255,0.06)"; e.currentTarget.style.color = "rgba(255,255,255,0.6)"; }}>
{String(i + 1).padStart(2, "0")}
{s.title}
))}
{sv.eyebrow}
{isEs ? <>La IA aplicada a cada solución.> : <>AI applied to every solution.>}
{sv.intro}
{sv.items.map((item, i) => (
{ window.location.hash = `service-${item.id}`; }} vis={gridVis} idx={i} />
))}
{isEs ? "Pasa el cursor sobre cada tarjeta para ver el ángulo de IA →" : "Hover each card to see the AI angle →"}
{sv.items.map((s, i) => (
))}
);
}
/* ─── TECH PARTNER PAGE ──────────────────────────────────────── */
function TechPartnerPage({ lang, navigate }) {
const t = C()[lang];
const tp = t.techPartner;
const [refServices, visServices] = useReveal();
const [refSteps, visSteps] = useReveal();
const [refProfiles, visProfiles] = useReveal();
return (
{tp.badge}
{tp.eyebrow}
{tp.title}
{tp.sub}
navigate("contact")}>{tp.cta}
{tp.withOrWithout.title}
{tp.withOrWithout.sub}
{tp.withOrWithout.cards.map((card, i) => (
{card.scenario}
{card.title}
{card.desc}
))}
{tp.eyebrow}
{tp.whatTitle}
{tp.whatIntro}
{tp.services.map((s, i) => {
const [hov, setHov] = useState(false);
return (
setHov(true)} onMouseLeave={() => setHov(false)}
style={{ background: hov ? "#fff" : "#F7F8FC", padding: "44px 40px", transition: "background 0.2s", opacity: visServices ? 1 : 0, transform: visServices ? "none" : "translateY(20px)", transitionProperty: "background, opacity, transform", transitionDuration: "0.2s, 0.5s, 0.5s", transitionDelay: `0s, ${i * 0.07}s, ${i * 0.07}s` }}>
{s.desc}
{s.points.map((p, pi) => (
→
{p}
))}
);
})}
{tp.eyebrow}
{tp.howTitle}
{tp.howSub}
{tp.steps.map((step, i) => (
{step.num}
{step.title}
{step.desc}
))}
{tp.eyebrow}
{tp.forWho}
{tp.profiles.map((p, i) => (
))}
{tp.ctaSub}
navigate("contact")}>{tp.cta}
);
}
function FeaturedArticle({ a, i, lang }) {
const [h, setH] = useState(false);
const readLabel = lang === "es" ? "Leer artículo" : "Read article";
return (
setH(true)}
onMouseLeave={() => setH(false)}
onClick={() => { window.location.href = ARTICLE_URLS[i]; }}
style={{
background: "#fff", borderRadius: 4, cursor: "pointer", overflow: "hidden",
display: "grid", gridTemplateColumns: "1fr 1fr",
borderBottom: `3px solid ${h ? "#2B5797" : "transparent"}`,
transition: "border-color 0.2s", marginBottom: 24,
}}
>
{a.tag}
{a.date}
{a.title}
{readLabel} →
);
}
function InsightsPage({ lang, navigate }) {
const t = C()[lang];
const ip = t.insightsPage;
const articles = t.insights.items;
return (
{articles.slice(1).map((a, i) =>
)}
);
}
/* ─── CONTACT PAGE ───────────────────────────────────────────── */
const CONTACT_ICONS = {
calendar: (
),
mail: (
)
};
function ContactInfoBlock({ icon, title, value, href }) {
return (
);
}
function ContactPage({ lang }) {
const t = C()[lang].contact;
const [form, setForm] = useState({ name: "", email: "", company: "", phone: "", timeline: "", message: "" });
const [sent, setSent] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [focused, setFocused] = useState(null);
const labelStyle = { fontSize: 13, fontWeight: 600, color: "#374151", marginBottom: 8, display: "block", fontFamily: "'Inter', sans-serif" };
const reqMark = * ;
const iStyle = (f) => ({
width: "100%",
background: "#fff",
border: `1px solid ${focused === f ? "#2B5797" : "#E5E7EB"}`,
borderRadius: 6,
padding: "12px 14px",
color: "#111827",
fontSize: 15,
fontFamily: "'Inter', sans-serif",
outline: "none",
transition: "border-color 0.2s, box-shadow 0.2s",
boxShadow: focused === f ? "0 0 0 3px rgba(43,87,151,0.12)" : "none",
boxSizing: "border-box"
});
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const res = await fetch("/api/contact.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...form, website: "" })
});
const data = await res.json();
if (data.success) {
setSent(true);
} else {
setError(data.error || (lang === "es" ? "Error al enviar. Por favor, inténtalo de nuevo." : "Failed to send. Please try again."));
}
} catch (err) {
setError(lang === "es" ? "Error de red. Por favor, inténtalo de nuevo." : "Network error. Please try again.");
} finally {
setLoading(false);
}
};
const renderField = (key, type, required) => (
{t.form[key]}{required && reqMark}
setForm({ ...form, [key]: e.target.value })}
onFocus={() => setFocused(key)} onBlur={() => setFocused(null)}
style={iStyle(key)}
/>
);
return (
{/* Left column — info */}
{t.title}
{t.sub}
{t.expectTitle}
{t.steps.map((step, i) => (
{i + 1}
{step}
))}
{/* Right column — form card */}
);
}
/* ─── APP ROOT ───────────────────────────────────────────────── */
const PATH_TO_PAGE = { "/": "home", "/capabilities": "capabilities", "/partner": "partner", "/insights": "insights", "/contact": "contact" };
const PAGE_TO_PATH = { home: "/", capabilities: "/capabilities", partner: "/partner", insights: "/insights", contact: "/contact" };
function EdisikApp() {
const [lang, setLang] = useState("es");
const [page, setPage] = useState(PATH_TO_PAGE[window.location.pathname] || "home");
const [key, setKey] = useState(0);
useEffect(() => {
const onPop = () => {
const p = PATH_TO_PAGE[window.location.pathname] || "home";
setPage(p);
setKey(k => k + 1);
};
window.addEventListener("popstate", onPop);
return () => window.removeEventListener("popstate", onPop);
}, []);
const navigate = (p) => {
setPage(p);
setKey(k => k + 1);
window.scrollTo({ top: 0, behavior: "instant" });
history.pushState(null, "", PAGE_TO_PATH[p] || "/");
};
return (
{page === "home" && }
{page === "capabilities" && }
{page === "partner" && }
{page === "insights" && }
{page === "contact" && }
);
}
window.EdisikNav = EdisikNav;
window.EdisikFooter = EdisikFooter;
const _rootEl = document.getElementById("root");
if (_rootEl) {
ReactDOM.createRoot(_rootEl).render( );
}
if (typeof window !== "undefined" && typeof window.__edisikChromeReady === "function") {
window.__edisikChromeReady();
}