@@ -32,12 +32,24 @@ document.addEventListener("DOMContentLoaded", () => {
3232 const initHeaderScroll = ( ) => {
3333 const header = document . querySelector ( ".site-header" ) ;
3434 if ( ! header ) return ;
35+
36+ let ticking = false ;
3537 const onScroll = ( ) => {
36- if ( window . pageYOffset > 100 ) header . classList . add ( "scrolled" ) ;
37- else header . classList . remove ( "scrolled" ) ;
38+ if ( ! ticking ) {
39+ window . requestAnimationFrame ( ( ) => {
40+ if ( window . pageYOffset > 100 ) {
41+ header . classList . add ( "scrolled" ) ;
42+ } else {
43+ header . classList . remove ( "scrolled" ) ;
44+ }
45+ ticking = false ;
46+ } ) ;
47+ ticking = true ;
48+ }
3849 } ;
50+
3951 onScroll ( ) ;
40- window . addEventListener ( "scroll" , onScroll ) ;
52+ window . addEventListener ( "scroll" , onScroll , { passive : true } ) ;
4153 } ;
4254
4355 // ============= THEME TOGGLE (LIGHT/DARK) =============
@@ -84,27 +96,36 @@ document.addEventListener("DOMContentLoaded", () => {
8496 ".fade-in, [data-anim], .stagger-animation"
8597 ) ;
8698 if ( ! elements . length ) return ;
99+
100+ // Verificar se o usuário prefere animações reduzidas (acessibilidade)
101+ const prefersReducedMotion = window . matchMedia ( "(prefers-reduced-motion: reduce)" ) . matches ;
102+
87103 const observer = new IntersectionObserver (
88104 ( entries , obs ) => {
89105 entries . forEach ( ( entry ) => {
90106 if ( entry . isIntersecting ) {
91- entry . target . classList . add ( "in-view" ) ;
92- if ( entry . target . classList . contains ( "skill-category" ) ) {
93- // dispara animação de barras de habilidade quando categoria estiver visível
94- const bars = entry . target . querySelectorAll ( ".skill-progress" ) ;
95- bars . forEach ( ( bar , i ) => {
96- const progress = bar . getAttribute ( "data-progress" ) || "0%" ;
97- setTimeout (
98- ( ) => bar . style . setProperty ( "--progress" , progress ) ,
99- i * 100
100- ) ;
101- } ) ;
107+ if ( prefersReducedMotion ) {
108+ // Se o usuário prefere movimento reduzido, apenas mostrar sem animação
109+ entry . target . classList . add ( "in-view" ) ;
110+ } else {
111+ entry . target . classList . add ( "in-view" ) ;
112+ if ( entry . target . classList . contains ( "skill-category" ) ) {
113+ // dispara animação de barras de habilidade quando categoria estiver visível
114+ const bars = entry . target . querySelectorAll ( ".skill-progress" ) ;
115+ bars . forEach ( ( bar , i ) => {
116+ const progress = bar . getAttribute ( "data-progress" ) || "0%" ;
117+ setTimeout (
118+ ( ) => bar . style . setProperty ( "--progress" , progress ) ,
119+ i * 100
120+ ) ;
121+ } ) ;
122+ }
102123 }
103124 obs . unobserve ( entry . target ) ;
104125 }
105126 } ) ;
106127 } ,
107- { threshold : 0.15 , rootMargin : "0px 0px -8% 0px" }
128+ { threshold : 0.1 , rootMargin : prefersReducedMotion ? "0px" : "0px 0px -8% 0px" }
108129 ) ;
109130 elements . forEach ( ( el ) => observer . observe ( el ) ) ;
110131 } ;
@@ -269,31 +290,43 @@ document.addEventListener("DOMContentLoaded", () => {
269290 const moreBtn = document . createElement ( 'button' ) ;
270291 moreBtn . className = 'tags-more-btn' ;
271292 moreBtn . type = 'button' ;
293+ moreBtn . setAttribute ( 'aria-label' , `${ i18n [ CURRENT_LANG ] . tags_show || 'Show' } ${ overflow } ${ i18n [ CURRENT_LANG ] . tags_more || 'more' } ` ) ;
272294 moreBtn . innerText = `+${ overflow } ` ;
273295 moreBtn . addEventListener ( 'click' , ( ) => {
274296 const isExpanded = moreBtn . getAttribute ( 'aria-expanded' ) === 'true' ;
275297 if ( ! isExpanded ) {
276298 tags . forEach ( ( t ) => t . classList . remove ( 'hidden-by-js' ) ) ;
277299 moreBtn . setAttribute ( 'aria-expanded' , 'true' ) ;
278300 moreBtn . innerText = i18n [ CURRENT_LANG ] . tags_hide || 'Hide' ;
301+ moreBtn . setAttribute ( 'aria-label' , i18n [ CURRENT_LANG ] . tags_hide || 'Hide' ) ;
279302 } else {
280303 tags . forEach ( ( t , idx ) => {
281304 if ( idx >= showCount ) t . classList . add ( 'hidden-by-js' ) ;
282305 } ) ;
283306 moreBtn . setAttribute ( 'aria-expanded' , 'false' ) ;
284307 moreBtn . innerText = `+${ overflow } ` ;
308+ moreBtn . setAttribute ( 'aria-label' , `${ i18n [ CURRENT_LANG ] . tags_show || 'Show' } ${ overflow } ${ i18n [ CURRENT_LANG ] . tags_more || 'more' } ` ) ;
285309 }
286310 } ) ;
287311 container . appendChild ( moreBtn ) ;
288312 }
289313 } ) ;
290314 } ;
291315
292- let resizeTimer ;
293- window . addEventListener ( 'resize' , ( ) => {
294- clearTimeout ( resizeTimer ) ;
295- resizeTimer = setTimeout ( compute , 150 ) ;
296- } ) ;
316+ // Usar ResizeObserver para melhor performance
317+ if ( window . ResizeObserver ) {
318+ const resizeObserver = new ResizeObserver ( ( ) => {
319+ compute ( ) ;
320+ } ) ;
321+ resizeObserver . observe ( document . body ) ;
322+ } else {
323+ // Fallback para navegadores antigos
324+ let resizeTimer ;
325+ window . addEventListener ( 'resize' , ( ) => {
326+ clearTimeout ( resizeTimer ) ;
327+ resizeTimer = setTimeout ( compute , 150 ) ;
328+ } , { passive : true } ) ;
329+ }
297330 compute ( ) ;
298331 } ;
299332
@@ -349,7 +382,9 @@ document.addEventListener("DOMContentLoaded", () => {
349382 sent : "Enviado!" ,
350383 success : ( name ) => `Obrigado, ${ name } ! Sua mensagem foi enviada com sucesso.` ,
351384 send_error : "Ops! Algo deu errado. Por favor, tente novamente." ,
352- tags_hide : "Ocultar"
385+ tags_hide : "Ocultar" ,
386+ tags_show : "Mostrar" ,
387+ tags_more : "mais"
353388 } ,
354389 en : {
355390 form_fill : "Please fill all fields" ,
@@ -358,7 +393,9 @@ document.addEventListener("DOMContentLoaded", () => {
358393 sent : "Sent!" ,
359394 success : ( name ) => `Thanks, ${ name } ! Your message was sent successfully.` ,
360395 send_error : "Oops! Something went wrong. Please try again." ,
361- tags_hide : "Hide"
396+ tags_hide : "Hide" ,
397+ tags_show : "Show" ,
398+ tags_more : "more"
362399 }
363400 } ;
364401
@@ -406,27 +443,37 @@ document.addEventListener("DOMContentLoaded", () => {
406443 const initActiveSection = ( ) => {
407444 const sections = document . querySelectorAll ( "section[id]" ) ;
408445 const navLinks = document . querySelectorAll ( ".nav-list a" ) ;
446+ if ( ! sections . length || ! navLinks . length ) return ;
409447
448+ let ticking = false ;
410449 const highlightNav = ( ) => {
411- let scrollY = window . pageYOffset ;
412-
413- sections . forEach ( ( section ) => {
414- const sectionHeight = section . offsetHeight ;
415- const sectionTop = section . offsetTop - 100 ;
416- const sectionId = section . getAttribute ( "id" ) ;
417-
418- if ( scrollY > sectionTop && scrollY <= sectionTop + sectionHeight ) {
419- navLinks . forEach ( ( link ) => {
420- link . classList . remove ( "active" ) ;
421- if ( link . getAttribute ( "href" ) === `#${ sectionId } ` ) {
422- link . classList . add ( "active" ) ;
450+ if ( ! ticking ) {
451+ window . requestAnimationFrame ( ( ) => {
452+ let scrollY = window . pageYOffset ;
453+ const offset = window . innerWidth > 768 ? 120 : 80 ;
454+
455+ sections . forEach ( ( section ) => {
456+ const sectionHeight = section . offsetHeight ;
457+ const sectionTop = section . offsetTop - offset ;
458+ const sectionId = section . getAttribute ( "id" ) ;
459+
460+ if ( scrollY > sectionTop && scrollY <= sectionTop + sectionHeight ) {
461+ navLinks . forEach ( ( link ) => {
462+ link . classList . remove ( "active" ) ;
463+ if ( link . getAttribute ( "href" ) === `#${ sectionId } ` ) {
464+ link . classList . add ( "active" ) ;
465+ }
466+ } ) ;
423467 }
424468 } ) ;
425- }
426- } ) ;
469+ ticking = false ;
470+ } ) ;
471+ ticking = true ;
472+ }
427473 } ;
428474
429- window . addEventListener ( "scroll" , highlightNav ) ;
475+ window . addEventListener ( "scroll" , highlightNav , { passive : true } ) ;
476+ highlightNav ( ) ; // Executa uma vez no carregamento
430477 } ;
431478
432479 // ============= TIMELINE ANIMATION =============
@@ -455,56 +502,34 @@ document.addEventListener("DOMContentLoaded", () => {
455502
456503 // ============= SCROLL TO TOP BUTTON =============
457504 const initScrollToTop = ( ) => {
458- const scrollBtn = document . createElement ( "button" ) ;
459- scrollBtn . innerHTML = '<i class="fas fa-arrow-up"></i>' ;
460- scrollBtn . className = "scroll-to-top" ;
461- scrollBtn . style . cssText = `
462- position: fixed;
463- bottom: 30px;
464- right: 30px;
465- width: 50px;
466- height: 50px;
467- background: linear-gradient(135deg, #00d4ff, #7000ff);
468- border: none;
469- border-radius: 50%;
470- color: white;
471- font-size: 1.2rem;
472- cursor: pointer;
473- opacity: 0;
474- pointer-events: none;
475- transition: all 0.3s ease;
476- z-index: 1000;
477- box-shadow: 0 4px 15px rgba(0, 212, 255, 0.3);
478- ` ;
479-
480- document . body . appendChild ( scrollBtn ) ;
481-
482- window . addEventListener ( "scroll" , ( ) => {
483- if ( window . pageYOffset > 500 ) {
484- scrollBtn . style . opacity = "1" ;
485- scrollBtn . style . pointerEvents = "all" ;
486- } else {
487- scrollBtn . style . opacity = "0" ;
488- scrollBtn . style . pointerEvents = "none" ;
505+ // Usar o botão existente no HTML ao invés de criar um novo
506+ const scrollBtn = document . getElementById ( "scrollToTop" ) ;
507+ if ( ! scrollBtn ) return ;
508+
509+ let ticking = false ;
510+ const handleScroll = ( ) => {
511+ if ( ! ticking ) {
512+ window . requestAnimationFrame ( ( ) => {
513+ if ( window . pageYOffset > 500 ) {
514+ scrollBtn . classList . add ( "visible" ) ;
515+ } else {
516+ scrollBtn . classList . remove ( "visible" ) ;
517+ }
518+ ticking = false ;
519+ } ) ;
520+ ticking = true ;
489521 }
490- } ) ;
522+ } ;
523+
524+ window . addEventListener ( "scroll" , handleScroll , { passive : true } ) ;
525+ handleScroll ( ) ; // Verifica estado inicial
491526
492527 scrollBtn . addEventListener ( "click" , ( ) => {
493528 window . scrollTo ( {
494529 top : 0 ,
495530 behavior : "smooth" ,
496531 } ) ;
497532 } ) ;
498-
499- scrollBtn . addEventListener ( "mouseenter" , ( ) => {
500- scrollBtn . style . transform = "translateY(-5px)" ;
501- scrollBtn . style . boxShadow = "0 8px 25px rgba(0, 212, 255, 0.5)" ;
502- } ) ;
503-
504- scrollBtn . addEventListener ( "mouseleave" , ( ) => {
505- scrollBtn . style . transform = "translateY(0)" ;
506- scrollBtn . style . boxShadow = "0 4px 15px rgba(0, 212, 255, 0.3)" ;
507- } ) ;
508533 } ;
509534
510535 // ============= LAZY LOAD IMAGES =============
0 commit comments