/* Portafolio Cristian — single React component bundle */
const { useState, useEffect, useRef, useMemo } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "amber",
  "showSpotlight": true,
  "heroLayout": "split"
} /*EDITMODE-END*/;

const ACCENT_PRESETS = {
  amber: { a: "oklch(0.82 0.14 75)", b: "oklch(0.72 0.12 40)" },
  emerald: { a: "oklch(0.78 0.14 155)", b: "oklch(0.68 0.13 200)" },
  violet: { a: "oklch(0.78 0.13 305)", b: "oklch(0.68 0.16 270)" },
  rose: { a: "oklch(0.80 0.13 15)", b: "oklch(0.70 0.15 345)" }
};

/* ---------- Reveal hook ---------- */
function useReveal() {
  useEffect(() => {
    const els = document.querySelectorAll('.reveal');
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) {
          e.target.classList.add('in');
          io.unobserve(e.target);
        }
      });
    }, { threshold: .12, rootMargin: '0px 0px -8% 0px' });
    els.forEach((el) => io.observe(el));
    return () => io.disconnect();
  }, []);
}

/* ---------- Count-up ---------- */
function CountUp({ to, suffix = "" }) {
  const [v, setV] = useState(0);
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) {
        const start = performance.now();
        const dur = 1200;
        const tick = (t) => {
          const p = Math.min(1, (t - start) / dur);
          const eased = 1 - Math.pow(1 - p, 3);
          setV(Math.round(eased * to));
          if (p < 1) requestAnimationFrame(tick);
        };
        requestAnimationFrame(tick);
        io.disconnect();
      }
    });
    io.observe(el);
    return () => io.disconnect();
  }, [to]);
  return <span ref={ref}>{v}{suffix}</span>;
}

/* ---------- Icons (inline svg, simple) ---------- */
const Arrow = ({ size = 14 }) =>
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
    <path d="M7 17L17 7" /><path d="M9 7h8v8" />
  </svg>;

const ArrowDown = () =>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
    <path d="M12 5v14" /><path d="M5 12l7 7 7-7" />
  </svg>;

const Mail = () =>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
    <rect x="3" y="5" width="18" height="14" rx="2" /><path d="M3 7l9 7 9-7" />
  </svg>;

const GitHub = () =>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 .5a11.5 11.5 0 0 0-3.6 22.4c.6.1.8-.3.8-.6v-2.1c-3.2.7-3.9-1.4-3.9-1.4-.5-1.3-1.3-1.7-1.3-1.7-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.7-1.6-2.6-.3-5.3-1.3-5.3-5.8 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.3 1.2a11.4 11.4 0 0 1 6 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.6.2 2.8.1 3.1.7.8 1.2 1.8 1.2 3.1 0 4.5-2.7 5.5-5.3 5.8.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A11.5 11.5 0 0 0 12 .5z" /></svg>;

const LinkedIn = () =>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M4.98 3.5A2.5 2.5 0 1 1 5 8.5a2.5 2.5 0 0 1 0-5zM3 9.5h4v11H3v-11zm7 0h3.8v1.5h.05c.53-1 1.83-2 3.77-2 4.04 0 4.78 2.66 4.78 6.13V20.5H18.6v-4.8c0-1.14-.02-2.6-1.58-2.6-1.58 0-1.82 1.24-1.82 2.52v4.88H11v-11z" /></svg>;

const WhatsApp = () =>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M20.5 3.5A11.4 11.4 0 0 0 12 0C5.5 0 .2 5.3.2 11.8c0 2.1.5 4.1 1.6 5.9L0 24l6.5-1.7a11.7 11.7 0 0 0 5.5 1.4h.05c6.5 0 11.8-5.3 11.8-11.8 0-3.2-1.2-6.1-3.4-8.4zM12 21.6h-.05c-1.7 0-3.4-.5-4.9-1.4l-.35-.2-3.85 1 1-3.75-.2-.35a9.6 9.6 0 0 1-1.5-5.1c0-5.3 4.3-9.6 9.6-9.6 2.6 0 5 1 6.8 2.85a9.5 9.5 0 0 1 2.8 6.75c0 5.3-4.3 9.6-9.6 9.6zm5.3-7.2c-.3-.15-1.7-.85-2-.95-.25-.1-.45-.15-.65.15-.2.3-.75.95-.9 1.15-.15.2-.35.2-.65.05-.3-.15-1.25-.45-2.4-1.5-.9-.8-1.5-1.8-1.65-2.1-.15-.3-.05-.45.1-.6.15-.15.3-.35.45-.55.15-.2.2-.3.3-.55.1-.2.05-.4-.05-.55-.05-.15-.65-1.55-.9-2.15-.25-.55-.5-.45-.65-.5h-.55c-.2 0-.55.05-.85.4-.3.3-1.1 1.05-1.1 2.55s1.1 2.95 1.3 3.15c.15.2 2.2 3.35 5.35 4.7.75.3 1.35.5 1.8.65.75.25 1.45.2 2 .15.6-.1 1.85-.75 2.1-1.5.25-.75.25-1.4.2-1.5-.05-.1-.25-.15-.55-.3z" /></svg>;

const FileDown = () =>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6">
    <path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z" /><path d="M14 3v6h6" />
    <path d="M12 12v6" /><path d="M9 15l3 3 3-3" />
  </svg>;


