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 ; } function ServicePlaceholder({ label }) { const id = "p" + label.replace(/\W/g, ""); return (
{label}
); } /* ─── 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 ( ); } /* ─── 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 ( //
// Edisik team at work //
// ); // } function StatsSection({ t }) { const [ref, vis] = useReveal(); return (
{t.stats.map((s, i) => (
{s.value}
{s.label}
))}
); } /* ─── 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}

{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 (
{isOpen && (

{s.solution}

{s.outcomes.map((o, oi) => (
{o}
))}
{SERVICE_IMAGES[s.id] ? (
{s.title}
) : ( )}
)}
); })}
); } /* ─── 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 && }
{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 && }
); } /* ─── INSIGHTS PREVIEW ───────────────────────────────────────── */ function InsightsPreview({ t, navigate }) { const [ref, vis] = useReveal(); const ins = t.insights; return (
{ins.eyebrow}

{ins.title}

{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.title}
)}
{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] ? (
{s.title}
) : ( )}
); } 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 (
{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.icon}

{s.title}

{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) => (

{p.title}

{p.desc}

))}

{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.title}
{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 (
{icon}
{title}
{href ? {value} :
{value}
}
); } 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) => (
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) => (
  1. {i + 1} {step}
  2. ))}
{/* Right column — form card */}
{sent ? (

{t.form.success}

) : (
{/* Honeypot — hidden from users, catches bots */} {renderField("name", "text", true)} {renderField("email", "email", true)} {renderField("company", "text", false)} {renderField("phone", "tel", false)}