426 lines
15 KiB
JavaScript
426 lines
15 KiB
JavaScript
/**
|
|
* Main JavaScript
|
|
* @package MartinsEVieira
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
const body = document.body;
|
|
|
|
// ============= UTILITÁRIOS =============
|
|
|
|
/**
|
|
* Debounce function para otimizar performance
|
|
*/
|
|
function debounce(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Cache de elementos do DOM
|
|
*/
|
|
const elements = {
|
|
cookieBanner: document.getElementById('cookie-banner'),
|
|
cookieAccept: document.getElementById('cookie-accept'),
|
|
cookieDecline: document.getElementById('cookie-decline'),
|
|
themeToggle: document.getElementById('theme-toggle'),
|
|
themeToggleMobile: document.getElementById('theme-toggle-mobile'),
|
|
mobileMenuToggle: document.getElementById('mobile-menu-toggle'),
|
|
mobileMenuClose: document.getElementById('mobile-menu-close'),
|
|
mobileNav: document.getElementById('mobile-nav'),
|
|
mobileMenuOverlay: document.getElementById('mobile-menu-overlay'),
|
|
header: document.querySelector('.site-header'),
|
|
loadMoreBtn: document.getElementById('load-more-posts'),
|
|
postsContainer: document.getElementById('posts-container'),
|
|
loadingSpinner: document.getElementById('loading-spinner')
|
|
};
|
|
|
|
// ============= BANNER DE COOKIES (LGPD) =============
|
|
|
|
function initCookieBanner() {
|
|
if (!elements.cookieBanner) return;
|
|
|
|
const cookieConsent = localStorage.getItem('cookieConsent');
|
|
|
|
if (!cookieConsent) {
|
|
setTimeout(() => {
|
|
elements.cookieBanner.classList.add('show');
|
|
}, 1000);
|
|
}
|
|
|
|
function handleCookieConsent(accepted) {
|
|
localStorage.setItem('cookieConsent', accepted ? 'accepted' : 'declined');
|
|
localStorage.setItem('cookieConsentDate', new Date().toISOString());
|
|
elements.cookieBanner.classList.remove('show');
|
|
}
|
|
|
|
if (elements.cookieAccept) {
|
|
elements.cookieAccept.addEventListener('click', () => handleCookieConsent(true));
|
|
}
|
|
|
|
if (elements.cookieDecline) {
|
|
elements.cookieDecline.addEventListener('click', () => handleCookieConsent(false));
|
|
}
|
|
}
|
|
|
|
// ============= MODO ESCURO =============
|
|
|
|
function initDarkMode() {
|
|
const toggles = [elements.themeToggle, elements.themeToggleMobile].filter(Boolean);
|
|
if (toggles.length === 0) return;
|
|
|
|
const currentTheme = localStorage.getItem('theme');
|
|
|
|
// Aplicar tema inicial
|
|
if (currentTheme === 'light') {
|
|
body.classList.remove('dark-mode');
|
|
toggles.forEach(toggle => {
|
|
const icon = toggle.querySelector('i');
|
|
icon?.classList.replace('fa-sun', 'fa-moon');
|
|
});
|
|
} else {
|
|
body.classList.add('dark-mode');
|
|
toggles.forEach(toggle => {
|
|
const icon = toggle.querySelector('i');
|
|
icon?.classList.replace('fa-moon', 'fa-sun');
|
|
});
|
|
if (!currentTheme) {
|
|
localStorage.setItem('theme', 'dark');
|
|
}
|
|
}
|
|
|
|
// Toggle modo escuro
|
|
function toggleDarkMode(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
body.classList.toggle('dark-mode');
|
|
const isDark = body.classList.contains('dark-mode');
|
|
|
|
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
|
|
toggles.forEach(toggle => {
|
|
const icon = toggle.querySelector('i');
|
|
if (icon) {
|
|
icon.classList.replace(isDark ? 'fa-moon' : 'fa-sun', isDark ? 'fa-sun' : 'fa-moon');
|
|
}
|
|
});
|
|
}
|
|
|
|
toggles.forEach(toggle => {
|
|
toggle.addEventListener('click', toggleDarkMode);
|
|
});
|
|
}
|
|
|
|
// ============= MENU MOBILE =============
|
|
|
|
function initMobileMenu() {
|
|
if (!elements.mobileMenuToggle || !elements.mobileNav || !elements.mobileMenuOverlay) {
|
|
return;
|
|
}
|
|
|
|
function openMenu() {
|
|
elements.mobileMenuToggle.classList.add('active');
|
|
elements.mobileMenuToggle.setAttribute('aria-expanded', 'true');
|
|
elements.mobileNav.classList.add('active');
|
|
elements.mobileMenuOverlay.classList.add('active');
|
|
body.classList.add('menu-open');
|
|
}
|
|
|
|
function closeMenu() {
|
|
elements.mobileMenuToggle.classList.remove('active');
|
|
elements.mobileMenuToggle.setAttribute('aria-expanded', 'false');
|
|
elements.mobileNav.classList.remove('active');
|
|
elements.mobileMenuOverlay.classList.remove('active');
|
|
body.classList.remove('menu-open');
|
|
}
|
|
|
|
function toggleMenu() {
|
|
if (elements.mobileNav.classList.contains('active')) {
|
|
closeMenu();
|
|
} else {
|
|
openMenu();
|
|
}
|
|
}
|
|
|
|
// Abrir/fechar menu ao clicar no botão hamburguer
|
|
elements.mobileMenuToggle.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
toggleMenu();
|
|
});
|
|
|
|
// Fechar menu ao clicar no botão X
|
|
if (elements.mobileMenuClose) {
|
|
elements.mobileMenuClose.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
closeMenu();
|
|
});
|
|
}
|
|
|
|
// Fechar menu ao clicar no overlay
|
|
elements.mobileMenuOverlay.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
closeMenu();
|
|
});
|
|
|
|
// Fechar menu ao clicar em links
|
|
const mobileLinks = elements.mobileNav.querySelectorAll('a');
|
|
mobileLinks.forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
// Não previne o comportamento padrão para permitir navegação
|
|
// Apenas fecha o menu
|
|
closeMenu();
|
|
});
|
|
});
|
|
|
|
// Fechar menu ao clicar no botão de tema mobile
|
|
if (elements.themeToggleMobile) {
|
|
elements.themeToggleMobile.addEventListener('click', (e) => {
|
|
// Não fecha imediatamente, só após trocar tema
|
|
// O evento de tema já está tratado em initDarkMode
|
|
});
|
|
}
|
|
|
|
// Fechar menu com tecla ESC
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && elements.mobileNav.classList.contains('active')) {
|
|
closeMenu();
|
|
}
|
|
});
|
|
}
|
|
// ============= SMOOTH SCROLL =============
|
|
|
|
function initSmoothScroll() {
|
|
document.addEventListener('click', (e) => {
|
|
const anchor = e.target.closest('a[href*="#"]');
|
|
if (!anchor) return;
|
|
|
|
const href = anchor.getAttribute('href');
|
|
const hashIndex = href.indexOf('#');
|
|
|
|
if (hashIndex === -1) return;
|
|
|
|
const hash = href.substring(hashIndex);
|
|
const targetId = hash.substring(1);
|
|
const target = document.getElementById(targetId);
|
|
|
|
if (target) {
|
|
e.preventDefault();
|
|
|
|
// Calcular altura do header para ajustar o offset
|
|
const headerHeight = elements.header ? elements.header.offsetHeight : 0;
|
|
const targetPosition = target.getBoundingClientRect().top + window.pageYOffset;
|
|
const offsetPosition = targetPosition - headerHeight - 20; // 20px de margem extra
|
|
|
|
window.scrollTo({
|
|
top: offsetPosition,
|
|
behavior: 'smooth'
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// ============= HEADER SCROLL =============
|
|
|
|
function initHeaderScroll() {
|
|
if (!elements.header) return;
|
|
|
|
let lastScrollY = window.scrollY;
|
|
let ticking = false;
|
|
|
|
const updateHeader = () => {
|
|
const shouldElevate = lastScrollY > 100;
|
|
elements.header.style.boxShadow = shouldElevate
|
|
? '0 4px 20px rgba(0,0,0,0.15)'
|
|
: '0 2px 10px rgba(0,0,0,0.1)';
|
|
ticking = false;
|
|
};
|
|
|
|
const handleScroll = () => {
|
|
lastScrollY = window.scrollY;
|
|
|
|
if (!ticking) {
|
|
window.requestAnimationFrame(updateHeader);
|
|
ticking = true;
|
|
}
|
|
};
|
|
|
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
}
|
|
|
|
// ============= ANIMAÇÃO CARDS =============
|
|
|
|
function initCardAnimations() {
|
|
const observerOptions = {
|
|
threshold: 0.1,
|
|
rootMargin: '0px 0px -50px 0px'
|
|
};
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
// Usar requestAnimationFrame para animação suave
|
|
requestAnimationFrame(() => {
|
|
entry.target.style.opacity = '1';
|
|
entry.target.style.transform = 'translateY(0)';
|
|
});
|
|
// Parar de observar após animar
|
|
observer.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, observerOptions);
|
|
|
|
const cards = document.querySelectorAll('.area-card, .post-card, .post-card-archive');
|
|
cards.forEach(card => {
|
|
card.style.opacity = '0';
|
|
card.style.transform = 'translateY(20px)';
|
|
card.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
|
|
observer.observe(card);
|
|
});
|
|
}
|
|
|
|
// ============= EFEITO RIPPLE =============
|
|
|
|
function initRippleEffect() {
|
|
document.addEventListener('click', (e) => {
|
|
const card = e.target.closest('.area-card');
|
|
if (!card) return;
|
|
|
|
const ripple = document.createElement('span');
|
|
ripple.classList.add('ripple-effect');
|
|
|
|
const rect = card.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
const y = e.clientY - rect.top;
|
|
|
|
ripple.style.left = x + 'px';
|
|
ripple.style.top = y + 'px';
|
|
|
|
card.appendChild(ripple);
|
|
|
|
setTimeout(() => ripple.remove(), 600);
|
|
});
|
|
}
|
|
// ============= CARREGAR MAIS POSTS =============
|
|
|
|
function initLoadMorePosts() {
|
|
if (!elements.loadMoreBtn || !elements.postsContainer) return;
|
|
|
|
elements.loadMoreBtn.addEventListener('click', async function() {
|
|
const currentPage = parseInt(this.getAttribute('data-page'));
|
|
const maxPages = parseInt(this.getAttribute('data-max'));
|
|
const nextPage = currentPage + 1;
|
|
|
|
// Mostrar spinner
|
|
elements.loadMoreBtn.style.display = 'none';
|
|
elements.loadingSpinner.style.display = 'block';
|
|
|
|
const formData = new FormData();
|
|
formData.append('action', 'load_more_posts');
|
|
formData.append('page', nextPage);
|
|
formData.append('nonce', ajaxData.nonce);
|
|
|
|
try {
|
|
const response = await fetch(ajaxData.ajaxurl, {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
const tempDiv = document.createElement('div');
|
|
tempDiv.innerHTML = data.data.posts;
|
|
|
|
const newPosts = tempDiv.querySelectorAll('.post-card');
|
|
newPosts.forEach((post, index) => {
|
|
post.style.opacity = '0';
|
|
post.style.transform = 'translateY(20px)';
|
|
elements.postsContainer.appendChild(post);
|
|
|
|
setTimeout(() => {
|
|
post.style.transition = 'all 0.6s ease';
|
|
post.style.opacity = '1';
|
|
post.style.transform = 'translateY(0)';
|
|
}, index * 100);
|
|
});
|
|
|
|
elements.loadMoreBtn.setAttribute('data-page', nextPage);
|
|
|
|
if (nextPage >= maxPages) {
|
|
elements.loadMoreBtn.style.display = 'none';
|
|
elements.loadingSpinner.style.display = 'none';
|
|
} else {
|
|
elements.loadMoreBtn.style.display = 'inline-flex';
|
|
elements.loadingSpinner.style.display = 'none';
|
|
}
|
|
} else {
|
|
elements.loadMoreBtn.style.display = 'none';
|
|
elements.loadingSpinner.style.display = 'none';
|
|
}
|
|
} catch (error) {
|
|
elements.loadMoreBtn.style.display = 'inline-flex';
|
|
elements.loadingSpinner.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
// ============= INICIALIZAÇÃO =============
|
|
|
|
/**
|
|
* Lazy loading de imagens nativas
|
|
*/
|
|
function initLazyLoading() {
|
|
// Verificar se o navegador suporta lazy loading nativo
|
|
if ('loading' in HTMLImageElement.prototype) {
|
|
const images = document.querySelectorAll('img[loading="lazy"]');
|
|
images.forEach(img => {
|
|
if (img.dataset.src) {
|
|
img.src = img.dataset.src;
|
|
}
|
|
});
|
|
} else {
|
|
// Fallback: usar Intersection Observer
|
|
const imageObserver = new IntersectionObserver((entries, observer) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
const img = entry.target;
|
|
if (img.dataset.src) {
|
|
img.src = img.dataset.src;
|
|
img.removeAttribute('data-src');
|
|
}
|
|
observer.unobserve(img);
|
|
}
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
|
|
imageObserver.observe(img);
|
|
});
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initCookieBanner();
|
|
initDarkMode();
|
|
initMobileMenu();
|
|
initSmoothScroll();
|
|
initHeaderScroll();
|
|
initCardAnimations();
|
|
initRippleEffect();
|
|
initLoadMorePosts();
|
|
initLazyLoading();
|
|
});
|
|
|
|
})(); |