PageSourceSearch

https://www.hashtagtreinamentos.com/wp-content/themes/hashtag/dese…/assets/js/carrossel.js?ver=1787594435

js hashtagtreinamentos.com collected 2026-10-03 19:19:50 UTC 14,499 bytes, 543 lines download raw bytes

1(function () {
2  const initializedAttr = "data-carrossel-initialized";
3  const automaticInterval = 3000;
4
5  function rafThrottle(callback) {
6    let ticking = false;
7
8    return (...args) => {
9      if (ticking) {
10        return;
11      }
12
13      ticking = true;
14
15      window.requestAnimationFrame(() => {
16        callback(...args);
17        ticking = false;
18      });
19    };
20  }
21
22  function initCarousel(carrossel) {
23    if (!carrossel || carrossel.hasAttribute(initializedAttr)) {
24      return;
25    }
26
27    const mobileMedia = window.matchMedia("(max-width: 61.9375em)");
28    const track = carrossel.querySelector(".carrossel__track");
29
30    if (!track || !track.children.length) {
31      return;
32    }
33
34    const controls = carrossel.querySelector(".carrossel__controles");
35    const arrows = controls?.querySelectorAll(".carrossel__seta") || [];
36    const allowMobileAuto = carrossel.classList.contains(
37      "depoimentos__carrossel-wrapper"
38    );
39    let metrics = null;
40    let autoScrollId = null;
41
42    const getMetrics = () => {
43      const firstChild = track.children[0];
44
45      if (!firstChild) {
46        return null;
47      }
48
49      const styles = window.getComputedStyle(track);
50      const gap = Number.parseFloat(styles.gap) || 0;
51      const paddingLeft = Number.parseFloat(styles.paddingLeft) || 0;
52      const paddingRight = Number.parseFloat(styles.paddingRight) || 0;
53      const childWidth = firstChild.getBoundingClientRect().width;
54      const trackWidth = track.getBoundingClientRect().width;
55      const trackFullWidth =
56        track.children.length * childWidth +
57        (track.children.length - 1) * gap +
58        paddingLeft +
59        paddingRight;
60
61      return {
62        childWidth,
63        gap,
64        paddingLeft,
65        trackWidth,
66        trackFullWidth,
67      };
68    };
69
70    const updateControls = () => {
71      metrics = getMetrics();
72
73      if (!metrics) {
74        return;
75      }
76
77      const scrollLeft = track.scrollLeft;
78      const maxScrollLeft = Math.max(
79        0,
80        metrics.trackFullWidth - metrics.trackWidth - metrics.gap
81      );
82      const canScrollRight = scrollLeft < maxScrollLeft;
83      const canScrollLeft = scrollLeft > metrics.paddingLeft;
84
85      track.classList.toggle("js-scrollable-right", canScrollRight);
86      track.classList.toggle("js-scrollable-left", canScrollLeft);
87      track.toggleAttribute("scrolled", scrollLeft > 0);
88
89      if (!controls || arrows.length < 2) {
90        return;
91      }
92
93      if (mobileMedia.matches && !allowMobileAuto) {
94        controls.classList.add("carrossel__controles--mobile");
95        controls.classList.add("carrossel__controles--semscroll");
96        arrows.forEach((arrow) => {
97          arrow.classList.remove("carrossel__seta--habilitada");
98
99          if ("disabled" in arrow) {
100            arrow.disabled = true;
101          }
102        });
103        return;
104      }
105
106      controls.classList.remove("carrossel__controles--mobile");
107
108      controls.classList.toggle(
109        "carrossel__controles--semscroll",
110        !canScrollLeft && !canScrollRight
111      );
112      arrows[0].classList.toggle("carrossel__seta--habilitada", canScrollLeft);
113      arrows[1].classList.toggle("carrossel__seta--habilitada", canScrollRight);
114
115      if ("disabled" in arrows[0]) {
116        arrows[0].disabled = !canScrollLeft;
117      }
118
119      if ("disabled" in arrows[1]) {
120        arrows[1].disabled = !canScrollRight;
121      }
122    };
123
124    const scrollByAmount = (direction, isUserInteraction) => {
125      if (!metrics) {
126        metrics = getMetrics();
127      }
128
129      if (!metrics) {
130        return;
131      }
132
133      if (isUserInteraction) {
134        track.classList.add("js-user-interaction");
135      }
136
137      track.scrollTo({
138        left: Math.max(0, track.scrollLeft + metrics.childWidth * direction),
139        behavior: "smooth",
140      });
141    };
142
143    const stopAutoScroll = () => {
144      if (autoScrollId) {
145        window.clearInterval(autoScrollId);
146        autoScrollId = null;
147      }
148    };
149
150    const isVisibleEnough = (minimumRatio = 0.2) => {
151      const rect = carrossel.getBoundingClientRect();
152      const viewportHeight =
153        window.innerHeight || document.documentElement.clientHeight;
154
155      if (rect.height <= 0 || viewportHeight <= 0) {
156        return false;
157      }
158
159      const visibleHeight =
160        Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0);
161
162      return visibleHeight / rect.height >= minimumRatio;
163    };
164
165    const startAutoScroll = () => {
166      if (
167        !carrossel.classList.contains("carrossel--automatico") ||
168        autoScrollId ||
169        (mobileMedia.matches && !allowMobileAuto)
170      ) {
171        return;
172      }
173
174      autoScrollId = window.setInterval(() => {
175        if (track.classList.contains("js-user-interaction")) {
176          stopAutoScroll();
177          return;
178        }
179
180        updateControls();
181
182        if (track.classList.contains("js-scrollable-right")) {
183          scrollByAmount(1, false);
184        } else {
185          track.scrollTo({
186            left: 0,
187            behavior: "smooth",
188          });
189        }
190      }, automaticInterval);
191    };
192
193    track.addEventListener("scroll", rafThrottle(updateControls), {
194      passive: true,
195    });
196    track.addEventListener(
197      "pointerdown",
198      () => {
199        track.classList.add("js-user-interaction");
200        stopAutoScroll();
201      },
202      { passive: true }
203    );
204
205    if (arrows.length >= 2) {
206      arrows[0].addEventListener("click", () => scrollByAmount(-1, true));
207      arrows[1].addEventListener("click", () => scrollByAmount(1, true));
208    }
209
210    window.addEventListener(
211      "resize",
212      rafThrottle(() => {
213        if (mobileMedia.matches && !allowMobileAuto) {
214          stopAutoScroll();
215        }
216
217        updateControls();
218      }),
219      {
220        passive: true,
221      }
222    );
223
224    if (
225      "IntersectionObserver" in window &&
226      carrossel.classList.contains("carrossel--automatico")
227    ) {
228      const visibilityObserver = new IntersectionObserver(
229        (entries) => {
230          entries.forEach((entry) => {
231            if (entry.isIntersecting) {
232              startAutoScroll();
233            } else {
234              stopAutoScroll();
235            }
236          });
237        },
238        {
239          threshold: 0.2,
240        }
241      );
242
243      visibilityObserver.observe(carrossel);
244
245      if (isVisibleEnough()) {
246        startAutoScroll();
247      }
248    } else {
249      startAutoScroll();
250    }
251
252    updateControls();
253    carrossel.setAttribute(initializedAttr, "true");
254  }
255
256  function initVisibleCarousels() {
257    const carousels = document.querySelectorAll(".carrossel");
258
259    if (!carousels.length) {
260      return;
261    }
262
263    const priorityCarousels = Array.from(carousels).filter((carrossel) => {
264      if (carrossel.hasAttribute(initializedAttr)) {
265        return false;
266      }
267
268      // Na home, o carrossel de Cursos e Formacoes precisa nascer pronto
269      // visualmente, mesmo antes de entrar no viewport.
270      return Boolean(
271        document.body.classList.contains("pg-home") &&
272          carrossel.closest(".cursos")
273      );
274    });
275
276    priorityCarousels.forEach(initCarousel);
277
278    if (!("IntersectionObserver" in window)) {
279      carousels.forEach(initCarousel);
280      return;
281    }
282
283    const observer = new IntersectionObserver(
284      (entries) => {
285        entries.forEach((entry) => {
286          if (!entry.isIntersecting) {
287            return;
288          }
289
290          initCarousel(entry.target);
291          observer.unobserve(entry.target);
292        });
293      },
294      {
295        rootMargin: "300px 0px",
296        threshold: 0.01,
297      }
298    );
299
300    carousels.forEach((carrossel) => {
301      if (carrossel.hasAttribute(initializedAttr)) {
302        return;
303      }
304
305      observer.observe(carrossel);
306    });
307  }
308
309  window.checarCarrosselAutomatico = (carrossel) => {
310    if (carrossel instanceof Element) {
311      initCarousel(carrossel);
312      return;
313    }
314
315    initVisibleCarousels();
316  };
317
318  document.addEventListener("carrossel:contentReady", (event) => {
319    initCarousel(event.detail?.carrossel);
320  });
321
322  if (document.readyState === "loading") {
323    document.addEventListener("DOMContentLoaded", initVisibleCarousels, {
324      once: true,
325    });
326  } else {
327    initVisibleCarousels();
328  }
329})();
330
331(function () {
332  const initializedAttr = "data-course-cards-carousel-initialized";
333  const automaticInterval = 3000;
334  const mobileMedia = window.matchMedia
335    ? window.matchMedia("(max-width: 61.9375em)")
336    : { matches: false };
337  const reducedMotionMedia = window.matchMedia
338    ? window.matchMedia("(prefers-reduced-motion: reduce)")
339    : { matches: false };
340
341  function initCourseCardsCarousel(wrapper) {
342    if (!wrapper || wrapper.hasAttribute(initializedAttr)) {
343      return;
344    }
345
346    const track = wrapper.querySelector(".cards-track");
347    const nav = wrapper.querySelector(".carrossel-nav");
348
349    if (!track || !track.children.length) {
350      return;
351    }
352
353    const arrows = nav ? nav.querySelectorAll(".carrossel-nav__btn") : [];
354    const noAuto = wrapper.hasAttribute("data-cards-no-auto");
355    let autoId = null;
356    let userInteracted = false;
357
358    function updateArrows() {
359      if (arrows.length < 2) {
360        return;
361      }
362
363      const max = getMaxScroll();
364      const left = track.scrollLeft;
365      const canPrev = left > 1;
366      const canNext = left < max - 1;
367
368      arrows[0].toggleAttribute("hidden", !canPrev);
369      arrows[1].toggleAttribute("hidden", !canNext);
370    }
371
372    function getStep() {
373      const first = track.children[0];
374      const second = track.children[1];
375
376      if (!first) {
377        return 0;
378      }
379
380      const cardWidth = first.getBoundingClientRect().width;
381      const gap = second
382        ? second.getBoundingClientRect().left - first.getBoundingClientRect().right
383        : Number.parseFloat(window.getComputedStyle(track).gap) || 0;
384
385      return cardWidth + gap;
386    }
387
388    function getMaxScroll() {
389      return track.scrollWidth - track.clientWidth;
390    }
391
392    function scrollByStep(direction) {
393      const step = getStep();
394
395      track.scrollTo({
396        left: track.scrollLeft + step * direction,
397        behavior: "smooth",
398      });
399    }
400
401    function pauseAuto() {
402      if (autoId) {
403        window.clearInterval(autoId);
404        autoId = null;
405      }
406    }
407
408    function stopAuto() {
409      userInteracted = true;
410      pauseAuto();
411    }
412
413    // Com "reduzir movimento" ligado no sistema o carrossel NÃO anda sozinho, e
414    // isso é deliberado: conteúdo que se move sozinho é exatamente o que a
415    // preferência pede para desligar (e a WCAG 2.2.2 exige um jeito de parar
416    // qualquer movimento acima de 5s). Parece "travado" para quem não sabe que
417    // tem a opção ligada — por isso a seta continua visível e o track continua
418    // arrastável: quem quiser ver o resto navega, ninguém fica sem os cards.
419    function startAuto() {
420      if (
421        noAuto ||
422        autoId ||
423        userInteracted ||
424        mobileMedia.matches ||
425        reducedMotionMedia.matches
426      ) {
427        return;
428      }
429
430      autoId = window.setInterval(() => {
431        if (userInteracted) {
432          stopAuto();
433          return;
434        }
435
436        if (track.scrollLeft >= getMaxScroll() - 2) {
437          track.scrollTo({ left: 0, behavior: "smooth" });
438        } else {
439          scrollByStep(1);
440        }
441      }, automaticInterval);
442    }
443
444    if (arrows.length >= 2) {
445      arrows[0].addEventListener("click", () => {
446        stopAuto();
447        scrollByStep(-1);
448      });
449      arrows[1].addEventListener("click", () => {
450        stopAuto();
451        scrollByStep(1);
452      });
453    }
454
455    track.addEventListener("pointerdown", stopAuto, { passive: true });
456    track.addEventListener("wheel", stopAuto, { passive: true });
457    track.addEventListener("scroll", updateArrows, { passive: true });
458    window.addEventListener(
459      "resize",
460      () => {
461        if (mobileMedia.matches) {
462          pauseAuto();
463        }
464        updateArrows();
465      },
466      { passive: true }
467    );
468
469    updateArrows();
470
471    // O primeiro updateArrows() roda no DOMContentLoaded, e o CSS que dá largura
472    // aos cards chega DEPOIS (media="print" + onload). Nesse instante o track ainda
473    // não transborda, updateArrows conclui "não há para onde rolar" e esconde as
474    // DUAS setas — em produção o carrossel nascia sem seta nenhuma até o primeiro
475    // scroll. No DDEV não aparecia: sem CDN, o CSS chega antes de o JS medir.
476    // Remedir quando o layout assentar resolve nos dois casos.
477    window.addEventListener("load", updateArrows, { passive: true, once: true });
478
479    if ("ResizeObserver" in window) {
480      const layoutObserver = new ResizeObserver(() => updateArrows());
481      layoutObserver.observe(track);
482      if (track.children[0]) {
483        layoutObserver.observe(track.children[0]);
484      }
485    }
486
487    if ("IntersectionObserver" in window) {
488      const observer = new IntersectionObserver(
489        (entries) => {
490          entries.forEach((entry) => {
491            if (entry.isIntersecting) {
492              startAuto();
493            } else {
494              pauseAuto();
495            }
496          });
497        },
498        { threshold: 0.2 }
499      );
500
501      observer.observe(wrapper);
502    } else {
503      startAuto();
504    }
505
506    // A preferência do sistema muda com a página aberta (o Windows desliga
507    // animação sozinho ao entrar em economia de bateria, e o usuário pode
508    // trocar no painel). Sem ouvir a mudança, o valor lido no boot valeria
509    // para sempre — e ligar de volta só surtiria efeito no próximo reload.
510    if (typeof reducedMotionMedia.addEventListener === "function") {
511      reducedMotionMedia.addEventListener("change", (event) => {
512        if (event.matches) {
513          pauseAuto();
514          return;
515        }
516
517        const rect = wrapper.getBoundingClientRect();
518        const viewportHeight =
519          window.innerHeight || document.documentElement.clientHeight;
520
521        if (rect.bottom > 0 && rect.top < viewportHeight) {
522          startAuto();
523        }
524      });
525    }
526
527    wrapper.setAttribute(initializedAttr, "true");
528  }
529
530  function initCourseCardsCarousels() {
531    document
532      .querySelectorAll(".carrossel-wrapper")
533      .forEach(initCourseCardsCarousel);
534  }
535
536  if (document.readyState === "loading") {
537    document.addEventListener("DOMContentLoaded", initCourseCardsCarousels, {
538      once: true,
539    });
540  } else {
541    initCourseCardsCarousels();
542  }
543})();

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.