* fix: prevent long strings from overflowing public layout * fix: fall back to default hero copy and tighten hero line-height * fix: skip blank testimonials and guard empty portfolio metadata * feat: add branded 419, 429 and 503 error pages * fix: reset submit state on bfcache restore, swap label while sending, trap mobile menu focus * chore: track hardening task status * fix: restore contact submit state after bfcache * fix: allow contact links to wrap long unbroken strings
109 lines
3.8 KiB
JavaScript
109 lines
3.8 KiB
JavaScript
import './motion.js';
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const menuButton = document.querySelector('[data-menu-button]');
|
|
const navigation = document.querySelector('[data-main-nav]');
|
|
|
|
if (menuButton && navigation) {
|
|
const isDesktop = () => window.matchMedia('(min-width: 768px)').matches;
|
|
const isMenuOpen = () => navigation.classList.contains('is-open');
|
|
|
|
const setOpen = (isOpen, { returnFocus = false } = {}) => {
|
|
navigation.classList.toggle('is-open', isOpen);
|
|
navigation.classList.toggle('hidden', !isOpen && !isDesktop());
|
|
navigation.classList.toggle('flex', isOpen || isDesktop());
|
|
document.body.classList.toggle('menu-open', isOpen);
|
|
menuButton.setAttribute('aria-expanded', String(isOpen));
|
|
menuButton.setAttribute('aria-label', isOpen ? 'Fechar menu' : 'Abrir menu');
|
|
|
|
if (!isOpen && returnFocus) {
|
|
menuButton.focus();
|
|
}
|
|
};
|
|
|
|
menuButton.addEventListener('click', () => {
|
|
const isOpen = menuButton.getAttribute('aria-expanded') !== 'true';
|
|
setOpen(isOpen);
|
|
|
|
if (isOpen) {
|
|
const firstLink = navigation.querySelector('a');
|
|
if (firstLink) {
|
|
firstLink.focus();
|
|
}
|
|
}
|
|
});
|
|
|
|
navigation.querySelectorAll('a').forEach((link) => {
|
|
link.addEventListener('click', () => setOpen(false));
|
|
});
|
|
|
|
navigation.addEventListener('keydown', (event) => {
|
|
if (event.key === 'Escape') {
|
|
setOpen(false, { returnFocus: true });
|
|
|
|
return;
|
|
}
|
|
|
|
if (event.key === 'Tab' && !isDesktop() && isMenuOpen()) {
|
|
const focusables = navigation.querySelectorAll('a[href], button:not([disabled])');
|
|
|
|
if (focusables.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const first = focusables[0];
|
|
const last = focusables[focusables.length - 1];
|
|
|
|
if (event.shiftKey && document.activeElement === first) {
|
|
event.preventDefault();
|
|
last.focus();
|
|
} else if (!event.shiftKey && document.activeElement === last) {
|
|
event.preventDefault();
|
|
first.focus();
|
|
}
|
|
}
|
|
});
|
|
|
|
window.matchMedia('(min-width: 768px)').addEventListener('change', (event) => {
|
|
setOpen(false);
|
|
|
|
if (event.matches) {
|
|
navigation.classList.remove('hidden');
|
|
navigation.classList.add('flex');
|
|
}
|
|
});
|
|
}
|
|
|
|
document.querySelectorAll('form[data-contact-form]').forEach((form) => {
|
|
const submitButton = form.querySelector('[data-submit-button]');
|
|
|
|
const resetSubmitState = () => {
|
|
form.removeAttribute('aria-busy');
|
|
form.removeAttribute('aria-disabled');
|
|
|
|
if (submitButton) {
|
|
submitButton.disabled = false;
|
|
submitButton.textContent = submitButton.dataset.defaultLabel ?? 'Enviar briefing';
|
|
submitButton.removeAttribute('aria-busy');
|
|
}
|
|
};
|
|
|
|
form.addEventListener('submit', () => {
|
|
if (submitButton) {
|
|
submitButton.dataset.defaultLabel ??= submitButton.textContent.trim();
|
|
submitButton.textContent = 'Enviando…';
|
|
submitButton.disabled = true;
|
|
submitButton.setAttribute('aria-busy', 'true');
|
|
}
|
|
|
|
form.setAttribute('aria-busy', 'true');
|
|
});
|
|
|
|
window.addEventListener('pageshow', (event) => {
|
|
if (event.persisted) {
|
|
resetSubmitState();
|
|
}
|
|
});
|
|
});
|
|
});
|