/* ---------- Tech icons (simple stylized marks) ---------- */
const TechIcon = ({ name, size = 32 }) => {
  const props = { width: size, height: size, viewBox: "0 0 32 32", fill: "none" };
  switch (name) {
    case "java":return (
        <img src="https://img.icons8.com/ios/50/FFFFFF/java-coffee-cup-logo--v1.png" width={size} height={size} alt="Java" style={{ display: 'block' }} />);

    case "spring":return (
        <svg width={size} height={size} viewBox="0 0 48 48" fill="none">
          <path d="M43.982,23.635c0.069,-4.261 -0.891,-9.328 -2.891,-15.273l-1.568,-4.662l-2.13,4.433c-0.114,0.237 -0.244,0.469 -0.38,0.698c-3.499,-3.004 -8.039,-4.831 -13.013,-4.831c-11.046,0 -20,8.954 -20,20c0,11.046 8.954,20 20,20c11.046,0 20,-8.954 20,-20c0,-0.123 -0.016,-0.242 -0.018,-0.365z" fill="#a3a4a1" />
          <path d="M39.385,32.558c-3.123,4.302 -8.651,4.533 -13.854,4.442h-6.781h-1.938c4.428,-1.593 7.063,-1.972 9.754,-3.4c5.068,-2.665 10.078,-8.496 11.121,-14.562c-1.93,5.836 -7.779,10.85 -13.109,12.889c-3.652,1.393 -10.248,2.745 -10.248,2.745l-0.267,-0.145c-4.49,-2.259 -4.626,-12.313 3.537,-15.559c3.574,-1.423 6.993,-0.641 10.854,-1.593c4.122,-1.012 8.89,-4.208 10.83,-8.375c2.172,6.667 4.786,17.106 0.101,23.558zM15.668,38.445c-0.282,0.35 -0.713,0.555 -1.163,0.555c-0.823,0 -1.495,-0.677 -1.495,-1.5c0,-0.823 0.677,-1.5 1.495,-1.5c0.341,0 0.677,0.118 0.941,0.336c0.64,0.519 0.74,1.469 0.222,2.109z" fill="#ffffff" />
        </svg>);

    case "angular":return (
        <svg {...props}>
        <path d="M16 3L4 7l2 16 10 6 10-6 2-16-12-4z" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinejoin="round" />
        <path d="M16 7l-6 15h2.5l1.3-3.2h4.4L19.5 22H22L16 7zm-1.5 10l1.5-4 1.5 4h-3z" fill="currentColor" />
      </svg>);

    case "postgres":return (
        <img src="https://img.icons8.com/carbon-copy/100/FFFFFF/postgreesql.png" width={size} height={size} alt="PostgreSQL" style={{ display: 'block' }} />);

    case "docker":return (
        <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAADJ0lEQVR4nO2YW4hNURjH95FrvIgm5K7cyyXK5UXyxAwymRGepCYiNSXNg9sUUZPch0nJuKQhT0hyl5Jb5IHcQrlEjNxNzE/fzP+MdU5n5szZsztzjva/du211re+9X3ru+7teSFChAgRIkSI4EET8LIJ/G+KeEnmMh6EisQC6AZ08dLp+81FCmcNBz4B74BJWakI0Aa45Wz7BowPXJGg5po4Z7rInwM79P7CXC3bFCkX+VqND2hcnm2KXBb5FI27K15+pmwV0ogEZ9/QUkNcOFZ5qQTwBrgEbABGZqoiZ7Q025nLdbZ8AT4641qgoklF0j1nAEq1tNVzAIwCejvjnsAJ0b5yaTNFkcFa+mrCJhTwH91b0a5qdUVUN3oAo4EVcTVkRgLZjLZECcBwAWhnC3vlZ5mE10CRBB8CnJSCTxwak7myoY0x/6J18UdZ6J6yUwHQybGAKeHikWrNmHhT/RJBByDiZRCoD3C7+c/AUKC9sxYrK1AtRfoAR4BeCRhGgJnJeh5gAHAYmNbIuvGZD+wBcpLwygXOSbZNcWum1FGgrTtppor6JbKQFZsKYBdw1nG/36q8WyxT6FkH7JdrmJtEcVe90koFpwn/2FmvAa4rRleLVwmwHbgT505ReQ7FJQMrngvrLsXJxYu9DAHQURdhWNrcTcu04biXIQB2SqaLzY5b+RsKqK4pHnjNLsKvwI3wnKMAt4I4KNXN5qsNrXMS2hxgsgL3KnClJYLH8Z4K/FCsFfhhkC9FPgDLgUJgrp4lwHrgIPDAKZ75QSkgGSaqITQU+2UScaySDGby024DF4ASlnm+i39ZS5mNcG7E+pgqPfvU9y8CJsTk7mCy0zYntRcHxbjQqQW7gxQ6wVmzgGc6yy4wL+gDihxlrBj2DZC3dbp5wHnHVU8B/YM6IwY6rNqJiTKgn+df+HGq3lELRD9b5/nhmRKs5wKOOVmqVnWjVIoOs7pj3wLWzOl9IDDWBFTrUqVvbBe3gQV13xDpBPWCVcoyfnEf2GjJIq3CJwLQWT/PNiv9PtRPgBr9qnkPPAVuyhJrVJ98uWSIECFChAjhpRF/AfsxGYxhUoLgAAAAAElFTkSuQmCC" width={size} height={size} alt="Docker" style={{ display: 'block' }} />);

    case "laravel":return (
        <img src="https://img.icons8.com/ios/50/FFFFFF/laravel.png" width={size} height={size} alt="Laravel" style={{ display: 'block' }} />);

    case "ts":return (
        <svg {...props}>
        <rect x="3" y="3" width="26" height="26" rx="3" stroke="currentColor" strokeWidth="1.6" fill="none" />
        <path d="M10 14h8M14 14v9" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
        <path d="M19 22c.8 1 2 1.5 3.5 1.5 1.7 0 3-.8 3-2.2 0-2.8-6-1.8-6-4.5 0-1.2 1.2-2 2.8-2 1.2 0 2 .4 2.7 1.2" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" fill="none" />
      </svg>);

    default:return null;
  }
};
const NAV = [
{ id: "inicio", label: "Inicio" },
{ id: "sobre", label: "Sobre mí" },
{ id: "habilidades", label: "Habilidades" },
{ id: "proyectos", label: "Proyectos" },
{ id: "experiencia", label: "Experiencia" },
{ id: "educacion", label: "Educación" },
{ id: "contacto", label: "Contacto" }];


const SKILLS = [
{ num: "01", title: "Backend", items: ["Java 21", "Spring Boot", "Spring Security", "Spring Data JPA", "Spring WebFlux", "Flyway", "Laravel 12", "PHP 8.3"] },
{ num: "02", title: "Frontend", items: ["Angular 20", "TypeScript", "JavaScript", "Tailwind CSS", "Electron", "Standalone Components", "Signals"] },
{ num: "03", title: "Datos", items: ["PostgreSQL", "MySQL", "Redis", "Flyway Migrations", "Apache POI", "Reportes en Excel", "Web3j"] },
{ num: "04", title: "Plataforma", items: ["Docker", "Digital Ocean", "Git / GitHub", "Maven", "JWT", "OAuth2 / Keycloak", "Swagger / OpenAPI", "SourceAFIS"] }];


const PROJECTS = [
{
  n: "01",
  cat: "Plataforma SaaS",
  title: "Sistema Revolución Atlética",
  desc: "Plataforma integral de gestión de gimnasios: socios, membresías, inventario por turno, punto de venta, corte de caja y asesorías. Multitenant para administrar múltiples sucursales desde un único panel.",
  stack: ["Java 21", "Spring Boot", "Angular 20", "Electron", "PostgreSQL", "Docker", "SourceAFIS", "JWT"],
  role: "Full Stack · 2024 – Presente",
  link: "#",
  private: true,
  visual: { kind: "badge", label: "REVATL · v1.4", big: "R/A" },
  gallery: [
  "assets/proyectos/ra-0.png",
  "assets/proyectos/ra-1.png",
  "assets/proyectos/ra-2.png",
  "assets/proyectos/ra-3.png",
  "assets/proyectos/ra-4.png",
  "assets/proyectos/ra-5.png",
  "assets/proyectos/ra-6.png"]

},
{
  n: "02",
  cat: "Backend · Transporte",
  title: "Yajalones App",
  desc: "Backend completo para sistema de transporte interurbano Yajalón – Tuxtla Gutiérrez. Cálculo automático de tarifas por tipo de pasajero (Adulto, Niño, INAPAM) y gestión de viajes, paquetería, choferes y turnos.",
  stack: ["Java 21", "Spring Boot", "PostgreSQL", "JWT", "Docker", "Digital Ocean"],
  role: "Backend · 2025",
  link: "#",
  private: true,
  visual: { kind: "badge", label: "API · v1.0", big: "YJL" },
  gallery: [
  "assets/proyectos/yl-1.jpeg",
  "assets/proyectos/yl-2.jpeg"]

},
{
  n: "03",
  cat: "Fintech · Blockchain",
  title: "Green Wallet",
  desc: "API REST de una plataforma de fidelización para PYMES con sistema de puntos y cashback descentralizado en blockchain. Roles de cliente, empresa afiliada y administrador con Keycloak.",
  stack: ["Spring Boot", "Keycloak", "OAuth2", "Web3j", "PostgreSQL", "Swagger"],
  role: "Backend · 2025 — Completado",
  link: "https://github.com/CristianVazquez09/Green-Wallet",
  visual: { kind: "badge", label: "GW · v1.0", big: "GW₿" },
  gallery: [
  "assets/proyectos/gw-1.jpg",
  "assets/proyectos/gw-2.jpg",
  "assets/proyectos/gw-3.jpg",
  "assets/proyectos/gw-4.png"]

},
{
  n: "04",
  cat: "Sector público",
  title: "Gestión de proyectos — CEAS Chiapas",
  desc: "API REST para el seguimiento de proyectos, subproyectos y metas institucionales de una entidad gubernamental. Flujo de aprobación trimestral, indicadores automáticos e historial auditado de presupuestos.",
  stack: ["Spring Boot", "Spring Security", "PostgreSQL", "Flyway", "Problem Details RFC 7807"],
  role: "Backend · 2025 — En desarrollo",
  link: "#",
  private: true,
  visual: { kind: "badge", label: "CEAS · v1.0", big: "CS" },
  gallery: [
  "assets/proyectos/cs-2.jpeg",
  "assets/proyectos/cs-5.jpeg",
  "assets/proyectos/cs-4.jpeg",
  "assets/proyectos/cs-3.jpeg",
  "assets/proyectos/cs-1.jpeg"]

},
{
  n: "05",
  cat: "CRM · Sector financiero",
  title: "CRM Gea Corp",
  desc: "CRM a medida para Gea Corp (geacorp.uk) con dos paneles diferenciados: administrativo para asesores y staff, y portal exclusivo para clientes. Migración del sistema legado desde CodeIgniter 2 a Laravel 12.",
  stack: ["Laravel 12", "Filament 3", "PHP 8.3", "MySQL", "Redis", "OPcache"],
  role: "Full Stack · 2025 — Completado",
  link: "https://geacorp.uk/",
  visual: { kind: "badge", label: "geacorp.uk", big: "GC" },
  gallery: [
  "assets/proyectos/gp-1.png",
  "assets/proyectos/gp-2.png",
  "assets/proyectos/gp-3.png",
  "assets/proyectos/gp-4.png",
  "assets/proyectos/gp-5.png",
  "assets/proyectos/gp-6.png"]

}];


const JOBS = [
{
  year: "2024 – Presente",
  title: "Desarrollador Full Stack — Revolución Atlética",
  role: "Diseño de arquitectura · Backend & desktop app",
  desc: "Diseñé y desarrollé una plataforma para centralizar socios, membresías, inventario, punto de venta, cortes de caja, asesorías y control de acceso biométrico. Implementé backend con Spring Boot, seguridad con JWT, arquitectura multitenant, reportes en Excel y documentación OpenAPI. También desarrollé la aplicación de escritorio con Angular y Electron, desplegada en Digital Ocean con Docker.",
  tags: ["Spring Boot", "Angular", "Electron", "Docker", "PostgreSQL", "JWT"]
},
{
  year: "2025",
  title: "Desarrollador Backend — Yajalones App",
  role: "API REST de transporte interurbano",
  desc: "Desarrollé el backend de un sistema de transporte para gestionar viajes, pasajeros, paquetería, choferes y turnos. Implementé lógica de negocio para el cálculo automático de tarifas por tipo de pasajero, ruta y dirección, con recálculo de totales al agregar o confirmar registros. El sistema integra autenticación JWT, Spring Security y manejo global de excepciones.",
  tags: ["Spring Boot", "Spring Security", "JWT", "Docker", "Digital Ocean"]
},
{
  year: "2025 — En desarrollo",
  title: "Desarrollador Backend — CEAS Chiapas",
  role: "Sistema de gestión de proyectos institucionales",
  desc: "Desarrollo una API REST para el seguimiento de proyectos, subproyectos y metas institucionales de una entidad gubernamental. Implementé autenticación con JWT, control de roles ADMIN/AREA, flujo de aprobación y rechazo de avances trimestrales, recálculo automático de indicadores de cumplimiento e historial auditado de cambios presupuestales.",
  tags: ["Spring Boot", "PostgreSQL", "Flyway", "JWT", "Problem Details"]
},
{
  year: "2025",
  title: "Desarrollador Backend — Green Wallet",
  role: "Fidelización con cashback en blockchain",
  desc: "Desarrollé la API REST de una plataforma de fidelización para PYMES con sistema de puntos y cashback. Implementé autenticación y autorización con Keycloak, roles para clientes, empresas afiliadas y administradores, además de integración con Web3j para registrar transacciones sensibles en blockchain.",
  tags: ["Spring Boot", "Keycloak", "OAuth2", "Web3j", "Swagger"]
},
{
  year: "2025",
  title: "Desarrollador Full Stack — Gea Corp",
  role: "CRM remoto sector financiero",
  desc: "Desarrollé y desplegué un CRM para el sector financiero con panel administrativo, portal de clientes, gestión de estados de cuenta, sistema de tickets, reportes PDF y control de acceso por roles. Migré el sistema desde CodeIgniter 2 a Laravel 12 con Filament 3, integrando Redis y OPcache para optimización.",
  tags: ["Laravel", "Filament", "Redis", "MySQL", "Digital Ocean"]
}];


const CERTS = [
{ n: "01", title: "Desarrollo Backend con Java 17", year: "2024", file: "assets/certificados/certificado-java-backend.pdf" },
{ n: "02", title: "Desarrollo Full Stack con Java", year: "2024", file: "assets/certificados/certificado-java-fullstack.pdf" },
{ n: "03", title: "Spring WebFlux — Programación Reactiva", year: "2024", file: "assets/certificados/certificado-java-webflux.pdf" }];


/* ---------- Nav ---------- */
function Nav({ onMenu, isLight }) {
  const [scrolled, setScrolled] = useState(false);
  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 60);
    addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => removeEventListener('scroll', onScroll);
  }, []);
  return (
    <header className={`nav ${isLight ? 'light' : ''}`} style={scrolled ? {} : {}}>
      <div className="nav-inner">
        <a href="#inicio" className="brand">
          <span>Cristian Vázquez</span>
        </a>
        <nav className="nav-links">
          {NAV.slice(1, 6).map((n) =>
          <a key={n.id} href={`#${n.id}`}>{n.label}</a>
          )}
        </nav>
        <div className="nav-cta">
          <a href="#contacto" className="btn primary">Hablemos<Arrow /></a>
          <button className="menu-btn" aria-label="Menu" onClick={onMenu}><span></span></button>
        </div>
      </div>
    </header>);

}

