- "Ver Horário das aulas" desaparece assim que o acordeão abre (clique no
próprio botão ou o link "Horário" da navbar a abrir por JS) — deixa de
ficar um botão grande por cima da grelha já visível.
- Setas ‹ › junto ao dia em foco também no desktop (vista de 3 dias): só o
dia central as mostra, os laterais continuam sem elas (já são clicáveis
diretamente).
- Deslizar no telemóvel/tablet muda de dia, com o mesmo efeito das setas —
só reage a arrastos maioritariamente horizontais, para não roubar o
scroll vertical da página.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Sidebar do backoffice: Backups/Auditoria/IP bans/Geo/Origem das visitas/
Atualizações deixam de ser um link cada (enumerados à mão) e passam a um
só link "Sistema" para o índice partilhado do platform-tools
(platform.php?tool=index), como a fotografia/design/dance já fazem — uma
ferramenta nova no platform-tools passa a aparecer sozinha, sem link novo
a escrever aqui. Definições, Mensagens e Comunicações continuam com link
próprio (são páginas do fitme, não do router).
- Horário de Aulas (home e CrossFit, mesmo componente partilhado): a grelha
fica fechada por omissão atrás de um botão "Ver Horário das aulas"
(Bootstrap collapse); clicar em "Horário" na navbar (ou abrir a página já
com #horario no URL) abre-a de imediato em vez de só rolar até uma secção
fechada.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
O mapa deixa de ser um cartão comprimido dentro da coluna de Informações
(220px) e passa a ocupar a largura toda numa linha própria, por baixo da
grelha Informações + Formulário (480px, mesma altura do .map de outras
páginas). A coluna de Informações fica só com o texto (Localização,
Telefone, E-mail, Horário de Funcionamento).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Informações e contactos: os ícones fas ao lado do texto saem (desalinhavam
em layout centrado — fa-envelope é mais largo que os outros e o texto ficava
a começar em posições diferentes). Horário de Funcionamento deixa de ser
cartões (dt/dd com barra escura) e passa a texto simples centrado, uma
linha por dia, a condizer com o resto da coluna.
- Textarea "Mensagem" deixa de esticar até à base do mapa (chegava a 763px
de altura, vazio) — volta ao min-height:150px + resize:vertical do CSS.
- CrossFit: removida a secção "A nossa Localização" duplicada, agora que a
página tem a sua própria secção de Contactos com mapa incluído.
- Reviews: "Ver todas no Google" passa a link de texto simples (sublinhado);
"Deixa a tua review" mantém-se o botão em destaque.
- api/health.php, api/hub_ops.php, api/hub_pair.php: stubs de uma linha para
o platform-tools (mesmo padrão da fotografia/design/dance) — preparam a
entrada do fitme3070 na Frota do hub.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Horário de aulas (home e CrossFit, mesmo componente partilhado): removida
a barra de botões Seg/Ter/Qua/Qui/Sex/Sab acima da grelha — o próprio
cabeçalho do dia (já existente) passa a ser sempre visível (antes só em
desktop, 3 dias) e ganha setas ‹ › ao lado do nome do dia para mudar de
dia em ecrãs de telemóvel/tablet. No desktop (3 dias lado a lado) as
setas ficam escondidas — clicar no dia lateral já muda o dia em foco.
CSS/JS morto do botão-filtro removido.
- Informações e contactos extraído para site/includes/contactos.php,
partilhado pela home e pelo CrossFit — a página CrossFit ganha a mesma
secção de contactos (localização, telefone, e-mail, horário de
funcionamento, mapa, formulário) em vez de só linkar de volta à home.
Hero e restantes secções do CrossFit não foram tocados.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- .reviews-section deixa de repetir o fundo metálico escuro da secção de
mensalidades logo a seguir — passa a branca, restaurando a alternância de
fundos ao longo da página. Textos e o botão "Ver todas no Google"
recuperam as cores por omissão (escuras sobre branco); os cartões de
review mantêm o próprio fundo escuro.
- Horário de Funcionamento deixa de ser secção própria e passa a viver
dentro de "Informações" (Contactos), como mais um bloco de info, antes do
mapa.
- Ícones de Localização/Telefone/E-mail alinhados: fa-envelope é mais largo
que fa-map-marker-alt/fa-phone-volume, o que desalinhava o texto ao lado
(4.8px de deslocamento, medido no browser); os ícones ganham uma largura
fixa comum.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- migration_20260925_rgpd_requests.sql: tabela rgpd_requests (email, tipo,
estado, consentimento).
- config/functions_rgpd.php: rgpd_find() (acesso, art.º 15) e rgpd_erase()
(apagamento por anonimização, art.º 17) sobre membership_leads/trial_leads/
event_leads, + CRUD dos pedidos.
- Política de Privacidade: formulário público (POST + CSRF + anti-spam) para
pedir acesso ou apagamento, e o processo escrito (resposta em 30 dias).
- Backoffice: página "Pedidos RGPD" (nav + index) para exportar os dados de um
email (CSV, audit) ou anonimizá-los (POST + CSRF, audit).
O email está desligado, por isso os pedidos ficam guardados (como as leads),
não dependem de SMTP. Texto legal com placeholders — rever com jurista antes
do go-live (como já constava na Política).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
O diretório vendorizado incluía ficheiros de build/fonte que nunca são
carregados em runtime: js/, less/, scss/, metadata/, sprites/, svgs/, os
formatos de fonte não-woff2 (eot/svg/ttf/woff) e 13 folhas CSS redundantes.
Ficam só o que o site carrega de facto: css/all.css + 3 webfonts woff2
(solid/brands/regular). Sem alterar a fonte em si — nenhum ícone muda.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
As regras `.container>.row` e `.contact-form .row` usavam margem negativa de
-15px (herança do Bootstrap 4), mas o container do Bootstrap 5 tem padding de
12px — sobrava 3px de overflow horizontal a 375px (scrollWidth 378 vs 375).
Passaram a -0.75rem (o gutter real do BS5), anulando exatamente o padding.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contraste (auditoria 4.8, WCAG AA):
- Novo token --fitme-accent-ink #667000 (≈5.1:1 sobre #F7F8FA), aplicado aos 7
usos de texto que estavam em --fitme-accent-dark (#A6B600 ≈2.1:1 em fundo claro).
O --fitme-accent-dark mantém-se para fundos de botões em hover/active (onde tem
texto escuro por cima — escurecê-lo quebraria esses estados).
- .section-eyebrow passa a usar o ink green nas secções claras (oferta, galeria,
modalidades, horário, workhours); as secções escuras mantêm o verde vivo.
Fontes (CSS "Google Fonts ainda carregado do CDN"):
- Roboto (variável, latin, 43 KB woff2) em site/assets/fonts/roboto/.
- @font-face 100-900 em style.css; removidos os <link> para fonts.googleapis.com
em include.php e gym_chooser.php. Ficam apenas os pedidos à API do Places
(legítimos, server-side).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- assets/vendor/bootstrap/ (cópia BS 4.3.1) já sem referências desde a
migração para 5.3.3 (auditoria 5.1); removida do repositório.
- style.css: cabeçalho corrigido (Bootstrap 4.3.1 → 5.3.3).
- Breakpoints inventados alinhados à grelha (auditoria 4.1): 479px e os dois
339px passaram a max-width: 575px (o tier xs do Bootstrap). Os cartões de
detalhe (modalidade/equipa) passam a coluna em todos os telefones ≤575px.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
O logo tem style="width: 150px" sem height desde sempre; a auditoria
4.7 acrescentou width="756" height="252" (lidos do viewBox do SVG)
sem adicionar height:auto ao inline style. Sem height:auto, o browser
não escala a altura proporcionalmente ao width CSS — ficou fixa nos
252px do atributo, esticando a barra toda. Confirmado ao vivo:
getComputedStyle(img).height passou de "252px" para o valor
proporcional depois desta correção.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6 seletores tinham font-size a fazer step-function em vez de escalar:
.hero-text h1 (4 breakpoints), .section-title h2 (3), .page-top-section
h1, .icon-box-item h3/h4 e os dois .schedule_box>div (2 cada). Cada par/
trio de valores por media query passa a um único clamp() na regra base,
com a interpolação (vw) calculada para bater certo com o valor antigo
nas larguras 375/768/1400px+ testadas.
Ficam por mexer (não são apenas escala de tipo): .rounded_frame,
.hero-text .site-btn, .site-btn @479px e .schedule_frame (o font-size
muda junto com padding/width/min-width, é redimensionamento de layout,
não só tipografia); .footer-menu li a (idem, padding muda junto — alvos
de toque da auditoria 4.4); .contact-form/.newsletter-form input (o
valor sobe para 1rem no mobile, não desce — evita o zoom automático do
iOS ao focar); .md-card h3 (faz parte da troca de layout do cartão para
modal fixo, não é só escala); .schedule_box a (o valor de mobile já era
igual ao base, 11px, não havia degrau nenhum).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Auditoria: "Site público em Bootstrap 4.3.1 (EOL); backoffice em
5.3.3" — o backoffice já usa a 5.3.3 vendorizada (assets/vendor/bootstrap5/),
o site público passa a usar a mesma cópia em vez da 4.3.1.
Levantamento antes de mexer: a folha de estilo própria (style.css)
quase não usa classes utilitárias cruas do Bootstrap (grep confirma
zero ml-/mr-/pl-/pr-/float-left/float-right/custom-select/
custom-control/badge-/sr-only/font-weight-/text-left/text-right —
tudo classes próprias como .site-btn). O que precisava de mudar:
- CSS/JS: assets/vendor/bootstrap/ -> assets/vendor/bootstrap5/
(bundle, inclui Popper).
- Os 7 atributos data-toggle/data-target/data-dismiss (BS4) ->
data-bs-toggle/data-bs-target/data-bs-dismiss (BS5): toggle da nav
mobile, os dois modais (aula experimental, inscrição), o botão de
fechar do alerta. O seletor em main.js que lê data-target também
mudou para não parar de encontrar os botões.
- .close -> .btn-close (BS5 renomeou o componente; markup sem o
× interno, que o BS5 desenha via CSS).
- .form-row -> .row (BS4 tinha form-row com gutters menores; BS5 não
tem, style.css não tinha overrides para form-row, confirmado).
- O apanhado maior: dois <script> inline chamavam
jQuery('#trialModal').modal('show') ao estilo de plugin jQuery do
BS4 (para reabrir o modal depois de um erro de submissão) — o BS5
não regista plugins jQuery nenhuns, isto ficava morto em silêncio.
Trocado por new bootstrap.Modal(...).show(), a API JS nativa do
BS5. grep confirma zero outras chamadas .modal()/.collapse()/etc.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
125 imagens na home, 0 com srcset e 122 sem width/height — o browser
só sabia o tamanho depois de descarregar, o que causa layout shift.
Uploads são redimensionados a um limite de lado maior (functions_images.php),
não a uma dimensão fixa (fotos de equipa vão de 389x512 a 512x512, ícones
de oferta de 128x128 a 256x256), por isso as dimensões não dão para
hardcode: site_img_dims_attr() em functions_site.php lê o ficheiro real
com getimagesize() por pedido, tal como o gallery.php já fazia, e
devolve o par width="" height="" ou "" se o ficheiro não existir. SVG
não passa por getimagesize() (sem decoder embutido) — svg_img_dims_attr()
lê os atributos width/height (ícones fitme-*.svg, 48x48 fixo) ou cai
para o viewBox (logos do navbar, só têm viewBox).
Aplicado a: ícones da oferta, fotos de equipa (card + painel de detalhe),
ícone de modalidade em modalidade_icon_html() e no horário (index.php e
includes/horario.php), logo do navbar.
srcset ficou de fora: as imagens já vêm coartadas para pouco mais do que
o maior tamanho a que aparecem (fotos de equipa a 512px cabidas numa caixa
de no máx. 200px CSS com object-fit:cover; ícones a 128-256px mostrados a
44px) — é a folga normal para ecrãs retina, não desperdício a resolver
com múltiplas resoluções. E o layout é sempre CSS-driven (width/height:
100% + object-fit), por isso um srcset não mudaria o que é descarregado
por variação de viewport, só pouparia bytes nalguns casos — gerar 2-3
tamanhos por upload via GD seria trabalho a mais para o ganho real neste
site. Fica só o fix de dimensões, que é o que resolve o CLS que a
auditoria mediu.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrige os controlos interativos (não o texto denso em grelha/rodapé,
que fica para outra passagem): .navbar-toggler (40->44 de altura),
.md-close/.td-close (26x28 -> 44x44, box explícita em vez de
dimensionar pelo glifo), .schedule-dl-btn (padding compensado por
margin negativa — o texto sublinhado não engorda visualmente),
.sdf-btn (43->44, min-height), ícones sociais do rodapé (min 44x44).
Medido com getBoundingClientRect() no site ao vivo a 375px antes e
depois de aplicar.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
site/assets/js/jquery-3.7.1.min.js e assets/vendor/jquery/jquery-3.7.1.min.js
eram byte-idênticas (md5 igual). O site público passa a carregar a
mesma cópia que o backoffice já usa (assets/ já é servido em
.htaccess); a duplicada sai do git.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
As 5 transições que disparavam layout (não paint) passam a usar
propriedades compositáveis, sem mudar o efeito visual:
- .skip-link: top -100px→8px vira transform: translateY(-108px→0)
- .nav-link:after (sublinhado): width 0→100% vira transform: scaleX(0→1)
(width fica estático a 100%, position:absolute não afeta o layout à
volta mesmo com a largura sempre cheia)
- .crossfit-nav-link, .icon-box-item .ib-icon, .sdf-btn: transition:
all troca por uma lista explícita das propriedades que realmente
mudam no :hover (background-color/color/border-color/transform) —
o "all" também escutava mudanças de layout no resize por nada.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Modalidades: reduce gap between cards
- Horário: drop the day-title above the grid in single-day mode (the active
day-filter button already names it); restyle "Guardar horário" as a plain
link and drop the "(JPG)" suffix
- Mensalidades: give "Escolhe o teu plano" the same dark hammered-metal
background as the reviews/footer sections instead of the plain light one
Fecha os itens 3.2.2, 3.3.1, 4.6, 5.2, 5.6, 5.7 e 7.4 do relatório de
auditoria de 2026-09-24: escapa saídas de site_img_url()/canonical,
valida identificadores de coluna em db_info_save(), troca 100vh por
100dvh, remove a última cor solta, encurta a transição para ~150ms,
acrescenta decoding=async/fetchpriority=high nas imagens da home, e
documenta as versões vendorizadas em ASSETS.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Política de privacidade reescrita: tabela de dados/finalidades/fundamento
(inclui os pedidos de aula, inscrição e eventos com IP), prazos concretos,
subcontratantes, Google Maps e reviews, cookies. Termos e condições novos
(/home/termos), no rodapé e no sitemap. Campos [ENTRE PARÊNTESES RETOS]
e revisão por jurista pendentes antes de publicar no domínio real.
- Google Maps só carrega ao carregar em «Mostrar mapa» (map_click_to_load);
as fotos dos autores das reviews são guardadas pelo servidor em
uploads/reviews/ e servidas localmente (review_avatar_url).
- Retenção que a política declara, aplicada pelo cron (04:00): leads com
mais de 12 meses → lixeira; blocked_submissions e outbox de segurança
apagados aos 90 dias (trash_retention_sweep, auditado).
- Backoffice e login com tema claro/escuro/automático: data-bs-theme
aplicado no <head> antes do CSS (sem flash), escolha em localStorage,
segue o sistema em tempo real, color-scheme e theme-color para os dois.
Cores próprias passaram a variáveis do Bootstrap. lang="pt-PT".
Testado numa cópia da BD de produção: 72/72 verificações.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pedido de aula experimental passa a ficar em trial_leads (por ginásio)
ANTES do email: sem SMTP o visitante já não vê «erro» e o pedido não se
perde. Gestão → Aulas experimentais: estados, notas, CSV (auditado),
apagar para a lixeira; aviso «sem email» quando o aviso não saiu.
- Aula experimental, inscrição e eventos usam o anti-spam do platform-tools
(honeypot, carimbo assinado com tempo mínimo, prova de JS, limite por IP,
pontuação de conteúdo) e o bloqueio de IP, em vez de honeypots locais.
Recusas ficam em blocked_submissions; resposta igual à de sucesso.
Falha aberto se o módulo rebentar.
- Checkbox de consentimento da aula experimental passa a ser guardada.
- Audit das escritas de conteúdo: criar/editar equipa, modalidades,
eventos, mensalidades, pedidos de inscrição, galerias, horário (incl.
arrastar no calendário), horas, contactos e ginásios, com antes/depois
campo a campo (audit_row/audit_change). Exportação CSV de inscrições
auditada.
- .table-responsive em todas as tabelas do backoffice.
- Migração: database/migration_20260926_trial_leads.sql (só cria tabelas).
Testado numa cópia da BD de produção: 63/63 verificações HTTP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Estrutura multi-ginásio: as mesmas secções do site, conteúdo próprio por
ginásio, e cada um numa pasta da URL com o código postal da zona.
- migration_20260926_gyms: tabela gyms (pasta, nome, localidade, publicado,
principal, secções visíveis, título/imagem do header, SEO, galeria, Place ID,
página CrossFit) com o FitMe 3070 como ginásio 1 em /3070/; gym_id em team,
modalidades (o horário segue a modalidade), work_hours, memberships,
membership_leads e website_offer (tudo o existente → ginásio 1); events com
gym_id NULL = todos; fitme3070_info passa a uma linha por ginásio (id = gym).
- Site: /<pasta>/, /<pasta>/crossfit, /<pasta>/agendar|inscricao. Ginásio por
publicar → 404 para o público, pré-visualização (com faixa) para a equipa.
/ e /home → 301 para o ginásio principal (a manter a query); com mais de um
ginásio publicado, a raiz passa a uma página para escolher. Secções
desligadas não aparecem nem no menu; menu, canonical, JSON-LD, sitemap,
formulários, e-mails e reviews (Place ID e cache) são do ginásio.
- Backoffice: Gestão → Ginásios (criar/editar/apagar, secções, e «começar
com o conteúdo de» outro ginásio — cópias independentes) e o seletor
«A editar» no topo; equipa, modalidades, horários, horário de
funcionamento, mensalidades, pedidos, contactos e reviews trabalham sobre o
ginásio escolhido, e um registo de outro ginásio não abre.
- Modalidades passam para logo a seguir à galeria das instalações.
- Galeria sem cantos arredondados (raio da marca).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- assets/js/password-toggle.js, carregado nos dois layouts: liga os botões
existentes e cria um para cada campo de password que não o tenha (eram 3
em 12). aria-pressed, aria-label que muda, fora da ordem de tabulação, e
volta a ocultar ao submeter.
- Removidas 23 bibliotecas/folhas sem nenhuma referência (193 ficheiros),
incluindo o jQuery 3.2.1 (CVE-2020-11022/11023) que se servia por URL, o
Splide (a galeria passou a masonry própria em d70f13a), owl, mixitup,
masonry, magnific-popup, slicknav, jquery.lazy, fullpage, TweenMax,
circle-progress, progressbar, o template polo e cópias antigas do
Bootstrap/FontAwesome em site/assets.
- .table-responsive nas listagens de eventos e galerias.
- .gitignore: sem os 5 padrões herdados do fotografia; *.sql/*.dump
ignorados (exceto migrações e o esquema); .trash/ das imagens; nota sobre
vendor/ versionado de propósito. database/andmore_fitme3070_dev.sql passa a
database/schema.sql (só estrutura, 0 INSERT).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Correções da auditoria de 24/09 (audits/fitme3070-auditoria-2026-09-24.html).
Lixeira
- Tudo o que é importante passa a soft delete (deleted_at + deleted_by):
equipa, modalidades, eventos, mensalidades, galerias, imagens, horário,
horas de funcionamento, inscrições em eventos e pedidos de inscrição.
- config/functions_trash.php: um registo por tipo; apagar, restaurar, listar
e eliminar leem todos dele. Eliminar de vez só com 30 dias na lixeira —
a condição está no WHERE do DELETE, um POST forjado não apaga nada.
- Gestão → Lixeira (views/admin/trash/index.php): lista única com quem e
quando, restauro a qualquer momento.
- Imagens de galeria: o site lista a pasta, por isso o ficheiro passa para
.trash/ (negado pelo .htaccess) e volta no restauro.
- As listagens e o site público filtram deleted_at (horário também filtra
modalidades apagadas). Migração: database/migration_20260925_trash.sql
(só acrescenta colunas; tem de ser aplicada antes deste código).
CSRF
- csrf_check() recusa qualquer pedido que não seja POST (405). Antes saía
sem verificar nada em GET, e dez ações de apagar/restaurar eram GET.
- Os dez handlers passaram a POST com token; links viraram formulários.
Autenticação e sessão
- client_ip(): X-Real-IP / último X-Forwarded-For, só vindo de proxy privado
com TRUSTED_PROXY. O throttle contava o IP do proxy (limite global).
- Throttle também por conta (hash do email), além do IP.
- Sem oráculo de tempo: login verifica contra um hash de referência quando
a conta não existe; a recuperação deixa o email na fila.
- Cookie de sessão Secure/HttpOnly/SameSite=Lax e use_strict_mode.
- Definições de sistema só para sysadmin; autocomplete=username.
Servidor e uploads
- .htaccess: nosniff, Referrer-Policy, X-Frame-Options, Permissions-Policy,
CSP (frame-ancestors, base-uri, object-src, form-action); nega parciais
_*.php, PHP soltos do platform-tools e *.md; 404 própria.
- Uploads validados pelo conteúdo (finfo/getimagesize); SVG só sem scripts,
handlers on*=, foreignObject ou hrefs externos.
Testado numa cópia da BD de produção (fitme_audit_test) com um contentor
temporário: 34/34 verificações HTTP ponta-a-ponta.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- «Conhece as nossas instalações» (e a galeria do CrossFit) passa de slider
Splide a masonry: layout próprio em JS (sem biblioteca), fotos com a
proporção original, coluna mais baixa primeiro e algumas a ocupar duas
colunas (só quando não deixam buraco). Reorganiza ao mudar de largura
(ResizeObserver + resize) e as fotos entram em cascata ao aparecer.
Sem JS fica uma grelha CSS.
- Clicar abre a foto num <dialog>: anterior/seguinte, setas, Esc, deslizar.
- O Splide deixa de ser carregado (CSS e JS).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Ícones novos (Bootcamp, YourFit Core, Run3070, Total Workout, On Ramp, Jump)
do mesmo conjunto; só o LPO continua sem ícone no ficheiro de design.
- Os ícones fitme-*.svg passam a ir embutidos (modalidade_icon_html): cada
traço tem pathLength=1 e desenha-se ao aparecer, no hover e ao abrir o
cartão; cada família tem um movimento (levantar, soco, respirar, saltar,
chama, pedalar, correr). Outros ícones continuam <img>.
- No cartão de detalhe (desktop) o ícone e o nome deslizam até ao centro
vertical da coluna.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Mensalidades: ao passar o rato, a borda do cartão ganha um brilho verde num
conic-gradient que roda (@property --pc-a animável), centrado no cursor; o
cartão inclina-se e desliza para o rato como os da equipa.
- «O que dizem de nós» e rodapé: textura discreta de metal preto mate (grão de
pintura eletrostática, brilho difuso, riscas quase invisíveis), em SVG/CSS.
- Contactos: o site lê só fitme3070_info (Gestão → Contactos). Os campos
contact_*/social_* de Sistema → Definições não eram usados e foram editados
por engano — saem do formulário, com um aviso a apontar para Contactos.
site_info() deixa o WhatsApp só com dígitos (wa.me não aceita + nem espaços).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
O clip-path da animação cortava a box-shadow, por isso no desktop não havia
sombra nenhuma (e no telemóvel era pesada). Aberto, o recorte passa a ir 40px
para lá do cartão e a sombra é curta e suave, um pouco mais escura na equipa
por causa do fundo cinzento.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A faixa de modalidades passa a ser um scroller a sério: toque, trackpad,
roda do rato, arrastar e setas; continua a deslizar sozinha (~20px/s) e
pára enquanto alguém interage, retomando 2,5s depois. Loop infinito sobre
uma cópia do conjunto (a posição é mantida em float — scrollLeft += 0.3
arredondava para zero).
- Em vez do «Saber mais», o cartão com descrição abre um painel que nasce do
próprio cartão e estica para o lado (para a esquerda junto à margem
direita), com inclinação 3D a seguir o rato; no telemóvel voa para o
centro com o fundo escurecido. O painel vive fora do scroller para o
overflow não o cortar.
- 14 ícones novos (SVG, traço 3) em uploads/modalidades/icons/fitme-*.svg.
- Títulos dos cartões em <h3>, h2#modalidades-title + aria-labelledby; sem
ícone não se gera <img> vazio.
- #modalidades entra só com opacidade: o transform deixado pela animação de
entrada prendia o fundo escurecido (position:fixed) dentro da secção.
- prefers-reduced-motion: os cartões de detalhe (equipa e modalidades)
ficam fora da regra global que reduzia as transições a 0,01ms — com ela
(ativa por defeito em Remote Desktop / Windows sem animações) o cartão
aparecia de repente em vez de esticar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Horário de aulas: cartões mais pequenos e estreitos; a partir de 992px mostra
o dia anterior, hoje ao centro e o seguinte (a dar a volta à semana), com as
horas alinhadas e os dias laterais esbatidos. O JPG continua a semana toda.
- Horário de funcionamento: cartões compactos numa <dl>, ordenados por hora.
- Equipa: fundo no cinzento da parede do ginásio com juntas de blocos e grão
muito discretos (SVG inline).
- Tira o «FitMe 3070» do header e esconde a newsletter.
- Estrutura: um <main> em todas as páginas públicas (skip link aponta-lhe),
h2 por secção com aria-labelledby, h3 nos itens em vez de h4, sem secções
aninhadas, alts distintos na galeria e no h1 do CrossFit.
- robots.txt e sitemap.xml gerados com o domínio da instalação; SEO_NOINDEX
no .env tira uma cópia de staging dos motores de pesquisa.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>