feat(modalidades): faixa rolável, cartão de detalhe como o da equipa, ícones novos

- 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>
This commit is contained in:
andreandClaude Opus 5.5 committed 2026-09-24 21:47:17 +01:00
1 parent 8ebf20f52f
commit ee3fe69afb
17 files changed
+595 -179

No files matched your search

+33 -14
View File
@@ -170,32 +170,51 @@ if ($team):
// ── Modalidades ─────────────────────────────────────────────────────────────────
if ($modalidades):
?>
<section id="modalidades" class="service-section">
<section id="modalidades" class="service-section" aria-labelledby="modalidades-title">
<div class="container">
<div class="row justify-content-md-center mb-4">
<div class="col-lg-8">
<div class="section-title text-center deco">
<span class="section-eyebrow">Treinos</span>
<h2>Modalidades</h2>
<h2 id="modalidades-title">Modalidades</h2>
<p class="section-subtitle">Encontra a modalidade certa para ti.</p>
</div>
</div>
</div>
</div>
<!-- Full-width horizontal scroll of modalidade cards -->
<div class="mod-scroll">
<?php foreach($modalidades as $d): $desc = trim((string) ($d['descricao'] ?? '')); ?>
<div class="icon-box-item mod-card<?= $desc !== '' ? ' has-info' : '' ?>"<?= $desc !== '' ? ' role="button" tabindex="0" aria-expanded="false" aria-label="Ver descrição de ' . htmlspecialchars((string) $d['nome']) . '"' : '' ?>>
<div class="ib-icon fade-in">
<img loading="lazy" src="<?= site_img_url($d['icon'], 'uploads/modalidades/icons') ?>" alt="<?= htmlspecialchars((string) $d['nome']) ?>">
<?php /* Full-width strip of modalidade cards. It is a real horizontal
scroller (touch, trackpad/wheel, mouse drag, arrows) that also
glides on its own until someone interacts (main.js). A card with a
description opens .mod-detail — one shared panel outside the
scroller, so it isn't clipped — which grows out of the card. */ ?>
<div class="mod-strip">
<button type="button" class="mod-nav mod-nav-prev" aria-label="Modalidades anteriores"><i class="fas fa-chevron-left" aria-hidden="true"></i></button>
<div class="mod-scroll" tabindex="0" aria-label="Modalidades — desliza para ver mais">
<?php foreach($modalidades as $d): $desc = trim(strip_tags((string) ($d['descricao'] ?? ''))) !== '' ? trim((string) $d['descricao']) : ''; ?>
<div class="icon-box-item mod-card<?= $desc !== '' ? ' has-info' : '' ?>" data-color="<?= htmlspecialchars((string) $d['cor']) ?>"<?= $desc !== '' ? ' role="button" tabindex="0" aria-haspopup="dialog" aria-label="Ver descrição de ' . htmlspecialchars((string) $d['nome']) . '"' : '' ?>>
<div class="ib-icon">
<?php if (trim((string) $d['icon']) !== ''): ?><img loading="lazy" draggable="false" src="<?= site_img_url($d['icon'], 'uploads/modalidades/icons') ?>" alt=""><?php endif; ?>
</div>
<h3><?= htmlspecialchars((string) $d['nome']) ?></h3>
<?php if ($desc !== ''): ?>
<?php /* descricao is admin-authored rich text — sanitised allowlist HTML */ ?>
<template class="mod-desc"><?= clean_richtext($desc) ?></template>
<?php endif; ?>
</div>
<h4><?= htmlspecialchars((string) $d['nome']) ?></h4>
<?php if ($desc !== ''): ?>
<?php /* descricao is admin-authored rich text — sanitised allowlist HTML */ ?>
<div class="mod-tooltip" role="tooltip"><span><?= clean_richtext($desc) ?></span></div>
<?php endif; ?>
<?php endforeach; ?>
</div>
<button type="button" class="mod-nav mod-nav-next" aria-label="Mais modalidades"><i class="fas fa-chevron-right" aria-hidden="true"></i></button>
</div>
<div class="mod-detail" role="dialog" aria-modal="false" aria-hidden="true">
<button type="button" class="md-close" aria-label="Fechar">&times;</button>
<div class="md-card">
<div class="ib-icon"><img src="" alt="" hidden></div>
<h3></h3>
</div>
<div class="md-body">
<strong class="md-name"></strong>
<div class="md-text"></div>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>