/* global React */
const { useEffect, useRef, useState } = React;

/* ---------- reveal-on-scroll hook ---------- */
function useReveal() {
  useEffect(() => {
    const els = Array.from(document.querySelectorAll(".reveal"));

    const check = () => {
      const h = window.innerHeight;
      for (const el of els) {
        if (el.classList.contains("in")) continue;
        const r = el.getBoundingClientRect();
        if (r.top < h * 0.92 && r.bottom > 0) {
          el.classList.add("in");
        }
      }
    };

    check();
    // Re-check on next frames in case layout shifts after fonts/images load
    requestAnimationFrame(check);
    setTimeout(check, 100);
    setTimeout(check, 400);

    window.addEventListener("scroll", check, { passive: true });
    window.addEventListener("resize", check);
    return () => {
      window.removeEventListener("scroll", check);
      window.removeEventListener("resize", check);
    };
  }, []);
}

/* ---------- nav ---------- */
function Nav({ resumeUrl, linkedinUrl }) {
  return (
    <nav className="nav">
      <div className="nav-inner">
        <a className="nav-mark" href="#top">
          <span className="dot" />
          Lee Xin Yang
        </a>
        <div className="nav-links">
          <a href="#work">Work</a>
          <a href="#impact">Impact</a>
          <a href="#aimed">AI MED</a>
          <a href="#education">Education</a>
        </div>
        <div className="nav-cta-group">
          <a
            className="nav-ghost"
            href={linkedinUrl}
            target="_blank"
            rel="noreferrer"
            aria-label="LinkedIn"
          >
            <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
              <path d="M20.45 20.45h-3.55v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.14 1.45-2.14 2.94v5.67H9.36V9h3.41v1.56h.05c.47-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.46v6.28zM5.34 7.43a2.06 2.06 0 1 1 0-4.12 2.06 2.06 0 0 1 0 4.12zm1.78 13.02H3.56V9h3.56v11.45zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.56C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.72V1.72C24 .77 23.2 0 22.22 0z" />
            </svg>
            <span>LinkedIn</span>
          </a>
          <a
            className="nav-cta"
            href={resumeUrl}
            target="_blank"
            rel="noreferrer"
            download
          >
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <path d="M12 3v13M6 13l6 6 6-6M5 21h14" />
            </svg>
            <span>Resume</span>
          </a>
        </div>
      </div>
    </nav>
  );
}

/* ---------- hero ---------- */
function Hero() {
  return (
    <section className="hero" id="top" data-screen-label="01 Hero">
      <div className="wrap">
        <div className="hero-grid">
          <div className="hero-text">
            <div className="hero-eyebrow reveal">
              <span className="live-dot" />
              Available · AI Engineer · Johor Bahru, MY
            </div>
            <h1 className="hero-title reveal" data-delay="1">
              Lee&nbsp;Xin&nbsp;Yang.<br />
              <span className="accent">AI Engineer.</span>
            </h1>
            <p className="hero-sub reveal" data-delay="2">
              I architect production RAG systems, multi-agent workflows, and the
              inference infrastructure they run on.
              <span className="muted"> Currently shipping LLM products at Etiqa Insurance.</span>
            </p>
          </div>
          <div className="hero-portrait reveal" data-delay="2">
            <div className="portrait-frame">
              <img src="assets/portrait.png" alt="Lee Xin Yang" loading="eager" />
            </div>
            <div className="portrait-caption">
              <span className="dot" />
              <span>Johor Bahru, MY</span>
            </div>
          </div>
        </div>
        <div className="hero-meta reveal" data-delay="3">
          <div>
            Based in
            <b>Johor Bahru, Malaysia</b>
          </div>
          <div>
            Focus
            <b>LLMs · RAG · AIOps</b>
          </div>
          <div>
            Education
            <b>M.Data Science · 4.0 CGPA</b>
          </div>
          <div>
            Contact
            <b>+60 17 760 4363</b>
          </div>
        </div>
      </div>
      <div className="scroll-cue">
        <span>Scroll</span>
        <span className="line" />
      </div>
    </section>
  );
}

/* ---------- pitch ---------- */
function Pitch() {
  return (
    <section className="pitch">
      <div className="wrap">
        <p className="pitch-text reveal">
          Two years ago I was building bridges. Today I build the systems that
          let language models reason over a company's knowledge —
          <span className="dim"> reliably, at scale, in production.</span>
        </p>
      </div>
    </section>
  );
}