function Drawer({ open, onClose }) {
  return (
    <div className={`drawer ${open ? 'open' : ''}`} role="dialog" aria-hidden={!open}>
      <div className="drawer-top">
        <div className="brand"><span>Cristian Vázquez</span></div>
        <button className="menu-btn" onClick={onClose} aria-label="Cerrar">
          <svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" fill="none"><path d="M6 6l12 12M6 18L18 6" /></svg>
        </button>
      </div>
      <nav>
        {NAV.map((n, i) =>
        <a key={n.id} href={`#${n.id}`} onClick={onClose}>
            <span>{n.label}</span><span className="mono">0{i + 1}</span>
          </a>
        )}
      </nav>
      <div style={{ marginTop: 18, display: 'flex', gap: 12, paddingTop: 4 }}>
        <a href="assets/CV_Cristian_Vazquez.pdf" download className="btn primary lg" style={{ flex: 1 }}>Descargar CV<FileDown /></a>
      </div>
    </div>);

}

/* ---------- Hero ---------- */
function Hero() {
  return (
    <section className="hero" id="inicio">
      <div className="grain"></div>
      <div className="wrap hero-grid">
        <div>
          <div className="eyebrow reveal"><span className="dot"></span>Disponible para colaborar · CDMX / Remoto</div>
          <h1 className="reveal" data-delay="1" style={{ marginTop: 24 }}>
            Transformo<br />
            <em className="serif" style={{ fontStyle: 'italic', fontWeight: 400, color: 'var(--ink-dim)' }}>necesidades</em> de negocio<br />
            en sistemas <span className="glow">funcionales.</span>
          </h1>
          <p className="lede reveal" data-delay="2">
            Diseño y desarrollo plataformas web orientadas a resolver procesos reales de negocio,
            integrando APIs seguras, lógica backend sólida y despliegues en la nube.
            Mi enfoque está en construir software mantenible, escalable y listo para operar en entornos reales.
          </p>
          <div className="ctas reveal" data-delay="3">
            <a href="#proyectos" className="btn primary lg">Ver proyectos<Arrow /></a>
            <a href="#sobre" className="btn ghost lg">Conóceme <ArrowDown /></a>
          </div>
        </div>

        <div className="hero-right reveal" data-delay="2">
          <div className="portrait parallax" id="heroPortrait">
            <img src="assets/img_portafolio.jpeg" alt="Cristian Vázquez" className="portrait-img" />
            <span className="ph-stamp mono">EN LÍNEA · BACKEND</span>
            <div className="ph-meta mono">
              <span>ID · 0967.145.9575</span>
              <span>CHIAPAS, MX</span>
            </div>
          </div>
        </div>
      </div>

      <div className="wrap">
        <div className="trust reveal" data-delay="4">
          <div className="label">Stack principal<br />en producción</div>
          <div className="logos">
            <span><TechIcon name="java" /><b>Java </b></span>
            <span><TechIcon name="spring" /><b>Spring Boot</b></span>
            <span><TechIcon name="angular" /><b>Angular 20</b></span>
            <span><TechIcon name="postgres" /><b>PostgreSQL</b></span>
            <span><TechIcon name="docker" /><b>Docker</b></span>
            <span><TechIcon name="laravel" /><b>Laravel 12</b></span>
            <span><TechIcon name="ts" /><b>TypeScript</b></span>
          </div>
        </div>
      </div>

      <div className="scroll-prompt">
        <span>SCROLL</span><span className="bar"></span>
      </div>
    </section>);

}

