document.addEventListener("DOMContentLoaded", () => { /* ── Header scroll ── */ const header = document.querySelector(".pn-header"); const scrollTop = document.querySelector(".pn-scroll-top"); window.addEventListener("scroll", () => { const y = window.scrollY; if (header) header.classList.toggle("is-scrolled", y > 60); if (scrollTop) scrollTop.classList.toggle("is-visible", y > 400); }, { passive: true }); /* ── Mobile nav ── */ const hamburger = document.querySelector(".pn-hamburger"); const nav = document.querySelector(".pn-nav"); if (hamburger && nav) { hamburger.addEventListener("click", () => { const open = nav.classList.toggle("is-open"); hamburger.setAttribute("aria-expanded", String(open)); const s = hamburger.querySelectorAll("span"); if (s.length === 3) { s[0].style.transform = open ? "rotate(45deg) translate(5px, 5px)" : ""; s[1].style.opacity = open ? "0" : ""; s[2].style.transform = open ? "rotate(-45deg) translate(5px, -5px)" : ""; } }); document.addEventListener("click", e => { if (!nav.contains(e.target) && !hamburger.contains(e.target)) { nav.classList.remove("is-open"); hamburger.setAttribute("aria-expanded", "false"); hamburger.querySelectorAll("span").forEach(s => { s.style.transform = ""; s.style.opacity = ""; }); } }); } /* ── Nav active on scroll ── */ const navLinks = Array.from(document.querySelectorAll(".pn-nav__list a[href^='#']")); const sections = Array.from(document.querySelectorAll("section[id]")); if (navLinks.length && sections.length) { const obs = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { const id = "#" + e.target.id; navLinks.forEach(l => l.classList.toggle("is-active", l.getAttribute("href") === id)); } }); }, { rootMargin: "-20% 0px -70% 0px" }); sections.forEach(s => obs.observe(s)); } navLinks.forEach(l => l.addEventListener("click", () => { if (nav) nav.classList.remove("is-open"); })); /* ── Animated counters ── */ const counters = Array.from(document.querySelectorAll(".pn-counter__val")); const counterObs = new IntersectionObserver(entries => { entries.forEach(entry => { if (!entry.isIntersecting) return; counterObs.unobserve(entry.target); const el = entry.target; const end = parseInt(el.dataset.count, 10); const duration = 1800; const step = Math.ceil(end / (duration / 16)); let current = 0; const tick = () => { current = Math.min(current + step, end); el.textContent = current.toLocaleString("es-EC"); if (current < end) requestAnimationFrame(tick); }; requestAnimationFrame(tick); }); }, { threshold: 0.3 }); counters.forEach(c => counterObs.observe(c)); /* ── Skill bars animation ── */ const skillBars = Array.from(document.querySelectorAll(".pn-skill__fill")); const skillObs = new IntersectionObserver(entries => { entries.forEach(entry => { if (!entry.isIntersecting) return; skillObs.unobserve(entry.target); const bar = entry.target; const pct = bar.dataset.pct; setTimeout(() => { bar.style.width = pct + "%"; }, 100); }); }, { threshold: 0.2 }); skillBars.forEach(b => skillObs.observe(b)); /* ── Print ── */ document.querySelectorAll("[data-persona-print]").forEach(b => b.addEventListener("click", () => window.print())); /* ── Scroll top ── */ if (scrollTop) { scrollTop.addEventListener("click", e => { e.preventDefault(); window.scrollTo({ top: 0, behavior: "smooth" }); }); } });