1/* --------------------------------------------------------------------------- 2 * Utilita' di accessibilita' condivise (audit Ruffino righe 8, 118, 119) 3 * --------------------------------------------------------------------------- 4 * Focus trap riutilizzabile per modali e pannelli sovrapposti. Bootstrap 3 ha un 5 * proprio enforceFocus, ma riporta il focus sul contenitore del dialog invece di 6 * ciclare tra i controlli: Shift+Tab si blocca e l'ordine di tabulazione si 7 * interrompe (WCAG 2.4.3). Qui il ciclo e' esplicito e bidirezionale. 8 * 9 * a11yFocusTrap($contenitore, $trigger) attiva il ciclo; a11yFocusTrapOff lo 10 * rimuove e riporta il focus dove era prima dell'apertura. 11 * ------------------------------------------------------------------------- */ 12 13var a11yFocusabili = 'a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'; 14 15function a11yVisibili($elementi) { 16 return $elementi.filter(function () { 17 return this.offsetWidth > 0 || this.offsetHeight > 0 || this === document.activeElement; 18 }); 19} 20 21function a11yFocusTrap($contenitore, $trigger) { 22 if (!$contenitore || !$contenitore.length) { 23 return; 24 } 25 $contenitore.data('a11yTrigger', $trigger && $trigger.length ? $trigger : null); 26 27 $contenitore.off('keydown.a11yTrap').on('keydown.a11yTrap', function (e) { 28 if (e.key !== 'Tab' && e.keyCode !== 9) { 29 return; 30 } 31 var $f = a11yVisibili($contenitore.find(a11yFocusabili)); 32 if (!$f.length) { 33 return; 34 } 35 var primo = $f.first()[0]; 36 var ultimo = $f.last()[0]; 37 38 // Il contenitore stesso ha tabindex="-1" e non fa parte del ciclo: se il 39 // focus ci finisce (o esce del tutto) si rientra dall'estremo giusto. 40 if (e.shiftKey) { 41 if (document.activeElement === primo || !$.contains($contenitore[0], document.activeElement)) { 42 e.preventDefault(); 43 ultimo.focus(); 44 } 45 } else { 46 if (document.activeElement === ultimo || !$.contains($contenitore[0], document.activeElement)) { 47 e.preventDefault(); 48 primo.focus(); 49 } 50 } 51 }); 52} 53 54function a11yFocusTrapOff($contenitore) { 55 if (!$contenitore || !$contenitore.length) { 56 return; 57 } 58 $contenitore.off('keydown.a11yTrap'); 59 var $trigger = $contenitore.data('a11yTrigger'); 60 if ($trigger && $trigger.length && $trigger.is(':visible')) { 61 $trigger.trigger('focus'); 62 } 63 $contenitore.removeData('a11yTrigger'); 64} 65 66 67/* --------------------------------------------------------------------------- 68 * ACCESSIBILITA' (audit Ruffino righe 46, 47, 48, 87) - carousel lightSlider 69 * --------------------------------------------------------------------------- 70 * Gli slider erano solo visivi: nessun ruolo, nessun nome, slide fuori vista lette 71 * dagli screen reader come testo continuo, frecce <a> senza href (non raggiungibili 72 * da tastiera) e pallini annunciati come "link 1, link 2" senza stato. 73 * 74 * Invece di modificare il plugin (in modalita' seo si carica lightslider.min.js) si 75 * avvolge $.fn.lightSlider: ogni slider del sito, del tema e di /custom/, riceve il 76 * pattern WAI-ARIA APG carousel tramite i callback onSliderLoad/onAfterSlide, senza 77 * perdere quelli passati da chi lo inizializza. 78 * - contenitore: role="region", aria-roledescription, aria-label (opzione a11yLabel, 79 * altrimenti il primo titolo della sezione che contiene lo slider); 80 * - slide: role="group", aria-roledescription, aria-label "n di N"; quelle fuori 81 * vista e i cloni del loop hanno aria-hidden e i loro controlli escono dal tab order; 82 * - frecce del plugin: role="button", tabindex, nome, Invio/Spazio; 83 * - opzione a11yControls: pulsanti Precedente/Successivo per gli slider senza frecce 84 * (altrimenti navigabili solo trascinando, WCAG 2.5.7); 85 * - pallini: dentro un <nav> con nome, <button type="button"> al posto dell'<a href="#"> 86 * del plugin, "Vai alla slide n", aria-current. 87 * ------------------------------------------------------------------------- */ 88 89function a11yCarouselTesto(chiave) { 90 var predefiniti = { roledescription: 'carousel', slide: 'slide', slideOf: '{0} / {1}', prev: 'Previous slide', next: 'Next slide', goTo: 'Go to slide {0}', pager: 'Slide navigation', image: 'Image {0}' }; 91 return (typeof a11y_carousel !== 'undefined' && a11y_carousel[chiave]) ? a11y_carousel[chiave] : predefiniti[chiave]; 92} 93 94function a11yFormat(testo, a, b) { 95 return testo.replace('{0}', a).replace('{1}', b); 96} 97 98function a11yAttivaConTastiera(e) { 99 if (e.key === 'Enter' || e.key === ' ' || e.keyCode === 13 || e.keyCode === 32) { 100 e.preventDefault(); 101 $(this).trigger('click'); 102 } 103} 104 105// Toglie o rimette nel tab order i controlli di una slide, ricordando il tabindex originale. 106function a11yTabOrder($contenitore, attivo) { 107 $contenitore.find(a11yFocusabili).add($contenitore.find('[data-a11y-tabindex]')).each(function () { 108 var $f = $(this); 109 if (attivo) { 110 if ($f.is('[data-a11y-tabindex]')) { 111 var t = $f.attr('data-a11y-tabindex'); 112 if (t === '') { 113 $f.removeAttr('tabindex'); 114 } else { 115 $f.attr('tabindex', t); 116 } 117 $f.removeAttr('data-a11y-tabindex'); 118 } 119 } else if (!$f.is('[data-a11y-tabindex]')) { 120 $f.attr('data-a11y-tabindex', $f.attr('tabindex') || '').attr('tabindex', '-1'); 121 } 122 });
123} 124 125function a11yCarouselInit($el, opzioni) { 126 var $outer = $el.closest('.lSSlideOuter'); 127 if (!$outer.length || $outer.data('a11yCarousel')) { 128 return; 129 } 130 $outer.data('a11yCarousel', { $el: $el, opzioni: opzioni }).attr('data-a11y-carousel', '1'); 131 132 // Senza a11yLabel si usa il titolo piu' vicino: si risale di pochi livelli e ci si ferma 133 // al primo antenato che ne contiene uno fuori dallo slider. Conta anche il titolo solo 134 // visivo (<p class="h3">, es. "Potrebbe interessarti" nel carrello), altrimenti si 135 // finirebbe sul titolo della pagina. 136 var nome = opzioni.a11yLabel; 137 if (!nome) { 138 $outer.parents().slice(0, 4).each(function () { 139 var $titolo = $(this).find(':header, .h1, .h2, .h3, .h4, .h5').not($outer.find('*')).first(); 140 if ($titolo.length) { 141 nome = $.trim($titolo.text()); 142 return false; 143 } 144 }); 145 } 146 $outer.attr({ 'role': 'region', 'aria-roledescription': a11yCarouselTesto('roledescription') }); 147 if (nome) { 148 $outer.attr('aria-label', nome); 149 } 150 151 // Frecce del plugin: <a> senza href. Non si aggiungono classi: il plugin distingue 152 // le due frecce confrontando l'attributo class per intero. 153 $outer.find('.lSAction > a').each(function () { 154 var $a = $(this); 155 $a.attr({ 'role': 'button', 'tabindex': '0', 'aria-label': a11yCarouselTesto($a.hasClass('lSPrev') ? 'prev' : 'next') }) 156 .on('keydown', a11yAttivaConTastiera) 157 .find('img').attr('alt', ''); 158 }); 159 160 if (opzioni.a11yControls) { 161 var $controlli = $('<div class="a11y-carousel-controls">' + 162 '<button type="button" class="a11y-carousel-prev"><span aria-hidden="true">←</span></button>' + 163 '<button type="button" class="a11y-carousel-next"><span aria-hidden="true">→</span></button>' + 164 '</div>'); 165 $controlli.find('.a11y-carousel-prev').attr('aria-label', a11yCarouselTesto('prev')).on('click', function () { 166 if ($(this).attr('aria-disabled') !== 'true') { 167 $el.goToPrevSlide(); 168 } 169 }); 170 $controlli.find('.a11y-carousel-next').attr('aria-label', a11yCarouselTesto('next')).on('click', function () { 171 if ($(this).attr('aria-disabled') !== 'true') { 172 $el.goToNextSlide(); 173 } 174 }); 175 $outer.append($controlli); 176 } 177 178 // I pallini vengono rigenerati dal plugin al resize: la conversione in <button> e gli 179 // attributi si rimettono in a11yCarouselUpdate, qui solo il contenitore di navigazione. 180 // Nessun handler di tastiera: Invio e Spazio arrivano dal <button> nativo. 181 var $pager = $outer.find('.lSPager'); 182 if ($pager.length && !$pager.parent().is('nav.a11y-carousel-pager')) { 183 $pager.wrap('<nav class="a11y-carousel-pager"></nav>'); 184 $pager.parent().attr('aria-label', a11yCarouselTesto('pager')); 185 } 186} 187 188/* I pallini (e le miniature della galleria) sono <a href="#"> generati dal plugin: si 189 * sostituiscono con <button type="button">, cosi' ruolo, Invio e Spazio arrivano 190 * dall'elemento invece che da role="button" piu' un handler di tastiera. 191 * - type="button" e' obbligatorio: il pager sta dentro il <form runat="server"> e un 192 * <button> senza type ne farebbe il submit; 193 * - il click del plugin e' legato al <li>, mai al figlio (createPager lega $pager = i 194 * <li>, e in tutto il plugin non c'e' un solo selettore sull'<a>): sostituire il 195 * figlio non tocca il comportamento; 196 * - createPager() riscrive gli <a> a ogni resize, ma a11yCarouselUpdate e' richiamata 197 * dal resize handler qui sotto (250 ms, dopo i 200 ms del plugin) oltre che da 198 * onSliderLoad/onAfterSlide: e' lo stesso riaggancio da cui dipendono gia' i nomi. 199 * Idempotente: se il <button> c'e' gia', lo restituisce e basta. */ 200function a11yPagerControl($li) { 201 var $controllo = $li.children('button'); 202 if ($controllo.length) { 203 return $controllo; 204 } 205 var $a = $li.children('a'); 206 if (!$a.length) { 207 return $(); 208 } 209 $controllo = $('<button type="button"></button>').append($a.contents()); 210 $a.replaceWith($controllo); 211 return $controllo; 212} 213 214function a11yCarouselUpdate($el) { 215 var $outer = $el.closest('.lSSlideOuter'); 216 var dati = $outer.data('a11yCarousel'); 217 if (!dati) { 218 return; 219 } 220 var opzioni = dati.opzioni; 221 var wrapper = $outer.find('.lSSlideWrapper')[0]; 222 var box = wrapper ? wrapper.getBoundingClientRect() : null; 223 var $slide = $el.children().not('.clone');
224 var totale = $slide.length; 225 226 $slide.each(function (i) { 227 var $s = $(this); 228 var visibile; 229 if (opzioni.mode === 'fade') { 230 visibile = $s.hasClass('active'); 231 } else { 232 var r = this.getBoundingClientRect(); 233 visibile = !box || (Math.min(r.right, box.right) - Math.max(r.left, box.left) > 1); 234 } 235 $s.attr({ 'role': 'group', 'aria-roledescription': a11yCarouselTesto('slide'), 'aria-label': a11yFormat(a11yCarouselTesto('slideOf'), i + 1, totale) }); 236 if (visibile) { 237 $s.removeAttr('aria-hidden'); 238 } else { 239 $s.attr('aria-hidden', 'true'); 240 } 241 a11yTabOrder($s, visibile); 242 }); 243 $el.children('.clone').each(function () { 244 $(this).attr('aria-hidden', 'true'); 245 a11yTabOrder($(this), false); 246 }); 247 248 if (opzioni.a11yControls && box && $slide.length) { 249 var primo = $slide.first()[0].getBoundingClientRect(); 250 var ultimo = $slide.last()[0].getBoundingClientRect(); 251 var inizio = !opzioni.loop && primo.left >= box.left - 1; 252 var fine = !opzioni.loop && ultimo.right <= box.right + 1; 253 $outer.find('.a11y-carousel-prev').attr('aria-disabled', inizio ? 'true' : 'false'); 254 $outer.find('.a11y-carousel-next').attr('aria-disabled', fine ? 'true' : 'false'); 255 $outer.find('.a11y-carousel-controls').prop('hidden', inizio && fine); 256 } 257 258 $outer.find('.lSPager.lSpg > li').each(function (i) { 259 var $p = a11yPagerControl($(this)); 260 $p.attr('aria-label', a11yFormat(a11yCarouselTesto('goTo'), i + 1)); 261 if ($(this).hasClass('active')) { 262 $p.attr('aria-current', 'true'); 263 } else { 264 $p.removeAttr('aria-current'); 265 } 266 }); 267 $outer.find('.lSPager.lSGallery > li').each(function (i) { 268 var $p = a11yPagerControl($(this)); 269 $p.attr('aria-label', a11yFormat(a11yCarouselTesto('image'), i + 1)).find('img').attr('alt', ''); 270 if ($(this).hasClass('active')) { 271 $p.attr('aria-current', 'true'); 272 } else { 273 $p.removeAttr('aria-current'); 274 } 275 }); 276} 277 278(function ($) { 279 if (!$ || !$.fn.lightSlider) { 280 return; 281 } 282 var lightSliderOriginale = $.fn.lightSlider; 283 $.fn.lightSlider = function (opzioni) { 284 var o = $.extend({}, opzioni); 285 var onLoad = o.onSliderLoad; 286 var onAfter = o.onAfterSlide; 287 // keyPress del plugin ascolta keyup su tutto il documento e fa preventDefault su 288 // qualunque tasto fuori da input/textarea: annullava lo Spazio sui <button> della 289 // pagina (che si attivano al keyup) e le frecce spostavano tutti gli slider insieme. 290 // Si spegne e le frecce funzionano solo con il focus dentro il carousel. 291 var frecce = !!o.keyPress; 292 o.keyPress = false; 293 o.onSliderLoad = function ($el) { 294 a11yCarouselInit($el, o); 295 if (frecce) { 296 $el.closest('.lSSlideOuter').off('keydown.a11yCarousel').on('keydown.a11yCarousel', function (e) { 297 if ($(e.target).is('input, textarea, select, [role="combobox"]')) { 298 return; 299 } 300 if (e.key === 'ArrowLeft' || e.keyCode === 37) { 301 e.preventDefault(); 302 $el.goToPrevSlide(); 303 } else if (e.key === 'ArrowRight' || e.keyCode === 39) { 304 e.preventDefault(); 305 $el.goToNextSlide(); 306 } 307 }); 308 } 309 if (typeof onLoad === 'function') { 310 onLoad.apply(this, arguments); 311 } 312 a11yCarouselUpdate($el); 313 }; 314 o.onAfterSlide = function ($el) { 315 if (typeof onAfter === 'function') { 316 onAfter.apply(this, arguments); 317 } 318 a11yCarouselUpdate($el); 319 // la transizione CSS puo' non essere ancora finita: secondo passaggio 320 setTimeout(function () { a11yCarouselUpdate($el); }, (o.speed || 400) + 50); 321 }; 322 return lightSliderOriginale.call(this, o); 323 }; 324 325 var timer = null; 326 $(window).on('resize.a11yCarousel', function () { 327 clearTimeout(timer); 328 timer = setTimeout(function () { 329 $('.lSSlideOuter[data-a11y-carousel]').each(function () { 330 var dati = $(this).data('a11yCarousel'); 331 if (dati) { 332 a11yCarouselUpdate(dati.$el); 333 } 334 }); 335 }, 250); 336 });
337})(window.jQuery); 338 339 340// Cookie Consent by https://www.TermsFeed.com 341if (typeof cookieconsentDisable == "undefined" || cookieconsentDisable === false) { 342 343 document.addEventListener('DOMContentLoaded', function () { 344 345 cookieconsent.run({ 346 "notice_banner_type": "simple", 347 "consent_type": "express", 348 "palette": "dark", 349 "language": $("body").data("language-iso"), 350 "page_load_consent_levels": ["strictly-necessary"], 351 "notice_banner_reject_button_hide": false, 352 "preferences_center_close_button_hide": false, 353 "page_refresh_confirmation_buttons": false, 354 "website_name": "", 355 "callbacks": { 356 "scripts_specific_loaded": (level) => { 357 switch (level) { 358 case 'targeting': 359 gtag('consent', 'update', { 360 'ad_storage': 'granted', 361 'ad_user_data': 'granted', 362 'ad_personalization': 'granted', 363 'analytics_storage': 'granted', 364 'functionality_storage': 'granted', 365 'security_storage': 'granted', 366 'personalization_storage': 'granted' 367 }); 368 break; 369 } 370 }, 371 // audit riga 10: a consenso espresso si aprono gli overlay rimasti in coda 372 // (modale paese), uno alla volta e mai sopra il banner. Vedi modalCountryShow(). 373 "i_agree_button_clicked": function () { a11yOverlayNext(); }, 374 "i_decline_button_clicked": function () { a11yOverlayNext(); }, 375 "user_consent_saved": function () { a11yOverlayNext(); } 376 }, 377 "callbacks_force": true 378 }); 379 }); 380} 381 382var lsslider; 383var lsnews; 384var lscsellingcart; 385var scroolTopOffset = 90; 386 387$(function () { 388 389 //cart (fuori dal percorso critico: vedi cart_get_deferred in default.js) 390 cart_get_deferred(); 391 392 var lastScrollTop = 0; 393 $(window).scroll(function() { 394 var currentScrollTop = $(this).scrollTop(); 395 396 if (currentScrollTop >= scroolTopOffset) { 397 398 $("body").addClass("scroll"); 399 400 if (currentScrollTop > lastScrollTop) { 401 $("body").removeClass("scrolltop"); 402 $("body").addClass("scrolldown"); 403 $("body").addClass("scrolldownfirst"); 404 } else { 405 $("body").removeClass("scrolldown"); 406 $("body").addClass("scrolltop"); 407 $("body").addClass("scrolltopfirst"); 408 } 409 410 } else { 411 412 $("body").removeClass("scroll"); 413 $("body").removeClass("scrolldown"); 414 $("body").removeClass("scrolltop"); 415 $("body").removeClass("scrolltopfirst"); 416 $("body").removeClass("scrolldownfirst"); 417 418 } 419 420 lastScrollTop = currentScrollTop; 421 422 }); 423 424 //if ($(window).width() > 768) { 425 // $(".dropdown").mouseenter(function () { 426 // $(this).addClass('open'); 427 // }); 428 429 // $(".dropdown").mouseleave(function () { 430 // $(this).removeClass("open"); 431 // }); 432 //} 433 434 //$(".dropdown").on("click", ".dropdown-menu", function (e) { 435 // e.stopPropagation(); 436 //}); 437 438 //scrollTo 439 initScrollTo(); 440 441 //language_url sostituisci in: link accettazione privacy, cookies, terms & conditions 442 var language_url = $("body").data("language-url"); 443 $(".reg_privacy a, .form_privacy a, .newsletter_privacy a, .alert-cookies a, .order_privacy a, .order_terms a").each(function () { 444 $(this).attr("href", $(this).attr("href").replace("{language_url}", language_url)); 445 }); 446 447 var el_gallery = document.getElementsByClassName('lg-gallery'); 448 for (let item of el_gallery) { 449 lightGallery(item, { 450 plugins: [lgVideo], 451 selector: '.lg-gallery-item', 452 share:false, 453 download:false, 454 iframeMaxWidth: '80%' 455 }) 456 } 457 458 var el_video = document.getElementsByClassName('lg-video'); 459 for (let item of el_video) { 460 lightGallery(item, { 461 plugins: [lgVideo], 462 iframeMaxWidth: '80%' 463 }) 464 } 465 466 467 ////lightgallery 468 //$('.lg-gallery').lightGallery({ 469 // selector: '.lg-gallery-item', 470 // videoMaxWidth: '80%', 471 // iframeMaxWidth: '80%', 472 // thumbnail: false, 473 // animateThumb: false, 474 // showThumbByDefault: false, 475 // share: true, 476 // googlePlus: false, 477 // download: false 478 //}); 479 480 //$('#video-gallery').lightGallery(); 481 482 ////lightgallery single image 483 //$('.lg-image').lightGallery({ 484 // selector: 'this', 485 // download: false 486 //}); 487 488 ////lightgallery Video 489 //$('.lg-video').lightGallery({ 490 // selector: 'this', 491 // videoMaxWidth: '80%' 492 //}); 493 494 ////lightgallery Page 495 //$('.lg-page').lightGallery({ 496 // selector: 'this', 497 // iframeMaxWidth: '80%' 498 //}); 499 500 501 //primary nav 502 // ACCESSIBILITA' (audit righe 118, 119): il trigger e' ora un <button> con 503 // aria-expanded/aria-controls (vedi MasterPage.master). Qui si tiene aggiornato lo 504 // stato, si contiene il focus dentro il pannello aperto e si chiude con Esc 505 // riportando il focus sul trigger: senza, superato l'ultimo link il focus usciva dal 506 // menu senza tornare al punto di partenza. 507 $('#header').on('click', '.primary-nav-trigger', function (e) { 508 e.preventDefault(); 509 var $trigger = $(this); 510 var aperto = !$('body').hasClass('navigation-is-open'); 511 512 $('body').toggleClass('navigation-is-open');
513 $trigger.find('.menu-icon').toggleClass('is-clicked'); 514 $trigger.attr('aria-expanded', aperto ? 'true' : 'false'); 515 516 var $pannello = $('#' + ($trigger.attr('aria-controls') || 'primary-nav')); 517 if (aperto) { 518 a11yFocusTrap($pannello, $trigger); 519 $pannello.find(a11yFocusabili).first().trigger('focus'); 520 } else { 521 a11yFocusTrapOff($pannello); 522 $trigger.trigger('focus'); 523 } 524 }); 525 526 // Esc chiude il menu mobile, come ci si aspetta da un pannello sovrapposto. 527 $(document).on('keydown.a11yNav', function (e) { 528 if (e.key === 'Escape' && $('body').hasClass('navigation-is-open')) { 529 $('#header .primary-nav-trigger').trigger('click'); 530 } 531 }); 532 533 // ACCESSIBILITA' (audit riga 8): il focus trap di Bootstrap 3 riporta il focus sul 534 // contenitore del dialog (tabindex="-1") invece di ciclare tra i controlli. Il 535 // risultato e' che Shift+Tab si incastra e non si puo' tornare indietro. 536 // Qui il ciclo e' esplicito e bidirezionale, e resta confinato agli elementi 537 // realmente interattivi della modale. Vale per tutte le modali del tema. 538 $(document).on('shown.bs.modal', '.modal', function () { 539 var $modale = $(this); 540 a11yFocusTrap($modale, $(document.activeElement)); 541 $modale.find(a11yFocusabili).first().trigger('focus'); 542 }); 543 544 $(document).on('hidden.bs.modal', '.modal', function () { 545 a11yFocusTrapOff($(this)); 546 }); 547 548 //newseletter open 549 $("#newsletter_open").on("click", function () { 550 $("#newsletter .newsletter_email").val($("#newsletter_open_email").val()); 551 }); 552 553 554 //Slider principale 555 if ($(".ls-banner .ls-item").length > 1) { 556 $(".ls-banner").lightSlider({ 557 keyPress: true, 558 auto: true, 559 mode: 'fade', 560 useCSS: true, 561 loop: true, 562 item: 1, 563 speed: 800, 564 pause: 4000, 565 controls: true, 566 pager: false, 567 centerSlide: false, 568 prevHtml: '<img class="hidden-xs" src="/site/vinora.1.0/theme/img/ico-arrow-left-white.svg" alt="next" width="40" height="20" />', 569 nextHtml: '<img class="hidden-xs" src="/site/vinora.1.0/theme/img/ico-arrow-right-white.svg" alt="prev" width="40" height="20" />' 570 }); 571 } 572 if ($(".ls-banner .ls-item").length === 0) { 573 $('#header').addClass('menu-transparent-dark'); 574 } 575 576 //NEWS 577 if ($(".ls-news .ls-item").length > 0) { 578 lsnews = $(".ls-news").lightSlider({ 579 keyPress: true, 580 auto: false, 581 mode: 'slide', 582 useCSS: true, 583 cssEasing: 'ease', 584 easing: 'ease', 585 loop: false, 586 item: 3, 587 slideMargin: 30, 588 speed: 400, 589 pause: 4000, 590 controls: false, 591 pager: false, 592 centerSlide: false, 593 prevHtml: '<img class="hidden-xs" src="/site/vinora.1.0/theme/img/ico-arrow-left-grey.png" alt="prev" />', 594 nextHtml: '<img class="hidden-xs" src="/site/vinora.1.0/theme/img/ico-arrow-right-grey.png" alt="next" />', 595 responsive: [{ 596 breakpoint: 992, 597 settings: { 598 item: 2 599 } 600 }, { 601 breakpoint: 768, 602 settings: { 603 item: 1 604 } 605 }] 606 }); 607 //$('.ls-news-next').click(function (e) { 608 // e.preventDefault(); 609 // lsnews.goToNextSlide(); 610 //}); 611 } 612 613 //Slider principale 614 615 if ($(".ls-slider .ls-item").length > 1) { 616 lsslider = $(".ls-slider").lightSlider({ 617 keyPress: true, 618 auto: true, 619 mode: 'fade', 620 useCSS: true, 621 loop: true, 622 item: 1, 623 speed: 800, 624 pause: 4000, 625 controls: true, 626 pager: false, 627 centerSlide: false, 628 prevHtml: '<img class="hidden-xs" src="/site/vinora.1.0/theme/img/ico-arrow-left-white.svg" alt="next" width="40" height="20" />', 629 nextHtml: '<img class="hidden-xs" src="/site/vinora.1.0/theme/img/ico-arrow-right-white.svg" alt="prev" width="40" height="20" />' 630 }); 631 } 632 633 //Slider New 634 if ($(".ls-slider-new .ls-item").length > 1) { 635 $(".ls-slider-new").lightSlider({ 636 keyPress: true,
637 auto: false, 638 mode: 'slide', 639 useCSS: true, 640 cssEasing: 'ease', 641 easing: 'ease', 642 loop: true, 643 item: 1, 644 speed: 800, 645 pause: 4000, 646 controls: false, 647 pager: true, 648 centerSlide: false, 649 prevHtml: '<img class="hidden-xs" src="/site/vinora.1.0/theme/img/ico-arrow-left-white.svg" alt="next" width="40" height="20" />', 650 nextHtml: '<img class="hidden-xs" src="/site/vinora.1.0/theme/img/ico-arrow-right-white.svg" alt="prev" width="40" height="20" />' 651 }); 652 } 653 654 //CROSS SELLING CARRELLO 655 if ($(".ls-cselling_cart .ls-item").length > 0) { 656 lscsellingcart = $(".ls-cselling_cart").lightSlider({ 657 keyPress: true, 658 auto: false, 659 mode: 'slide', 660 useCSS: true, 661 cssEasing: 'ease', 662 easing: 'ease', 663 loop: false, 664 item: 3, 665 slideMargin: 30, 666 speed: 400, 667 pause: 4000, 668 controls: false, 669 pager: true, 670 centerSlide: false, 671 prevHtml: '<img class="hidden-xs" src="/site/vinora.1.0/theme/img/ico-arrow-left-grey.png" alt="prev" />', 672 nextHtml: '<img class="hidden-xs" src="/site/vinora.1.0/theme/img/ico-arrow-right-grey.png" alt="next" />', 673 responsive: [{ 674 breakpoint: 1200, 675 settings: { 676 item: 2 677 } 678 }, { 679 breakpoint: 768, 680 settings: { 681 item: 1 682 } 683 }] 684 }); 685 } 686 // La guardia su $.fn.coverVid non e' difensiva a caso: con il setting "covervidEnabled" a 687 // false (k_setting.covervidEnabled) la MasterPage non include piu' covervid.konico.js, e 688 // senza guardia qui partirebbe un "coverVid is not a function" che interromperebbe il resto 689 // di initAfterPageLoad. In quel caso l'effetto cover lo fa il CSS: 690 // .covervid-wrapper video { object-fit: cover } in theme/css/theme.css. 691 setTimeout(function () { 692 if ($.fn.coverVid && $(".covervid-wrapper video").length > 0) { 693 $(".covervid-wrapper video").coverVid(1920, 1080); 694 } 695 }, 100); 696 697 698 //video background attiva/disattiva audio 699 //da rimuovere da custom classe hidden a video-play 700 $(".video-play").on("click", function (e) { 701 e.preventDefault(); 702 var v = $(this).parents(".video-wrapper").find("video").get(0); 703 $(this).find(".volume-off").addClass("hidden"); 704 $(this).find(".volume-on").addClass("hidden"); 705 if (v.paused) { v.play(); } 706 if (v.muted) { 707 v.muted = false; 708 $(this).find(".volume-on").removeClass("hidden"); 709 } else { 710 v.muted = true; 711 $(this).find(".volume-off").removeClass("hidden"); 712 } 713 }); 714 715 716 717 718 719}); //chiusura document ready 720 721 722//variabili per abilitare da custom imagesloaded e il delay 723let imgload = true; 724let imgload_delay = 200; 725let imgload_delay_div = 0; 726 727//da eseguire quando il contenuto e' visibile: animazioni allo scroll e risorse lazy 728function initAfterPageLoad() { 729 730 $(".wpl").each(function () { 731 var wp = $(this); 732 wp.css("animation-delay", wp.data("delay")); 733 wp.waypoint(function () { 734 $(this.element).addClass("animated " + wp.data("animation")); 735 }, { offset: wp.data("offset") }); 736 }); 737 738 $(".lazy-loading-video").each(function () { 739 $(this).html("<video controls='controls' style='height:auto; max-width:100%'></video>"); 740 $(this).find("video").attr("src", $(this).data("src")); 741 $(this).find("video").attr("poster", $(this).data("poster")); 742 $(this).find("video")[0].load(); 743 }); 744 745 // "data-lazy" porta la variante base (big per gli slider a piena larghezza), "data-lazy-full" 746 // quella grande da usare sopra i 1200 px: stessa soglia delle media query emesse da 747 // k_image.slider_bg_css per gli stessi slider in home e page/1. Qui serve il JS e non una 748 // regola CSS perche' il lazy scrive lo style inline, che vincerebbe comunque sulla media query. 749 // Gli elementi senza "data-lazy-full" (le gallery, e tutto con seoOptimize off) si comportano 750 // esattamente come prima. 751 $(".lazy-loading-background").each(function () { 752 var full = $(this).data("lazy-full"); 753 var src = (full && window.matchMedia("(min-width:1201px)").matches) ? full : $(this).data("lazy"); 754 $(this).css("background-image", "url('" + src + "')"); 755 });
756 757} 758 759function initPageLoad() { 760 //Con "pageLoader" = "false" in Settings.config la MasterPage non renderizza il velo di 761 //caricamento. Il controllo sulla lunghezza e' obbligatorio: jQuery non esegue la callback di 762 //fadeOut su una selezione vuota, quindi senza questo ramo initAfterPageLoad() non partirebbe e 763 //resterebbero fermi i waypoint delle animazioni, i video e gli sfondi lazy. 764 var loader = $(".page-loader"); 765 if (loader.length === 0) { 766 initAfterPageLoad(); 767 return; 768 } 769 loader.find("div").delay(imgload_delay_div).fadeOut(); 770 loader.delay(imgload_delay).fadeOut("slow", initAfterPageLoad); 771} 772$(function () { 773 //senza loader da nascondere non ha senso aspettare window.load 774 if (imgload && $(".page-loader").length) { 775 $(window).on("load", initPageLoad); 776 } else { 777 initPageLoad(); 778 } 779}); 780 781 782 783/* ACCESSIBILITA' (audit riga 10) - WCAG 2.4.3 Focus Order 784 * Alla prima visita partivano insieme il banner cookie (dialog aria-modal) e la 785 * modale paese, che con l'enforceFocus di Bootstrap si riprendeva il focus: da 786 * tastiera il banner non era piu' raggiungibile. Ora gli overlay sono in sequenza: 787 * finche' il consenso cookie non e' stato espresso la modale paese resta in coda e 788 * si apre dai callback di TermsFeed (vedi cookieconsent.run in cima al file). */ 789var a11yOverlayQueue = []; 790 791function a11yCookieConsentPending() { 792 if (typeof cookieconsentDisable !== "undefined" && cookieconsentDisable === true) { 793 return false; 794 } 795 if (typeof cookieconsent === "undefined") { 796 return false; 797 } 798 return Cookies.get('cookie_consent_user_accepted') !== 'true'; 799} 800 801function a11yOverlayNext() { 802 // Il callback arriva prima che TermsFeed tolga il banner: si lascia finire. 803 setTimeout(function () { 804 // TermsFeed lancia user_consent_saved anche al caricamento, quando per un utente 805 // nuovo salva da solo il livello "strictly-necessary" (page_load_consent_levels): 806 // in quel momento il banner e' ancora aperto e la coda deve restare ferma. 807 // Si procede solo quando la scelta dell'utente e' registrata. 808 if (a11yCookieConsentPending()) { 809 return; 810 } 811 while (a11yOverlayQueue.length) { 812 a11yOverlayQueue.shift()(); 813 } 814 }, 0); 815} 816 817function modalCountryShow() { 818 if (Cookies.get('modal-country') == 1) { 819 return; 820 } 821 var show = function () { 822 $('#modal-country').modal('show'); 823 Cookies.set('modal-country', '1', { expires: 7 }); 824 }; 825 if (a11yCookieConsentPending()) { 826 a11yOverlayQueue.push(show); 827 } else { 828 // ACCESSIBILITA' (audit riga 8): $(show) e non show(). Questa funzione e' chiamata da uno 829 // <script> inline (RegisterStartupScript in MasterPage.master.vb) che gira DURANTE il parsing: 830 // aprendo la modale li', shown.bs.modal scattava ~2 ms prima di DOMContentLoaded e il gestore 831 // che aggancia il focus trap, registrato dentro il $(function(){...}) piu' sopra, arrivava 832 // tardi. Risultato: modale senza trap, focus fermo sul contenitore e Tab che usciva dalla 833 // modale. $() esegue subito se il DOM e' gia' pronto, altrimenti al ready: la modale si apre 834 // qualche ms dopo, quando il gestore c'e'. Il ramo in coda non ha il problema perche' viene 835 // svuotato dai callback di TermsFeed, molto dopo il ready. Vedi voce 8 del report Ruffino. 836 $(show); 837 } 838} 839 840 841/* ACCESSIBILITA' (audit riga 41) - link che aprono una nuova finestra 842 * Ogni a[target=_blank] riceve un testo .sr-only con l'avviso, a meno che il 843 * testo del link non lo contenga gia' (icone social della MasterPage). 844 * Copre anche i link scritti dopo il caricamento (consensi e privacy iniettati da 845 * custom/theme.js, banner TermsFeed, postback asincroni) con un MutationObserver. */ 846function a11yNewWindowLinks() { 847 if (typeof a11y_new_window === "undefined" || !a11y_new_window) { 848 return; 849 } 850 var avviso = a11y_new_window.toLowerCase(); 851 $('a[target="_blank"]').each(function () { 852 var $a = $(this); 853 if ($a.children('.a11y-new-window').length || $a.text().toLowerCase().indexOf(avviso) !== -1) { 854 return; 855 } 856 $a.append('<span class="sr-only a11y-new-window"> (' + $('<div>').text(a11y_new_window).html() + ')</span>'); 857 });
858} 859 860(function () { 861 if (!window.MutationObserver) { 862 return; 863 } 864 var timer = null; 865 $(function () { 866 new MutationObserver(function () { 867 clearTimeout(timer); 868 timer = setTimeout(a11yNewWindowLinks, 50); 869 }).observe(document.body, { childList: true, subtree: true }); 870 }); 871})(); 872 873 874/* ACCESSIBILITA' (audit riga 69) - WCAG 1.3.1 875 * La voce corrente era comunicata solo dalla classe .active (colore/sottolineatura). 876 * - menu header, pannello mobile e menu footer: aria-current="page" sul link della 877 * pagina aperta, aria-current="true" sulle voci .active che la contengono; 878 * - filtri categoria e paginazione dello shop: aria-current="page" sulla voce attiva. */ 879function a11yPath(p) { 880 return (p || '').replace(/\/+$/, '').toLowerCase(); 881} 882 883function a11yAriaCurrent() { 884 var path = a11yPath(location.pathname); 885 $('#header .menu a[href], #primary-nav a[href], #footer-menu a[href]').each(function () { 886 var $a = $(this); 887 var href = $a.attr('href'); 888 // href="#" o "javascript:" (voci contenitore, controlli): il pathname risolto 889 // coinciderebbe con la pagina corrente senza esserlo. 890 if (href.charAt(0) === '#' || href.indexOf('javascript:') === 0) { 891 $a.removeAttr('aria-current'); 892 return; 893 } 894 if (this.host === location.host && a11yPath(this.pathname) === path) { 895 $a.attr('aria-current', 'page'); 896 } else if ($a.parent('li').hasClass('active')) { 897 $a.attr('aria-current', 'true'); 898 } else { 899 $a.removeAttr('aria-current'); 900 } 901 }); 902 $('#category-filters li.active > a').attr('aria-current', 'page'); 903 $('.paging .btn-link.active').attr('aria-current', 'page'); 904} 905 906/* ACCESSIBILITA' (audit righe 63, 64, 65, 66) - postback asincroni e pulsanti-link 907 * - Un UpdatePanel che si aggiorna sostituisce il controllo che aveva il focus (le select 908 * AutoPostBack sono anche disabilitate da KoalaInitializeRequest): il focus cadeva su <body>. 909 * Finito il postback, se il focus e' perso torna sul controllo che l'ha lanciato, ricreato 910 * con lo stesso id; per ricerca e annulla ricerca va sul campo di ricerca. 911 * - Listing prodotti (variabile a11y_listing della pagina categoria): quando il pannello del 912 * listing si aggiorna, la live region #listing-status annuncia il risultato. 913 * - LinkButton con role="button" (classe a11y-button): Invio funziona gia' come link, qui 914 * si aggiunge lo Spazio come per un <button>. */ 915var a11yPostbackId = null; 916 917function a11yListingAnnounce(ricerca) { 918 var $status = $('#listing-status'); 919 if (typeof a11y_listing === 'undefined' || !$status.length) { 920 return; 921 } 922 var n = parseInt($('#listing-count').attr('data-count'), 10) || 0; 923 var testo = ricerca ? (n > 0 ? a11yFormat(a11y_listing.found, n) : a11y_listing.none) : a11yFormat(a11y_listing.updated, n); 924 // svuotata e riscritta dopo un attimo: lo stesso testo ripetuto verrebbe ignorato 925 $status.text(''); 926 setTimeout(function () { $status.text(testo); }, 100); 927} 928 929/* ACCESSIBILITA' (audit righe 83, 85) - messaggi di stato 930 * Scrive il testo in una delle live region fisse della MasterPage (#a11y-status cortese, 931 * #a11y-alert urgente). Svuotata e riscritta dopo un attimo, cosi' anche lo stesso testo 932 * ripetuto viene annunciato; ripulita dopo qualche secondo per non lasciare testo vecchio 933 * in fondo alla pagina. */ 934var a11yAnnounceTimer = {}; 935 936function a11yAnnounce(testo, urgente) { 937 var id = urgente ? 'a11y-alert' : 'a11y-status'; 938 var $r = $('#' + id); 939 testo = $.trim(testo || ''); 940 if (!$r.length || !testo) { 941 return; 942 } 943 clearTimeout(a11yAnnounceTimer[id]); 944 $r.text(''); 945 setTimeout(function () { 946 $r.text(testo); 947 a11yAnnounceTimer[id] = setTimeout(function () { $r.text(''); }, 7000); 948 }, 100); 949} 950 951/* Errori dei validatori resi dal server dopo un postback asincrono (es. cv_cart "carrello non 952 * valido"): il pannello arriva con il messaggio gia' dentro e role="alert" su un nodo appena 953 * inserito non viene annunciato in modo affidabile. Si rilegge il testo dei validatori visibili 954 * nei pannelli aggiornati e lo si annuncia nella live region urgente. */ 955function a11yAnnounceValidators(pannelli) { 956 if (!pannelli || !pannelli.length) { 957 return; 958 } 959 setTimeout(function () { 960 var candidati = $.makeArray(window.Page_Validators || []).concat($.makeArray(window.Page_ValidationSummaries || [])); 961 $(pannelli).find('[role="alert"]').each(function () { candidati.push(this); }); 962 var visti = [], testi = []; 963 $.each(candidati, function (i, el) { 964 if (!el || $.inArray(el, visti) !== -1 || !document.body.contains(el)) { 965 return; 966 } 967 visti.push(el); 968 var dentro = $.grep(pannelli, function (p) { return p.contains(el); }).length > 0;
969 var stile = window.getComputedStyle(el); 970 var testo = $.trim($(el).text()); 971 if (dentro && testo && stile.visibility !== 'hidden' && stile.display !== 'none' && (el.offsetWidth > 0 || el.offsetHeight > 0)) { 972 testi.push(testo); 973 } 974 }); 975 if (testi.length) { 976 a11yAnnounce(testi.join(' '), true); 977 } 978 }, 0); 979} 980 981function a11yAsyncPostback() { 982 if (typeof Sys === 'undefined' || !Sys.WebForms || !Sys.WebForms.PageRequestManager) { 983 return; 984 } 985 var prm = Sys.WebForms.PageRequestManager.getInstance(); 986 prm.add_initializeRequest(function (sender, args) { 987 var el = args.get_postBackElement(); 988 a11yPostbackId = el && el.id ? el.id : null; 989 }); 990 prm.add_pageLoaded(function (sender, args) { 991 a11yAnnounceValidators(args.get_panelsUpdated()); 992 var id = a11yPostbackId; 993 a11yPostbackId = null; 994 if (!id) { 995 return; 996 } 997 var listing = typeof a11y_listing !== 'undefined' ? a11y_listing : null; 998 var ricerca = !!(listing && $.inArray(id, listing.searchTriggers) !== -1); 999 if (listing) { 1000 var aggiornati = $.map(args.get_panelsUpdated() || [], function (p) { return p.id; }); 1001 if ($.inArray(listing.panel, aggiornati) !== -1) { 1002 // "N prodotti trovati" solo dopo una ricerca; annulla ricerca, ordinamento e filtri: "Lista aggiornata" 1003 a11yListingAnnounce(id === listing.searchTriggers[0]); 1004 } 1005 } 1006 var attivo = document.activeElement; 1007 if (attivo && attivo !== document.body && document.body.contains(attivo)) { 1008 return; 1009 } 1010 var target = ricerca ? document.getElementById(listing.search) : document.getElementById(id); 1011 if (target && (target.offsetWidth > 0 || target.offsetHeight > 0)) { 1012 target.focus(); 1013 } 1014 }); 1015} 1016 1017$(document).on('keydown', 'a.a11y-button[role="button"]', function (e) { 1018 if (e.key === ' ' || e.keyCode === 32) { 1019 e.preventDefault(); 1020 this.click(); 1021 } 1022}); 1023 1024$(function () { 1025 a11yAsyncPostback(); 1026}); 1027 1028function themePageLoad() { 1029 1030 $(function () { 1031 a11yAriaCurrent(); 1032 a11yNewWindowLinks(); 1033 1034 //selectordie 1035 $("#block-product .sod").selectOrDie({ 1036 cycle: true, 1037 size: 5 1038 }); 1039 }); 1040 1041}
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.