/* ---------- About ---------- */
function About() {
  return (
    <section className="about" id="sobre">
      <div className="wrap about-grid">
        <div>
          <div className="eyebrow reveal"><span className="dot"></span>SOBRE MÍ</div>
          <h2 className="reveal" data-delay="1" style={{ marginTop: 24 }}>
            Construyo <em>backends</em><br />
            que sostienen<br />
            sistemas reales.
          </h2>
        </div>
        <div>
          <p className="reveal">
            Soy <strong style={{ color: 'var(--ink)' }}>Cristian Ivan Vázquez Gómez</strong>, desarrollador de software
            especializado en backend con Java y Spring Boot. Me enfoco en crear sistemas
            <em className="serif" style={{ color: 'var(--ink)' }}> seguros, escalables y bien estructurados</em>,
            pensados para resolver necesidades reales de negocio.
          </p>
          <p className="reveal" data-delay="1">
            He trabajado en proyectos donde no solo importa que el sistema funcione, sino que sea
            mantenible, claro y preparado para crecer. Por eso cuido la arquitectura, la lógica de
            negocio, la seguridad, la documentación y el despliegue en producción.
          </p>
          <p className="reveal" data-delay="2">
            Además del backend, también puedo integrar soluciones full stack cuando el proyecto lo
            necesita, conectando interfaces, APIs, bases de datos y servicios en la nube.
          </p>

          <div className="about-stats">
            <div className="stat reveal" data-delay="2">
              <div className="num"><CountUp to={5} suffix="+" /></div>
              <div className="lab">PROYECTOS REALES</div>
            </div>
            <div className="stat reveal" data-delay="3">
              <div className="num"><CountUp to={21} /></div>
              <div className="lab">SISTEMAS DESPLEGADOS</div>
            </div>
            <div className="stat reveal" data-delay="4">
              <div className="num"><CountUp to={6} /></div>
              <div className="lab">SECTORES DISTINTOS</div>
            </div>
          </div>
        </div>
      </div>
    </section>);

}

