1/** 2 * Secondary Menu Anchors Navigation 3 * Gestisce la navigazione anchor senza modificare l'URL e lo stato "active" basato sulla visibilità 4 */ 5 6(function() { 7 'use strict'; 8 9 document.addEventListener('DOMContentLoaded', function() { 10 // Seleziona solo il menu con classe "secondary-menu--anchors" 11 const anchorMenu = document.querySelector('.secondary-menu--anchors'); 12 13 if (!anchorMenu) { 14 return; // Se il menu non esiste, esci 15 } 16 17 const anchorLinks = anchorMenu.querySelectorAll('a[data-anchor]'); 18 19 if (anchorLinks.length === 0) { 20 return; 21 } 22 23 // Mappa per associare gli ID delle sezioni ai link 24 const sectionMap = new Map(); 25 const sections = []; 26 27 // Prepara la mappa e raccoglie le sezioni 28 anchorLinks.forEach(link => { 29 const anchorId = link.getAttribute('data-anchor'); 30 if (anchorId) { 31 const section = document.getElementById(anchorId); 32 if (section) { 33 sectionMap.set(anchorId, { 34 link: link, 35 section: section 36 }); 37 sections.push(section); 38 } 39 } 40 }); 41 42 if (sections.length === 0) { 43 return; 44 } 45 46 /** 47 * Rimuove la classe "active" da tutti i link 48 */ 49 function removeActiveClass() { 50 anchorLinks.forEach(link => { 51 link.classList.remove('active'); 52 }); 53 } 54 55 /** 56 * Aggiunge la classe "active" al link corrispondente 57 */ 58 function setActiveLink(anchorId) { 59 removeActiveClass(); 60 const mapped = sectionMap.get(anchorId); 61 if (mapped && mapped.link) { 62 mapped.link.classList.add('active'); 63 } 64 } 65 66 /** 67 * Scroll smooth alla sezione senza modificare l'URL 68 */ 69 function scrollToSection(section) { 70 // Calcola la posizione della sezione 71 const sectionTop = section.getBoundingClientRect().top + window.pageYOffset; 72 73 // Offset di 50px sopra al div 74 const offset = 50; 75 76 // Scroll smooth 77 window.scrollTo({ 78 top: sectionTop - offset, 79 behavior: 'smooth' 80 }); 81 } 82 83 /** 84 * Gestisce il click sui link 85 */ 86 anchorLinks.forEach(link => { 87 link.addEventListener('click', function(e) { 88 e.preventDefault(); 89 e.stopPropagation(); 90 const anchorId = this.getAttribute('data-anchor'); 91 const mapped = sectionMap.get(anchorId); 92 93 if (mapped && mapped.section) { 94 scrollToSection(mapped.section); 95 // Imposta temporaneamente come active (l'observer aggiornerà se necessario)
96 setActiveLink(anchorId); 97 } 98 }); 99 }); 100 101 /** 102 * Intersection Observer per rilevare quando una sezione è visibile 103 */ 104 const observerOptions = { 105 root: null, // viewport 106 rootMargin: '-50px 0px -70% 0px', // Considera l'offset di 50px, la sezione è attiva quando è nel 30% superiore del viewport 107 threshold: [0, 0.1, 0.25, 0.5, 0.75, 1.0] // Threshold multipli per maggiore precisione 108 }; 109 110 // Traccia tutte le sezioni visibili con i loro dati 111 const visibleSectionsData = new Map(); 112 113 const observer = new IntersectionObserver(function(entries) { 114 // Aggiorna i dati delle sezioni visibili 115 entries.forEach(entry => { 116 const section = entry.target; 117 if (entry.isIntersecting) { 118 const rect = section.getBoundingClientRect(); 119 const distanceFromTop = Math.abs(rect.top - 50); // Distanza dal top con offset di 50px 120 const visibleHeight = Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0); 121 const ratio = rect.height > 0 ? visibleHeight / rect.height : 0; 122 123 visibleSectionsData.set(section, { 124 distanceFromTop: distanceFromTop, 125 ratio: ratio, 126 intersectionRatio: entry.intersectionRatio 127 }); 128 } else { 129 visibleSectionsData.delete(section); 130 } 131 }); 132 133 // Se non ci sono sezioni visibili, rimuovi l'active 134 if (visibleSectionsData.size === 0) { 135 removeActiveClass(); 136 return; 137 } 138 139 // Trova la sezione più visibile (quella con il maggior ratio e più vicina al top) 140 let mostVisible = null; 141 let bestScore = -1; 142 143 visibleSectionsData.forEach((data, section) => { 144 // Calcola uno score combinando ratio e distanza dal top 145 // Più alto è il ratio e più vicina è al top, migliore è lo score 146 const score = data.ratio * 100 - (data.distanceFromTop / 10); 147 148 // Considera solo sezioni con almeno 10% di visibilità e vicine al top (entro 200px) 149 if (data.ratio >= 0.1 && data.distanceFromTop <= 200 && score > bestScore) { 150 bestScore = score; 151 mostVisible = section; 152 } 153 }); 154 155 // Se c'è una sezione visibile che soddisfa i criteri, attiva il link corrispondente 156 if (mostVisible) { 157 const anchorId = mostVisible.getAttribute('id'); 158 if (anchorId) {
159 setActiveLink(anchorId); 160 } 161 } else { 162 // Nessuna sezione soddisfa i criteri, rimuovi l'active 163 removeActiveClass(); 164 } 165 }, observerOptions); 166 167 // Osserva tutte le sezioni 168 sections.forEach(section => { 169 observer.observe(section); 170 }); 171 172 // Gestisce anche lo scroll manuale per aggiornare lo stato active 173 let scrollTimeout; 174 window.addEventListener('scroll', function() { 175 clearTimeout(scrollTimeout); 176 scrollTimeout = setTimeout(function() { 177 // Trova la sezione più vicina al top del viewport (considerando l'offset di 50px) 178 let closestSection = null; 179 let closestDistance = Infinity; 180 const offset = 50; 181 182 sections.forEach(section => { 183 const rect = section.getBoundingClientRect(); 184 // Considera la posizione della sezione rispetto al top del viewport con offset 185 const sectionTop = rect.top; 186 187 // Una sezione è considerata "attiva" se: 188 // - à sopra o vicino al top del viewport (con offset di 50px) 189 // - à almeno parzialmente visibile nel viewport 190 const isAboveOrNearTop = sectionTop <= offset + 100; // 100px di tolleranza 191 const isVisible = rect.top < window.innerHeight && rect.bottom > 0; 192 193 if (isVisible && isAboveOrNearTop) { 194 const distance = Math.abs(sectionTop - offset); 195 if (distance < closestDistance) { 196 closestDistance = distance; 197 closestSection = section; 198 } 199 } 200 }); 201 202 if (closestSection) { 203 const anchorId = closestSection.getAttribute('id'); 204 if (anchorId) {
205 setActiveLink(anchorId); 206 } 207 } else { 208 // Nessuna sezione con anchor è visibile o vicina al top, rimuovi l'active 209 removeActiveClass(); 210 } 211 }, 100); 212 }); 213 214 // Scroll orizzontale con rotella del mouse e drag per menu scrollable 215 if (anchorMenu.classList.contains('secondary-menu__buttons--scrollable')) { 216 const scrollableMenu = anchorMenu.querySelector('.secondary-menu__buttons'); 217 218 if (scrollableMenu) { 219 // Variabili per il drag 220 let isDragging = false; 221 let startX = 0; 222 let scrollLeft = 0; 223 let hasMoved = false; 224 let preventClick = false; 225 226 // Scroll orizzontale con la rotella del mouse 227 scrollableMenu.addEventListener('wheel', function(e) { 228 // Previeni lo scroll verticale della pagina quando si scrolla orizzontalmente 229 if (Math.abs(e.deltaX) < Math.abs(e.deltaY)) { 230 e.preventDefault(); 231 // Converti lo scroll verticale in orizzontale 232 scrollableMenu.scrollLeft += e.deltaY; 233 } 234 }, { passive: false }); 235 236 // Drag/Trascinamento per scorrere 237 scrollableMenu.addEventListener('mousedown', function(e) { 238 // Non attivare il drag se si clicca direttamente su un link 239 if (e.target.tagName === 'A' || e.target.closest('a')) { 240 return; 241 } 242 243 isDragging = true; 244 hasMoved = false; 245 preventClick = false; 246 startX = e.pageX - scrollableMenu.offsetLeft; 247 scrollLeft = scrollableMenu.scrollLeft; 248 249 // Cambia il cursore 250 scrollableMenu.style.cursor = 'grabbing'; 251 scrollableMenu.style.userSelect = 'none'; 252 253 // Previeni il comportamento di default 254 e.preventDefault(); 255 }); 256 257 scrollableMenu.addEventListener('mouseleave', function() { 258 if (isDragging) { 259 isDragging = false; 260 scrollableMenu.style.cursor = 'grab'; 261 scrollableMenu.style.userSelect = ''; 262 } 263 }); 264 265 scrollableMenu.addEventListener('mouseup', function(e) { 266 if (isDragging) { 267 isDragging = false; 268 scrollableMenu.style.cursor = 'grab'; 269 scrollableMenu.style.userSelect = ''; 270 271 // Se c'è stato movimento, previeni il click sui link 272 if (hasMoved) { 273 preventClick = true; 274 setTimeout(function() { 275 preventClick = false; 276 }, 100); 277 } 278 } 279 }); 280 281 scrollableMenu.addEventListener('mousemove', function(e) { 282 if (!isDragging) { 283 return; 284 } 285 286 e.preventDefault(); 287 hasMoved = true; 288 const x = e.pageX - scrollableMenu.offsetLeft; 289 const walk = (x - startX) * 2; // Moltiplica per velocità maggiore 290 scrollableMenu.scrollLeft = scrollLeft - walk; 291 }); 292 293 // Imposta il cursore iniziale 294 scrollableMenu.style.cursor = 'grab'; 295 296 // Previeni il click sui link se c'è stato movimento durante il drag 297 anchorLinks.forEach(link => { 298 link.addEventListener('click', function(e) { 299 if (preventClick) { 300 e.preventDefault(); 301 e.stopPropagation(); 302 } 303 }, true); 304 }); 305 } 306 } 307 }); 308})(); 309
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.