feat: contactos editáveis, equipa com título/aptidões, mensalidades e reviews Google
- Contactos: nova página no backoffice (fitme3070_info) e 964 201 137 como telefone principal; o telefone secundário só aparece quando existe. - Equipa: título, aptidões/funções e formações; o cartão mostra foto, nome e título e abre um cartão horizontal de detalhe (hover no desktop, toque no telemóvel) sem mexer na grelha. - Mensalidades: cartões editáveis com um destaque, modal de inscrição que guarda o pedido (RGPD + estado + campos reservados para o CRM) e avisa o ginásio por e-mail; lista de pedidos com estados, notas e CSV. - Reviews Google via Places API (New), filtradas por classificação mínima e em cache configurável; página de configuração com pesquisa do Place ID. - Assets com ?v=<mtime>: site/assets é servido com cache imutável de 1 ano. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8a494d59a5
commit
f95c9ebc52
27 files changed
+2063
-16
No files matched your search
@@ -222,3 +222,81 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
var t;
|
||||
window.addEventListener('resize', function () { clearTimeout(t); t = setTimeout(sync, 150); }, { passive: true });
|
||||
});
|
||||
|
||||
|
||||
/* ── Equipa: horizontal detail card ─────────────────────────────────────────
|
||||
Desktop (fine pointer) opens it on hover purely in CSS; this handles tap /
|
||||
click / keyboard (.is-open, one at a time; Esc, the × and outside clicks
|
||||
close) and nudges the absolutely-positioned card sideways (--td-shift) so an
|
||||
edge column's card stays inside the viewport. On small / touch screens the
|
||||
CSS turns it into a centred fixed card, so no shift is applied there. */
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var items = document.querySelectorAll('.trainer-item.has-detail');
|
||||
if (!items.length) return;
|
||||
var floating = window.matchMedia('(max-width: 767px), (hover: none)');
|
||||
|
||||
function place(item) {
|
||||
var card = item.querySelector('.trainer-detail');
|
||||
if (!card) return;
|
||||
card.style.setProperty('--td-shift', '0px');
|
||||
if (floating.matches) return;
|
||||
var r = card.getBoundingClientRect();
|
||||
var itemR = item.getBoundingClientRect();
|
||||
// Where the card would sit centred on the item (ignoring its current transform).
|
||||
var left = itemR.left + itemR.width / 2 - r.width / 2;
|
||||
var right = left + r.width;
|
||||
var gutter = 16, vw = document.documentElement.clientWidth, shift = 0;
|
||||
if (left < gutter) shift = gutter - left;
|
||||
else if (right > vw - gutter) shift = (vw - gutter) - right;
|
||||
card.style.setProperty('--td-shift', Math.round(shift) + 'px');
|
||||
}
|
||||
var section = document.getElementById('equipa');
|
||||
function setOpen(item, open) {
|
||||
item.classList.toggle('is-open', open);
|
||||
item.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
if (section) section.classList.toggle('has-open-detail', !!section.querySelector('.trainer-item.is-open'));
|
||||
}
|
||||
function closeAll(except) {
|
||||
items.forEach(function (it) { if (it !== except) setOpen(it, false); });
|
||||
}
|
||||
|
||||
items.forEach(function (item) {
|
||||
item.addEventListener('mouseenter', function () { place(item); });
|
||||
item.addEventListener('focus', function () { place(item); });
|
||||
item.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
if (e.target.closest('.td-close')) { setOpen(item, false); return; }
|
||||
// Clicks inside an open card (e.g. scrolling / selecting text) keep it open.
|
||||
if (e.target.closest('.trainer-detail') && item.classList.contains('is-open')) return;
|
||||
// Tapping the dimmed backdrop (the item's ::after) closes it.
|
||||
if (floating.matches && item.classList.contains('is-open') && !e.target.closest('.trainer-detail')) {
|
||||
setOpen(item, false); return;
|
||||
}
|
||||
closeAll(item);
|
||||
place(item);
|
||||
setOpen(item, !item.classList.contains('is-open'));
|
||||
});
|
||||
item.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') {
|
||||
if (e.target !== item) return;
|
||||
e.preventDefault();
|
||||
closeAll(item); place(item);
|
||||
setOpen(item, !item.classList.contains('is-open'));
|
||||
} else if (e.key === 'Escape') {
|
||||
setOpen(item, false);
|
||||
item.blur();
|
||||
}
|
||||
});
|
||||
});
|
||||
document.addEventListener('click', function () { closeAll(null); });
|
||||
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeAll(null); });
|
||||
});
|
||||
|
||||
/* ── Mensalidades: pre-select the clicked card in the sign-up modal ───────── */
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var select = document.querySelector('#joinModal select[name="membership_id"]');
|
||||
if (!select) return;
|
||||
document.querySelectorAll('[data-target="#joinModal"][data-membership]').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () { select.value = btn.getAttribute('data-membership'); });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user