/* ---------- Skills ---------- */
function Skills() {
  return (
    <section className="skills" id="habilidades">
      <div className="wrap">
        <div className="eyebrow reveal"><span className="dot"></span>HABILIDADES</div>
        <h2 className="reveal" data-delay="1">El stack con el que <em>construyo</em> sistemas listos para producción.</h2>
        <div className="skills-grid">
          {SKILLS.map((s, i) =>
          <div className="skill-card reveal" data-delay={i + 1} key={s.title}>
              <div className="num">{s.num}.</div>
              <h3>{s.title}</h3>
              <ul>
                {s.items.map((it) => <li key={it}>{it}</li>)}
              </ul>
            </div>
          )}
        </div>
      </div>
    </section>);

}

/* ---------- Projects ---------- */
function ProjectGallery({ images, label }) {
  const [idx, setIdx] = useState(0);
  const [paused, setPaused] = useState(false);
  const n = images.length;
  const go = (i) => setIdx((i + n) % n);
  const next = () => go(idx + 1);
  const prev = () => go(idx - 1);

  // auto-advance
  useEffect(() => {
    if (paused) return undefined;
    const t = setInterval(() => setIdx((i) => (i + 1) % n), 3200);
    return () => clearInterval(t);
  }, [paused, n]);

  return (
    <div className="gallery"
    onMouseEnter={() => setPaused(true)}
    onMouseLeave={() => setPaused(false)}>
      <div className="gallery-meta mono">
        <span>{label}</span>
        <span>{String(idx + 1).padStart(2, '0')} / {String(n).padStart(2, '0')}</span>
      </div>
      <div className="gallery-stage">
        {images.map((src, i) =>
        <img key={src} src={src} alt={`${label} captura ${i + 1}`}
        className={`gallery-img ${i === idx ? 'active' : ''}`}
        draggable="false" />
        )}
        <button className="gallery-nav prev" aria-label="Anterior"
        onClick={(e) => {e.preventDefault();prev();}}>
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M15 18l-6-6 6-6" /></svg>
        </button>
        <button className="gallery-nav next" aria-label="Siguiente"
        onClick={(e) => {e.preventDefault();next();}}>
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M9 6l6 6-6 6" /></svg>
        </button>
      </div>
      <div className="gallery-dots">
        {images.map((_, i) =>
        <button key={i} className={`dot ${i === idx ? 'on' : ''}`}
        aria-label={`Ir a captura ${i + 1}`}
        onClick={(e) => {e.preventDefault();go(i);}} />
        )}
      </div>
    </div>);

}