/* ---------- metrics ---------- */
function Impact() {
  const items = [
    {
      n: "1.7",
      unit: "×",
      label: "vLLM inference throughput, via 8-bit KV cache + prefix caching.",
      tag: "Throughput",
    },
    {
      n: "80",
      unit: "%",
      label: "Less manual knowledge-base update effort via an autonomous learning loop.",
      tag: "Automation",
    },
    {
      n: "98",
      unit: "%",
      label: "Of synthetic Q&A pairs validated as contextually correct with G-Eval.",
      tag: "Quality",
    },
    {
      n: "2k",
      unit: "+",
      label: "Synthetic insurance Q&A pairs generated through a custom RAG pipeline.",
      tag: "Scale",
    },
  ];
  return (
    <section id="impact" data-screen-label="02 Impact">
      <div className="wrap">
        <p className="eyebrow reveal">Impact in production</p>
        <h2 className="section-title reveal" data-delay="1">
          Numbers that<br />moved the needle.
        </h2>
        <p className="section-lede reveal" data-delay="2">
          A selection of measurable outcomes shipped during my current role
          architecting LLM systems at Etiqa.
        </p>
        <div className="metrics reveal" data-delay="3">
          {items.map((m, i) => (
            <div className="metric" key={i}>
              <div className="metric-num">
                {m.n}
                <span className="unit">{m.unit}</span>
              </div>
              <div className="metric-label">{m.label}</div>
              <div className="metric-tag">{m.tag}</div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

window.Nav = Nav;
window.Hero = Hero;
window.Pitch = Pitch;
window.Impact = Impact;
window.useReveal = useReveal;


/* ===== additional sections ===== */

/* global React */

/* ---------- experience ---------- */
const ROLES = [
  {
    id: "etiqa",
    when: "Aug 2024 — Present",
    where: "Johor Bahru, MY",
    co: "Maybank Ageas — Etiqa Insurance",
    title: "Python / AI Engineer",
    groups: [
      {
        head: "LLM & Generative AI",
        items: [
          <>
            Architected an end-to-end <b>RAG chatbot platform</b> with{" "}
            <span className="chip">LangGraph</span>{" "}
            <span className="chip">LlamaIndex</span>{" "}
            <span className="chip">LangChain</span> and{" "}
            <span className="chip">PostgreSQL · PGVector</span>, deploying
            multiple specialized agents (query-rewrite, retrieval, policy product)
            to automate customer enquiries.
          </>,
          <>
            Configured the <b>LangGraph Checkpointer</b> with Redis to persist
            conversational state across multi-agent workflows and support
            concurrent users with session reliability.
          </>,
          <>
            Engineered an <b>Agentic Learning Loop</b> that autonomously
            classifies unresolved conversations, generates golden datasets,
            and ingests new knowledge — reducing manual KB update effort by 80%.
          </>,
          <>
            Built a RAG pipeline producing <b>2,000+ synthetic Q&A pairs</b> on
            insurance products and validated correctness with G-Eval (98%
            contextually correct).
          </>,
          <>
            Fine-tuned an LLM with <span className="chip">Unsloth QLoRA</span>{" "}
            (78% accuracy) and applied <span className="chip">AWQ</span>{" "}
            quantization via LLM Compressor to optimize inference speed and
            GPU memory usage.
          </>,
        ],
      },
      {
        head: "AI Infrastructure & Deployment",
        items: [
          <>
            Improved <span className="chip">vLLM</span> inference throughput by{" "}
            <b>~1.7× tokens/sec</b> via 8-bit quantized KV cache and prefix
            caching, reducing GPU memory usage by 40%.
          </>,
          <>
            Reduced container build time by <b>1.9×</b> by migrating package
            installation from <span className="chip">pip</span> to{" "}
            <span className="chip">uv</span>.
          </>,
          <>
            Containerized <span className="chip">Ollama</span>{" "}
            <span className="chip">vLLM</span>{" "}
            <span className="chip">NGINX</span> with Podman/Docker on RHEL
            servers for reliable MLOps from UAT through production.
          </>,
          <>
            Configured NGINX reverse proxy with OpenSSL certificates to securely
            serve the AI chatbot.
          </>,
          <>
            Set up <span className="chip">CUDA</span>{" "}
            <span className="chip">cuDNN</span> and the NVIDIA Container Toolkit
            on RHEL servers for GPU-accelerated training and inference.
          </>,
        ],
      },
    ],
  },
  {
    id: "manja",
    when: "Jan 2024 — Aug 2024",
    where: "Johor Bahru, MY",
    co: "Manja Technologies",
    title: "Machine Learning Engineer",
    groups: [
      {
        head: null,
        items: [
          <>
            Implemented a <b>Retrieval Augmented Generation</b> framework using
            closed- and open-source LLMs integrated with{" "}
            <span className="chip">Elastic Search</span> as the vector store,
            speeding customer-service information retrieval.
          </>,
          <>
            Integrated a <span className="chip">LangChain SQL Agent</span> into
            the RAG model for question-answering over a highly structured bus
            fares SQL dataset.
          </>,
          <>
            Deployed the model on AWS EC2 via Docker for seamless POC
            demonstrations and efficient testing environments.
          </>,
        ],
      },
    ],
  },
];

function Experience() {
  return (
    <section id="work" data-screen-label="03 Experience">
      <div className="wrap">
        <p className="eyebrow reveal">Experience</p>
        <h2 className="section-title reveal" data-delay="1">
          Two roles. One<br />through-line.
        </h2>
        <p className="section-lede reveal" data-delay="2">
          From research RAG prototypes to live, multi-agent platforms behind a
          reverse proxy — taking LLMs the full distance from notebook to
          production.
        </p>
        <div style={{ marginTop: 80 }}>
          {ROLES.map((r) => (
            <article className="role reveal" key={r.id}>
              <div className="role-side">
                <span className="when">{r.when}</span>
                <span className="where">{r.where}</span>
              </div>
              <div>
                <h3 className="role-co">{r.co}</h3>
                <p className="role-title">{r.title}</p>
                {r.groups.map((g, gi) => (
                  <div className="role-group" key={gi}>
                    {g.head && <p className="role-group-h">{g.head}</p>}
                    <ul className="role-list">
                      {g.items.map((item, i) => (
                        <li key={i}>{item}</li>
                      ))}
                    </ul>
                  </div>
                ))}
              </div>
            </article>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---------- AI MED spotlight ---------- */
function Spotlight() {
  return (
    <section id="aimed" data-screen-label="04 AI MED">
      <div className="wrap">
        <p className="eyebrow reveal">Ongoing project</p>
        <h2 className="section-title reveal" data-delay="1">
          The side<br />that ships.
        </h2>
        <p className="section-lede reveal" data-delay="2">
          AI MED is a medical RAG Q&A platform bridging the knowledge gap for
          Malaysia's medical students. Built, deployed, and live in production
          today.
        </p>
        <div className="spotlight reveal" data-delay="3">
          <div className="spotlight-grid">
            <div>
              <p className="spot-eyebrow">
                <span className="live" />
                Live · 2025 — Present
              </p>
              <h3 className="spot-title">AI&nbsp;MED.</h3>
              <p className="spot-lede">
                A LangChain Deep Agent that orchestrates FAISS retrieval,
                medical-abbreviation expansion, and live PubMed search to
                produce evidence-grounded answers with cited sources.
              </p>
              <a className="spot-link" href="https://medmyacademy.com" target="_blank" rel="noreferrer">
                Visit medmyacademy.com <span className="arr">→</span>
              </a>
            </div>
            <div className="spot-stack">
              <div className="spot-row">
                <span className="k">Agent</span>
                <span>
                  LangChain Deep Agent · multi-step tool calling · cited sources
                </span>
              </div>
              <div className="spot-row">
                <span className="k">Retrieval</span>
                <span>
                  LlamaParse · Jina AI embeddings · FAISS · PubMed E-utilities
                </span>
              </div>
              <div className="spot-row">
                <span className="k">Backend</span>
                <span>FastAPI · SSE streaming · Ollama · GPT-4.5 via CLIProxyAPI</span>
              </div>
              <div className="spot-row">
                <span className="k">Frontend</span>
                <span>Next.js · deployed on Vercel</span>
              </div>
              <div className="spot-row">
                <span className="k">Infra</span>
                <span>Docker on AWS EC2 · Cloudflare Tunnel</span>
              </div>
              <div className="spot-row">
                <span className="k">Mission</span>
                <span>
                  Digitalize the frontline clinical workflow for Malaysian
                  medical students.
                </span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ---------- skills ---------- */
const SKILLS = [
  {
    head: "LLM & RAG",
    items: [
      ["LangGraph", "Agents"],
      ["LangChain", "RAG"],
      ["LlamaIndex", "Retrieval"],
      ["FAISS / PGVector", "Vector DB"],
      ["Unsloth · QLoRA", "Fine-tune"],
      ["AWQ · LLM Compressor", "Quantize"],
      ["G-Eval", "Eval"],
    ],
  },
  {
    head: "AI Infra · AIOps",
    items: [
      ["vLLM · Ollama", "Inference"],
      ["NGINX · OpenSSL", "Serving"],
      ["Podman · Docker", "Containers"],
      ["RHEL Linux", "OS"],
      ["CUDA · cuDNN", "GPU"],
      ["AWS EC2", "Cloud"],
      ["uv · pip", "Packaging"],
    ],
  },
  {
    head: "Engineering",
    items: [
      ["Python", "Primary"],
      ["FastAPI · SSE", "API"],
      ["PostgreSQL · Redis", "Data"],
      ["Next.js", "Frontend"],
      ["Elastic Search", "Index"],
      ["Cloudflare Tunnel", "Edge"],
      ["Git · CI/CD", "Workflow"],
    ],
  },
];

function Skills() {
  return (
    <section id="skills" data-screen-label="05 Skills">
      <div className="wrap">
        <p className="eyebrow reveal">Toolkit</p>
        <h2 className="section-title reveal" data-delay="1">
          The stack I<br />reach for.
        </h2>
        <p className="section-lede reveal" data-delay="2">
          Tools I've shipped with in the last 18 months, grouped by where they
          sit in the system.
        </p>
        <div className="skills reveal" data-delay="3">
          {SKILLS.map((col, i) => (
            <div className="skill-col" key={i}>
              <h4>{col.head}</h4>
              <ul>
                {col.items.map(([name, kind], j) => (
                  <li key={j}>
                    <span>{name}</span>
                    <span className="lvl">{kind}</span>
                  </li>
                ))}
              </ul>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

/* ---------- education ---------- */
function Education() {
  return (
    <section id="education" data-screen-label="06 Education">
      <div className="wrap">
        <p className="eyebrow reveal">Education</p>
        <h2 className="section-title reveal" data-delay="1">
          From civil<br />to silicon.
        </h2>
        <p className="section-lede reveal" data-delay="2">
          A pivot from civil engineering to data science — distinction-grade and
          intentional.
        </p>
        <div className="edu reveal" data-delay="3">
          <div className="edu-card">
            <span className="when">Mar 2022 — Feb 2024</span>
            <h3 className="deg">Master of Data Science</h3>
            <div className="school-row">
              <img className="um-logo" src="assets/um.png" alt="University of Malaya" />
              <p className="school">University of Malaya (UM)</p>
            </div>
            <div className="gpa">
              <span>CGPA</span>
              <span>4.00 / 4.00 · Distinction</span>
            </div>
          </div>
          <div className="edu-card">
            <span className="when">Sep 2016 — Sep 2020</span>
            <h3 className="deg">B.Eng (Hons) Civil Engineering</h3>
            <div className="school-row">
              <img className="edu-logo" src="assets/utp.png" alt="Universiti Teknologi PETRONAS" />
              <p className="school">Universiti Teknologi PETRONAS (UTP)</p>
            </div>
            <div className="gpa">
              <span>CGPA</span>
              <span>3.47 / 4.00 · Second Class Upper</span>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ---------- contact ---------- */
function Contact({ resumeUrl, linkedinUrl }) {
  return (
    <section className="contact" id="contact" data-screen-label="07 Contact">
      <div className="wrap">
        <p className="eyebrow reveal" style={{ justifyContent: "center" }}>Get in touch</p>
        <h2 className="reveal" data-delay="1">
          Let's build<br />
          <a className="mail" href="mailto:leexinyang@gmail.com">something good.</a>
        </h2>
        <p className="contact-sub reveal" data-delay="2">
          Open to roles in applied AI, LLM engineering, and inference
          infrastructure. Remote-friendly across APAC.
        </p>
        <div className="contact-links reveal" data-delay="3">
          <a href="mailto:leexinyang@gmail.com">leexinyang@gmail.com</a>
          <a href="tel:+60177604363">+60 17 760 4363</a>
          <a href={linkedinUrl} target="_blank" rel="noreferrer">LinkedIn ↗</a>
          <a href={resumeUrl} target="_blank" rel="noreferrer" download>Resume (PDF) ↓</a>
          <a href="https://medmyacademy.com" target="_blank" rel="noreferrer">medmyacademy.com ↗</a>
        </div>
      </div>
    </section>
  );
}

function Footer() {
  return (
    <footer>
      <div className="wrap">
        <span>© 2026 Lee Xin Yang</span>
        <span>Designed &amp; engineered in Johor Bahru</span>
      </div>
    </footer>
  );
}

window.Experience = Experience;
window.Spotlight = Spotlight;
window.Skills = Skills;
window.Education = Education;
window.Contact = Contact;
window.Footer = Footer;
