1/** 2 * main.js â Utilitários globais 3 * - prefers-reduced-motion flag no <html> 4 * - IntersectionObserver para seções da home (showcase, blog, categorias, instagram) 5 * - Header scroll via header.js / footer via footer.js (não duplicar aqui) 6 */ 7 8( function () { 9 'use strict'; 10 11 // âââ prefers-reduced-motion âââââââââââââââââââââââââââââââââââ 12 if ( window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches ) { 13 document.documentElement.setAttribute( 'data-reduced-motion', 'true' ); 14 } 15 16 // âââ Scroll reveal genérico âââââââââââââââââââââââââââââââââââ 17 // Adiciona .is-visible quando o elemento entra na viewport. 18 // Header, footer e seus filhos têm observers próprios (header.js / footer.js). 19 const targets = [ 20 { selector: '.showcase', threshold: 0.15 }, 21 { selector: '.blog-section', threshold: 0.15 }, 22 { selector: '.categories-section', threshold: 0.15 }, 23 { selector: '.highlight', threshold: 0.15 }, 24 { selector: '.instagram-section', threshold: 0.15 }, 25 { selector: '.video-institucional', threshold: 0.15 }, 26 { selector: '.video-block', threshold: 0.15 }, 27 { selector: '.archive__hero', threshold: 0.15 }, 28 { selector: '.archive__content', threshold: 0.10 }, 29 { selector: '.blog-single__hero', threshold: 0.15 }, 30 { selector: '.blog-single__featured-img', threshold: 0.15 }, 31 { selector: '.blog-single__article', threshold: 0.10 }, 32 { selector: '.blog-single__related', threshold: 0.15 }, 33 { selector: '.page-content', threshold: 0.10 }, 34 { selector: '.header-hero', threshold: 0.15 }, 35 { selector: '.produtos-grid', threshold: 0 }, 36 { selector: '.produto-detail', threshold: 0 }, 37 { selector: '.produtos-relacionados', threshold: 0 }, 38 { selector: '.pillars__quote-wrap', threshold: 0.15 }, 39 { selector: '.pillars__card', threshold: 0.15 }, 40 { selector: '.material-card', threshold: 0.12 }, 41 { selector: '.collection-card', threshold: 0.12 }, 42 ]; 43 44 targets.forEach( function ( item ) { 45 const els = document.querySelectorAll( item.selector ); 46 if ( ! els.length ) return; 47 48 const observer = new IntersectionObserver( 49 function ( entries ) { 50 entries.forEach( function ( entry ) { 51 if ( entry.isIntersecting ) { 52 entry.target.classList.add( 'is-visible' ); 53 observer.unobserve( entry.target ); 54 } 55 } ); 56 }, 57 { threshold: item.threshold } 58 ); 59
60 els.forEach( function ( el ) { observer.observe( el ); } ); 61 } ); 62 63 // âââ Máscara de telefone â (99) 99999-9999 âââââââââââââââââââ 64 function applyPhoneMask( input ) { 65 input.addEventListener( 'input', function () { 66 var digits = this.value.replace( /\D/g, '' ).slice( 0, 11 ); 67 var masked = ''; 68 69 if ( digits.length === 0 ) { 70 masked = ''; 71 } else if ( digits.length <= 2 ) { 72 masked = '(' + digits; 73 } else if ( digits.length <= 7 ) { 74 masked = '(' + digits.slice( 0, 2 ) + ') ' + digits.slice( 2 ); 75 } else { 76 masked = '(' + digits.slice( 0, 2 ) + ') ' + digits.slice( 2, 7 ) + '-' + digits.slice( 7 ); 77 } 78 79 this.value = masked; 80 } ); 81 } 82 83 document.querySelectorAll( 'input[type="tel"]' ).forEach( applyPhoneMask ); 84 85 // âââ Máscara CPF â 999.999.999-99 âââââââââââââââââââââââââââ 86 function applyCpfMask( input ) { 87 input.setAttribute( 'maxlength', '14' ); 88 input.addEventListener( 'input', function () { 89 var d = this.value.replace( /\D/g, '' ).slice( 0, 11 ); 90 var v = ''; 91 if ( d.length <= 3 ) { 92 v = d; 93 } else if ( d.length <= 6 ) { 94 v = d.slice( 0, 3 ) + '.' + d.slice( 3 ); 95 } else if ( d.length <= 9 ) { 96 v = d.slice( 0, 3 ) + '.' + d.slice( 3, 6 ) + '.' + d.slice( 6 ); 97 } else { 98 v = d.slice( 0, 3 ) + '.' + d.slice( 3, 6 ) + '.' + d.slice( 6, 9 ) + '-' + d.slice( 9 ); 99 } 100 this.value = v; 101 } ); 102 } 103 104 // âââ Máscara CNPJ â 99.999.999/9999-99 ââââââââââââââââââââââ 105 function applyCnpjMask( input ) { 106 input.setAttribute( 'maxlength', '18' ); 107 input.addEventListener( 'input', function () { 108 var d = this.value.replace( /\D/g, '' ).slice( 0, 14 ); 109 var v = ''; 110 if ( d.length <= 2 ) { 111 v = d; 112 } else if ( d.length <= 5 ) { 113 v = d.slice( 0, 2 ) + '.' + d.slice( 2 ); 114 } else if ( d.length <= 8 ) { 115 v = d.slice( 0, 2 ) + '.' + d.slice( 2, 5 ) + '.' + d.slice( 5 ); 116 } else if ( d.length <= 12 ) { 117 v = d.slice( 0, 2 ) + '.' + d.slice( 2, 5 ) + '.' + d.slice( 5, 8 ) + '/' + d.slice( 8 ); 118 } else { 119 v = d.slice( 0, 2 ) + '.' + d.slice( 2, 5 ) + '.' + d.slice( 5, 8 ) + '/' + d.slice( 8, 12 ) + '-' + d.slice( 12 ); 120 } 121 this.value = v; 122 } ); 123 } 124 125 var SEL_CPF = 'input[name="cpf"], input[name="CPF"], input[name*="_cpf"], input[data-mask="cpf"]'; 126 var SEL_CNPJ = 'input[name="cnpj"], input[name="CNPJ"], input[name*="_cnpj"], input[data-mask="cnpj"]'; 127 128 document.querySelectorAll( SEL_CPF ).forEach( applyCpfMask ); 129 document.querySelectorAll( SEL_CNPJ ).forEach( applyCnpjMask ); 130 131 // Aplica em inputs adicionados dinamicamente (ex: formulários carregados via AJAX) 132 var telObserver = new MutationObserver( function ( mutations ) { 133 mutations.forEach( function ( mutation ) { 134 mutation.addedNodes.forEach( function ( node ) { 135 if ( node.nodeType !== 1 ) return; 136 137 var tels = node.matches( 'input[type="tel"]' ) 138 ? [ node ] 139 : Array.from( node.querySelectorAll( 'input[type="tel"]' ) ); 140 tels.forEach( applyPhoneMask ); 141 142 var cpfs = node.matches( SEL_CPF ) 143 ? [ node ] 144 : Array.from( node.querySelectorAll( SEL_CPF ) ); 145 cpfs.forEach( applyCpfMask ); 146 147 var cnpjs = node.matches( SEL_CNPJ ) 148 ? [ node ] 149 : Array.from( node.querySelectorAll( SEL_CNPJ ) ); 150 cnpjs.forEach( applyCnpjMask ); 151 } ); 152 } ); 153 } ); 154 telObserver.observe( document.body, { childList: true, subtree: true } ); 155 156 // âââ Scroll suave no link "Ver mais" do hero de categoria ââââ 157 document.querySelectorAll( '.term-desc-excerpt__link[href^="#"]' ).forEach( function ( link ) { 158 link.addEventListener( 'click', function ( e ) { 159 var target = document.querySelector( link.getAttribute( 'href' ) ); 160 if ( ! target ) return; 161 e.preventDefault(); 162 target.scrollIntoView( { behavior: 'smooth', block: 'start' } ); 163 } ); 164 } ); 165 166 // âââ Conteúdo expandÃvel do produto ââââââââââââââââââââââââââ 167 ( function () { 168 var bodies = document.querySelectorAll( '.product-content-body' ); 169
170 bodies.forEach( function ( body ) { 171 var inner = body.querySelector( '.product-content-body__inner' ); 172 var btn = body.querySelector( '[data-action="toggle"]' ); 173 if ( ! inner || ! btn ) return; 174 175 // Se o conteúdo cabe sem rolar, remove a UI de expansão 176 if ( inner.scrollHeight <= inner.clientHeight + 4 ) { 177 inner.style.maxHeight = 'none'; 178 var grad = inner.querySelector( '.product-content-body__gradient' ); 179 if ( grad ) grad.remove(); 180 var toggle = body.querySelector( '.product-content-body__toggle' ); 181 if ( toggle ) toggle.remove(); 182 return; 183 } 184 185 var labelMore = btn.dataset.labelMore || 'Ver mais â'; 186 var labelLess = btn.dataset.labelLess || 'Ver menos â'; 187 188 btn.addEventListener( 'click', function () { 189 var expanded = inner.classList.toggle( 'is-expanded' ); 190 if ( expanded ) { 191 inner.style.setProperty( '--full-h', inner.scrollHeight + 'px' ); 192 btn.textContent = labelLess; 193 } else { 194 btn.textContent = labelMore; 195 body.scrollIntoView( { behavior: 'smooth', block: 'start' } ); 196 } 197 } ); 198 } ); 199 } )(); 200 201 // âââ Ãndice automático da página (.page-sidebar__nav) ââââââââ 202 // Varre os h2 dentro de .page-body, atribui IDs e monta o <ol>. 203 // Também destaca o item ativo conforme o scroll (IntersectionObserver). 204 ( function () { 205 const nav = document.querySelector( '.page-sidebar__nav' ); 206 const body = document.querySelector( '.page-body' ); 207 if ( ! nav || ! body ) return; 208 209 const headings = Array.from( body.querySelectorAll( 'h2' ) ); 210 if ( ! headings.length ) { 211 nav.closest( '.page-sidebar' )?.remove(); 212 return; 213 } 214 215 // Gera slug único a partir do texto 216 const slugify = ( text ) => 217 text.toLowerCase() 218 .normalize( 'NFD' ).replace( /[\u0300-\u036f]/g, '' ) 219 .replace( /[^a-z0-9]+/g, '-' ) 220 .replace( /^-|-$/g, '' ); 221 222 const used = new Set(); 223 const pairs = headings.map( ( h ) => { 224 if ( ! h.id ) { 225 let slug = slugify( h.textContent ); 226 let candidate = slug; 227 let i = 2; 228 while ( used.has( candidate ) ) candidate = slug + '-' + i++; 229 used.add( candidate ); 230 h.id = candidate; 231 } 232 return { heading: h, id: h.id, label: h.textContent.trim() }; 233 } ); 234 235 // Constrói o <ol> 236 const ol = document.createElement( 'ol' ); 237 pairs.forEach( ( { id, label } ) => { 238 const li = document.createElement( 'li' ); 239 const a = document.createElement( 'a' ); 240 a.href = '#' + id; 241 a.textContent = label; 242 a.dataset.toc = id; 243 li.appendChild( a ); 244 ol.appendChild( li ); 245 } ); 246 nav.innerHTML = ''; 247 nav.appendChild( ol ); 248 249 // Destaque do item ativo via IntersectionObserver 250 const links = nav.querySelectorAll( 'a[data-toc]' ); 251 const setActive = ( id ) => { 252 links.forEach( ( a ) => { 253 a.classList.toggle( 'is-active', a.dataset.toc === id ); 254 } ); 255 }; 256 257 const io = new IntersectionObserver( 258 ( entries ) => { 259 entries.forEach( ( entry ) => { 260 if ( entry.isIntersecting ) setActive( entry.target.id ); 261 } ); 262 }, 263 { rootMargin: '-20% 0px -70% 0px', threshold: 0 } 264 ); 265 266 pairs.forEach( ( { heading } ) => io.observe( heading ) ); 267 } )(); 268 269} )();
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.