function ProjectVisual({ v, gallery }) {
  if (gallery && gallery.length) {
    return <ProjectGallery images={gallery} label={v.label} />;
  }
  if (v.kind === "terminal") {
    return (
      <div className="api-term">
        <div className="api-term-bar">
          <div className="api-dots"><span></span><span></span><span></span></div>
          <div className="api-title mono">{v.title}</div>
          <div className="api-tag mono">WIP</div>
        </div>
        <div className="api-body mono">
          <div className="api-req">
            <span className={`api-verb ${v.request.method.toLowerCase()}`}>{v.request.method}</span>
            <span className="api-path">{v.request.path}</span>
          </div>
          <div className="api-status"><span className="api-ok">●</span> {v.status}</div>
          <pre className="api-json">{v.response.map((line, i) => <div key={i} className="api-line">{line}</div>)}</pre>
        </div>
      </div>);

  }
  return (
    <div className="frame">
      <div className="frame-meta">
        <span>{v.label}</span><span>↘ CASE</span>
      </div>
      <div className="visual">
        <div className="big">{v.big}</div>
        <div className="placeholder">[ Captura · drop image ]</div>
      </div>
    </div>);

}

function Projects() {
  return (
    <section className="projects" id="proyectos">
      <div className="wrap">
        <div className="heading">
          <div>
            <div className="eyebrow reveal"><span className="dot"></span>PROYECTOS</div>
            <h2 className="reveal" data-delay="1" style={{ marginTop: 24 }}>
              Casos <em>reales</em> en<br />
              producción.
            </h2>
          </div>
          <p className="reveal" data-delay="2" style={{ color: 'var(--ink-dim)', maxWidth: '40ch', fontSize: 16, lineHeight: 1.5 }}>Cinco sistemas desplegados para clientes en transporte, fitness, sector público, fintech y servicios financieros. Cada uno se entregó con documentación OpenAPI completa .


          </p>
        </div>

        <div>
          {PROJECTS.map((p, i) =>
          <div className={`proj reveal ${i % 2 ? 'reverse' : ''}`} key={p.n}>
              <div className="proj-text">
                <div className="top">
                  <span>{p.n}.</span>
                  <span>{p.cat}</span>
                </div>
                <h3>{p.title}</h3>
                <p>{p.desc}</p>
                <div className="stack">
                  {p.stack.map((s) => <span key={s}>{s}</span>)}
                </div>
                <div className="bottom">
                  <div style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 11, color: '#6a6a66', letterSpacing: '.04em', textTransform: 'uppercase' }}>
                    {p.role}
                  </div>
                  {!p.private &&
                <a className="link" href={p.link} target={p.link.startsWith('http') ? '_blank' : undefined} rel="noreferrer">
                    Ver caso <Arrow />
                  </a>}
                </div>
              </div>
              <div className="proj-image">
                <ProjectVisual v={p.visual} gallery={p.gallery} />
              </div>
            </div>
          )}
        </div>
      </div>
    </section>);

}

