1document.addEventListener("DOMContentLoaded", () => { 2 3 /* ========================= 4 ELEMENTOS BASE 5 ========================= */ 6 7 const header = document.getElementById("site-header"); 8 const main = document.querySelector("main"); 9 const body = document.body; 10 11 const collapseEl = document.querySelector("#mainNavbar"); 12 const toggler = document.querySelector(".navbar-toggler"); 13 const navLinks = document.querySelectorAll(".nav-link"); 14 const hashLinks = document.querySelectorAll(".navbar-nav a[href^='/#']"); 15 16 if (!header || !main || !collapseEl || !toggler) return; 17 18 /* ========================= 19 TOOLBAR + HEADER OFFSET 20 ========================= */ 21 22 function getToolbarHeight() { 23 const toolbar = document.querySelector("#toolbar-bar"); 24 const tray = document.querySelector(".toolbar-tray.is-active"); 25 26 return (toolbar?.offsetHeight || 0) + (tray?.offsetHeight || 0); 27 } 28 29 function getHeaderOffset() { 30 return getToolbarHeight() + header.offsetHeight; 31 } 32 33 function adjustLayout() { 34 const offset = getHeaderOffset(); 35 36 header.style.top = getToolbarHeight() + "px"; 37 main.style.paddingTop = offset + "px"; 38 } 39 40 adjustLayout(); 41 window.addEventListener("resize", adjustLayout); 42 43 /* ========================= 44 SCROLL A HASH CON OFFSET 45 ========================= */ 46 47 function normalizeHash(hash) { 48 const clean = hash.replace("#", ""); 49 50 if (/^\d/.test(clean)) { 51 return { 52 legacy: clean, 53 normalized: "y" + clean 54 }; 55 } 56 57 return { 58 legacy: clean, 59 normalized: clean 60 }; 61 } 62 63 function scrollToHash(hash, smooth = true) { 64 if (!hash) return; 65 66 const { legacy, normalized } = normalizeHash(hash); 67 68 const target = 69 document.getElementById(normalized) || 70 document.getElementById(legacy); 71 72 if (!target) return; 73 74 requestAnimationFrame(() => { 75 const offset = getHeaderOffset(); 76 const y = target.getBoundingClientRect().top + window.scrollY - offset; 77 78 window.scrollTo({ 79 top: y, 80 behavior: smooth ? "smooth" : "auto" 81 }); 82 83 if (!target.hasAttribute("tabindex")) { 84 target.setAttribute("tabindex", "-1"); 85 } 86 87 target.focus({ preventScroll: true }); 88 }); 89 } 90 91 /* ========================= 92 ESTADO ACTIVO POR HASH 93 ========================= */ 94 95 function updateActiveByHash() { 96 const currentHash = window.location.hash; 97 98 hashLinks.forEach(link => { 99 const linkHash = link.getAttribute("href").replace("/", ""); 100 101 const isActive = linkHash === currentHash; 102 103 link.classList.toggle("is-active", isActive); 104 link.classList.toggle("active", isActive); 105 106 if (isActive) { 107 link.setAttribute("aria-current", "page"); 108 } else { 109 link.removeAttribute("aria-current"); 110 } 111 }); 112 } 113 114 /* ========================= 115 LINKS HASH (FRONTPAGE) 116 ========================= */ 117 118 if (body.classList.contains("path-frontpage")) { 119 120 hashLinks.forEach(link => { 121 link.addEventListener("click", (e) => { 122 const hash = link.getAttribute("href").replace("/", ""); 123 124 e.preventDefault(); 125 126 history.pushState(null, "", hash); 127 128 // Actualizar estado activo inmediatamente 129 updateActiveByHash(); 130 131 // Esperar a que Bootstrap cierre menú 132 setTimeout(() => { 133 scrollToHash(hash); 134 }, 50); 135 }); 136 }); 137 138 // Carga inicial 139 if (window.location.hash) { 140 scrollToHash(window.location.hash, false); 141 } 142 143 updateActiveByHash(); 144 145 // Navegación atrás / adelante 146 window.addEventListener("popstate", () => { 147 updateActiveByHash(); 148 scrollToHash(window.location.hash, false); 149 }); 150 } 151 152 /* ========================= 153 BLOQUEO SCROLL (MÃVIL) 154 ========================= */ 155 156 function lockScroll() { 157 body.classList.add("menu-open"); 158 } 159 160 function unlockScroll() { 161 body.classList.remove("menu-open"); 162 } 163 164 /* ========================= 165 EVENTOS BOOTSTRAP 166 ========================= */ 167 168 collapseEl.addEventListener("shown.bs.collapse", () => { 169 toggler.setAttribute("aria-expanded", "true"); 170 lockScroll(); 171 }); 172 173 collapseEl.addEventListener("hidden.bs.collapse", () => { 174 toggler.setAttribute("aria-expanded", "false"); 175 unlockScroll(); 176 }); 177 178 /* ========================= 179 CIERRE MENà 180 ========================= */ 181 182 navLinks.forEach(link => { 183 link.addEventListener("click", () => { 184 185 if (link.classList.contains("dropdown-toggle")) return; 186 if (link.closest(".dropdown-menu")) return; 187 188 const bsCollapse = bootstrap.Collapse.getOrCreateInstance(collapseEl); 189 bsCollapse.hide(); 190 });
191 }); 192 193});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.