/* ---------- Experience ---------- */
function Experience() {
  return (
    <section className="experience" id="experiencia">
      <div className="wrap">
        <div className="eyebrow reveal"><span className="dot"></span>EXPERIENCIA</div>
        <h2 className="reveal" data-delay="1" style={{ marginTop: 24 }}>
          Roles, responsabilidades<br />
          y <em>resultados</em>.
        </h2>
        <div className="timeline">
          {JOBS.map((j, i) =>
          <a className="job reveal" key={i} href="#proyectos">
              <div className="year">{j.year}</div>
              <div>
                <h3>{j.title}<span className="role">{j.role}</span></h3>
                <div className="desc">{j.desc}</div>
                <div className="tags">
                  {j.tags.map((t) => <span key={t}>{t}</span>)}
                </div>
              </div>
              <div className="arrow"><Arrow size={18} /></div>
            </a>
          )}
        </div>
      </div>
    </section>);

}

/* ---------- Education + Certs ---------- */
function Edu() {
  return (
    <section className="edu" id="educacion">
      <div className="wrap edu-grid">
        <div className="edu-block">
          <div className="eyebrow reveal"><span className="dot"></span>EDUCACIÓN</div>
          <h2 className="reveal" data-delay="1">Base formal y aprendizaje continuo.</h2>
          <div className="edu-card reveal" data-delay="2">
            <div className="school">Instituto Tecnológico de Tuxtla Gutiérrez</div>
            <h3>Ingeniería en Sistemas Computacionales</h3>
            <div className="meta">8° Semestre (cursando) · 2021 – Presente · Tuxtla Gutiérrez, Chiapas</div>
          </div>
        </div>
        <div className="edu-block">
          <div className="eyebrow reveal"><span className="dot"></span>CERTIFICACIONES</div>
          <h2 className="reveal" data-delay="1">Especialización<br />en backend Java.</h2>
          <div className="cert-list">
            {CERTS.map((c, i) =>
            <div className="cert reveal" key={c.n} data-delay={i + 1}>
                <div className="n">{c.n}.</div>
                <h4>{c.title}</h4>
                <div className="y">{c.year}</div>
                <a className="cert-link" href={c.file} target="_blank" rel="noreferrer" aria-label={`Ver certificado: ${c.title}`}>
                  Ver <Arrow />
                </a>
              </div>
            )}
          </div>
        </div>
      </div>
    </section>);

}

/* ---------- CV download CTA ---------- */
function CVCta() {
  return (
    <section className="cv-cta" id="cv">
      <div className="cv-cta-inner">
        <div>
          <div className="eyebrow reveal"><span className="dot"></span>CURRÍCULUM</div>
          <h2 className="reveal" data-delay="1" style={{ marginTop: 24 }}>
            Conoce mi <span className="glow">recorrido profesional</span> completo.
          </h2>
          <p className="reveal" data-delay="2">Un resumen claro de mi experiencia construyendo sistemas backend, soluciones full stack, APIs seguras y plataformas listas para producción.


          </p>
          <div className="ctas reveal" data-delay="3" style={{ marginTop: 32, display: 'flex', gap: 12, flexWrap: 'wrap' }}>
            <a href="assets/CV_Cristian_Vazquez.pdf" download className="btn primary lg">
              Descargar CV (PDF)<FileDown />
            </a>
            <a href="assets/CV_Cristian_Vazquez.pdf" target="_blank" rel="noreferrer" className="btn ghost lg">
              Abrir en pestaña<Arrow />
            </a>
          </div>
        </div>
        <div className="cv-doc reveal" data-delay="2">
          <div className="sheet">
            <span className="badge">PDF · 2 pp</span>
            <div className="line"></div>
            <div className="line s"></div>
            <div className="heading"></div>
            <div className="block">
              <div className="row w85"></div><div className="row w70"></div><div className="row"></div><div className="row w55"></div>
            </div>
            <div className="heading"></div>
            <div className="block">
              <div className="row w70"></div><div className="row"></div><div className="row w85"></div>
            </div>
            <div className="heading"></div>
            <div className="block">
              <div className="row w85"></div><div className="row w55"></div>
            </div>
          </div>
        </div>
      </div>
    </section>);

}

/* ---------- Contact ---------- */
function Contact() {
  const items = [
  { lab: "Correo", value: "cristian09tr@gmail.com", href: "mailto:cristian09tr@gmail.com", ic: <Mail /> },
  { lab: "LinkedIn", value: "linkedin.com/in/cristian-ivan-vg-09t", href: "https://www.linkedin.com/in/cristian-ivan-vg-09t", ic: <LinkedIn /> },
  { lab: "GitHub", value: "github.com/CristianVazquez09", href: "https://github.com/CristianVazquez09", ic: <GitHub /> },
  { lab: "WhatsApp", value: "+52 967 145 9575", href: "https://wa.me/529671459575", ic: <WhatsApp /> }];

  return (
    <section className="contact" id="contacto">
      <div className="wrap">
        <div className="eyebrow reveal"><span className="dot"></span>CONTACTO</div>
        <h2 className="reveal" data-delay="1" style={{ marginTop: 24 }}>
          ¿Tienes un proyecto en mente?<br />
          <em>Conversemos.</em>
        </h2>
        <p className="lede reveal" data-delay="2">
          Estoy abierto a colaboraciones, proyectos freelance y oportunidades de tiempo completo en
          equipos que valoren la ingeniería de software con buenas prácticas. Respondo en menos de 24 horas.
        </p>

        <div className="contact-grid">
          {items.map((it, i) =>
          <a key={it.lab} href={it.href} className="contact-card reveal" data-delay={i + 1}
          target={it.href.startsWith('http') ? '_blank' : undefined} rel="noreferrer">
              <div className="ic">{it.ic}</div>
              <div>
                <div className="lab">{it.lab}</div>
                <h3>{it.value}</h3>
              </div>
              <div className="go">Ir <Arrow /></div>
            </a>
          )}
        </div>
      </div>
    </section>);

}

/* ---------- Footer ---------- */
function Footer() {
  const year = new Date().getFullYear();
  return (
    <footer>
      <div className="wrap">
        <div className="foot-top">
          <div>
            <div className="foot-brand">Cristian.</div>
            <h3 style={{ marginTop: 24 }}>Ingeniero de software enfocado en construir sistemas funcionales.</h3>
            <a href="https://wa.me/529671459575" target="_blank" rel="noreferrer" className="btn dark lg" style={{ marginTop: 24 }}>Iniciar conversación<Arrow /></a>
          </div>
          <div className="foot-col">
            <h4>Secciones</h4>
            <ul>
              {NAV.map((n) => <li key={n.id}><a href={`#${n.id}`}>{n.label}</a></li>)}
            </ul>
          </div>
          <div className="foot-col">
            <h4>Conecta</h4>
            <ul>
              <li><a href="mailto:cristian09tr@gmail.com">cristian09tr@gmail.com</a></li>
              <li><a href="https://wa.me/529671459575">+52 967 145 9575</a></li>
              <li><a href="https://github.com/CristianVazquez09">github.com/CristianVazquez09</a></li>
              <li><a href="https://www.linkedin.com/in/cristian-ivan-vg-09t" target="_blank" rel="noreferrer">LinkedIn</a></li>
            </ul>
          </div>
        </div>
        <div className="foot-bot">
          <div className="mono">© {year} · CRISTIAN IVAN VÁZQUEZ GÓMEZ · CHIAPAS, MX</div>
          <div className="mono">DISEÑADO Y DESARROLLADO POR EL AUTOR</div>
        </div>
      </div>
    </footer>);

}

/* ---------- Tweaks ---------- */
function Tweaks({ t, setTweak }) {
  if (!window.TweaksPanel) return null;
  const { TweaksPanel, TweakSection, TweakRadio, TweakToggle } = window;
  return (
    <TweaksPanel title="Tweaks">
      <TweakSection label="Apariencia">
        <TweakRadio label="Acento"
        value={t.accent}
        options={['amber', 'emerald', 'violet', 'rose']}
        onChange={(v) => setTweak('accent', v)} />
        <TweakToggle label="Spotlight cursor"
        value={t.showSpotlight}
        onChange={(v) => setTweak('showSpotlight', v)} />
      </TweakSection>
    </TweaksPanel>);

}

/* ---------- App ---------- */
function App() {
  const [tweaks, setTweaks] = useState(TWEAK_DEFAULTS);
  const setTweak = (k, v) => {
    setTweaks((prev) => {
      const next = typeof k === 'object' ? { ...prev, ...k } : { ...prev, [k]: v };
      try {window.parent.postMessage({ type: '__edit_mode_set_keys', edits: typeof k === 'object' ? k : { [k]: v } }, '*');} catch {}
      return next;
    });
  };
  const [drawer, setDrawer] = useState(false);
  const [navLight, setNavLight] = useState(false);

  // accent variables
  useEffect(() => {
    const a = ACCENT_PRESETS[tweaks.accent] || ACCENT_PRESETS.amber;
    document.documentElement.style.setProperty('--accent', a.a);
    document.documentElement.style.setProperty('--accent-2', a.b);
  }, [tweaks.accent]);

  // spotlight toggle
  useEffect(() => {
    const sp = document.getElementById('spotlight');
    if (sp) sp.style.display = tweaks.showSpotlight ? '' : 'none';
  }, [tweaks.showSpotlight]);

  useReveal();

  // detect nav over light sections
  useEffect(() => {
    const lightSections = document.querySelectorAll('.skills, .edu, .contact, footer');
    const onScroll = () => {
      const navH = 70;
      let isLight = false;
      lightSections.forEach((s) => {
        const r = s.getBoundingClientRect();
        if (r.top <= navH && r.bottom >= navH) isLight = true;
      });
      setNavLight(isLight);
    };
    addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => removeEventListener('scroll', onScroll);
  }, []);

  // tweaks panel handles its own host registration; no app-level listener needed

  // hero portrait parallax
  useEffect(() => {
    const el = document.getElementById('heroPortrait');
    if (!el) return;
    const onScroll = () => {
      const y = window.scrollY;
      el.style.transform = `translateY(${y * -0.04}px) scale(${1 + Math.min(.04, y * 0.0001)})`;
    };
    addEventListener('scroll', onScroll, { passive: true });
    return () => removeEventListener('scroll', onScroll);
  }, []);

  return (
    <>
      <Nav isLight={navLight} onMenu={() => setDrawer(true)} />
      <Drawer open={drawer} onClose={() => setDrawer(false)} />
      <Hero />
      <About />
      <Skills />
      <Projects />
      <Experience />
      <Edu />
      <CVCta />
      <Contact />
      <Footer />
      <Tweaks t={tweaks} setTweak={setTweak} />
    </>);

}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);