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>
No files matched your search
@@ -2248,20 +2248,17 @@ h3.contact-title {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
*,
|
/* The team / modalidade detail cards are exempt: reduced-motion is on by
|
||||||
|
default over Remote Desktop and with Windows "animation effects" off, and
|
||||||
|
this nuke turned their grow-out-of-the-card move into a jarring pop. They
|
||||||
|
only move when the visitor asks (hover / tap) and settle in ~0.6s. */
|
||||||
|
*:not(.trainer-detail):not(.td-body):not(.mod-detail):not(.md-body),
|
||||||
*:before,
|
*:before,
|
||||||
*:after {
|
*:after {
|
||||||
animation-duration: .01ms !important;
|
animation-duration: .01ms !important;
|
||||||
transition-duration: .01ms !important;
|
transition-duration: .01ms !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Exempt the modalidades marquee: the .01ms nuke turns its infinite loop
|
|
||||||
into a strobing blur (reduced-motion is on by default over Remote Desktop
|
|
||||||
and with Windows "animation effects" off). Keep its gentle glide instead;
|
|
||||||
JS re-asserts the measured duration inline with !important. */
|
|
||||||
.mod-track {
|
|
||||||
animation-duration: 220s !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Decorated section title (eyebrow + accent underline + subtitle) ────────── */
|
/* ── Decorated section title (eyebrow + accent underline + subtitle) ────────── */
|
||||||
@@ -2307,136 +2304,305 @@ h3.contact-title {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Modalidades: tap card to reveal its description ───────────────────────── */
|
/* ── Modalidades: scrollable strip + detail card ─────────────────────────────
|
||||||
/* No separate icon — the whole card (icon + name) is the trigger. The
|
.mod-scroll is a real horizontal scroller (touch, trackpad, wheel, mouse
|
||||||
description reveals as a clean panel laid over the card footprint, so the
|
drag, arrow buttons). main.js glides it on its own until someone interacts,
|
||||||
grid never reflows. Click-only (no hover), per request. */
|
and loops it seamlessly over a cloned second set of cards. */
|
||||||
.mod-card {
|
/* content-visibility:auto implies paint containment, which would clip the
|
||||||
|
detail card at the section edge and trap position:fixed on touch. */
|
||||||
|
#modalidades.service-section {
|
||||||
|
content-visibility: visible;
|
||||||
|
contain: none;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modalidades.has-open-detail { z-index: 1100; }
|
||||||
|
|
||||||
|
.mod-strip {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mod-card.has-info {
|
/* Soft fades at both ends, under the arrow buttons. */
|
||||||
cursor: pointer;
|
.mod-strip:before,
|
||||||
|
.mod-strip:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 2;
|
||||||
|
width: 90px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-strip:before { left: 0; background: linear-gradient(90deg, var(--fitme-white), rgba(255, 255, 255, 0)); }
|
||||||
|
.mod-strip:after { right: 0; background: linear-gradient(270deg, var(--fitme-white), rgba(255, 255, 255, 0)); }
|
||||||
|
|
||||||
|
@media (max-width: 767px), (hover: none) {
|
||||||
|
.mod-strip:before, .mod-strip:after { width: 28px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-scroll {
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
padding: 10px 0 14px;
|
||||||
|
scrollbar-width: none;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
overscroll-behavior-x: contain;
|
||||||
|
cursor: grab;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mod-card.has-info .ib-icon,
|
.mod-scroll::-webkit-scrollbar { display: none; }
|
||||||
.mod-card.has-info h4 {
|
.mod-scroll.is-dragging { cursor: grabbing; user-select: none; }
|
||||||
transition: opacity var(--fitme-transition);
|
.mod-scroll.is-dragging .mod-card { pointer-events: none; }
|
||||||
|
|
||||||
|
/* Trailing margin (not flex gap) keeps both copies the same width, so the
|
||||||
|
loop jump from the clone back to the original is invisible. */
|
||||||
|
.mod-scroll .mod-card {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 180px;
|
||||||
|
min-height: 200px;
|
||||||
|
margin: 0 1rem 0 0;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mod-card.has-info:hover h4 {
|
.mod-scroll .mod-card:first-child { margin-left: 1rem; }
|
||||||
color: var(--fitme-accent-dark);
|
|
||||||
}
|
.mod-card.has-info { cursor: pointer; outline: none; }
|
||||||
|
|
||||||
|
.mod-card.has-info:hover h3 { color: var(--fitme-accent-dark); }
|
||||||
|
|
||||||
.mod-card.has-info:focus-visible {
|
.mod-card.has-info:focus-visible {
|
||||||
box-shadow: 0 0 0 3px rgba(var(--fitme-accent-rgb), .35);
|
box-shadow: 0 0 0 3px rgba(var(--fitme-accent-rgb), .35);
|
||||||
border-radius: var(--fitme-radius);
|
border-radius: var(--fitme-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Subtle "tap for more" affordance under the name */
|
/* The card whose detail is open is covered by the panel — hide it so the
|
||||||
.mod-card.has-info h4:after {
|
panel reads as the card itself. */
|
||||||
content: 'Saber mais';
|
.mod-card.is-source { visibility: hidden; }
|
||||||
display: block;
|
|
||||||
margin-top: .35rem;
|
|
||||||
font-size: .68rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: .12em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--fitme-accent-dark);
|
|
||||||
opacity: .85;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mod-tooltip {
|
/* Arrow buttons (desktop). Faded edges hint there is more to either side. */
|
||||||
|
.mod-nav {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: -10px -6px auto -6px;
|
top: 50%;
|
||||||
z-index: 6;
|
z-index: 3;
|
||||||
min-height: calc(100% + 20px);
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
margin-top: -30px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--fitme-dark-2);
|
||||||
|
color: var(--fitme-accent);
|
||||||
|
box-shadow: var(--fitme-shadow-lg);
|
||||||
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: #fff;
|
opacity: .9;
|
||||||
color: var(--fitme-text-soft);
|
|
||||||
border: 1px solid var(--fitme-surface-2);
|
|
||||||
border-top: 3px solid var(--fitme-accent);
|
|
||||||
border-radius: var(--fitme-radius);
|
|
||||||
box-shadow: var(--fitme-shadow-lg);
|
|
||||||
padding: 1rem 1.1rem;
|
|
||||||
font-size: .85rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
text-align: center;
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
transform: scale(.96);
|
|
||||||
transition: opacity var(--fitme-transition), transform var(--fitme-transition);
|
transition: opacity var(--fitme-transition), transform var(--fitme-transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mod-tooltip span {
|
.mod-nav:hover { opacity: 1; transform: scale(1.06); }
|
||||||
display: block;
|
.mod-nav-prev { left: 14px; }
|
||||||
|
.mod-nav-next { right: 14px; }
|
||||||
|
|
||||||
|
@media (max-width: 767px), (hover: none) {
|
||||||
|
.mod-nav { display: none; }
|
||||||
|
.mod-scroll { cursor: auto; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.mod-card.is-open .mod-tooltip {
|
/* ── Detail panel: grows out of the card ───────────────────────────────────
|
||||||
|
Same idea as the team card: main.js lays the panel so its left column
|
||||||
|
(.md-card, a replica of the card) sits exactly on the card, clipped to it;
|
||||||
|
opening unclips it sideways to reveal the description (.md-flip mirrors it
|
||||||
|
near the right edge). While open, the mouse drives a small 3D tilt. */
|
||||||
|
.mod-detail {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 30;
|
||||||
|
left: var(--md-x, 0px);
|
||||||
|
top: var(--md-y, 0px);
|
||||||
|
width: var(--md-w, 540px);
|
||||||
|
min-height: var(--md-ch, 200px);
|
||||||
|
display: flex;
|
||||||
|
text-align: left;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 24px 60px rgba(var(--fitme-dark-rgb), .28);
|
||||||
|
overflow: hidden;
|
||||||
|
clip-path: inset(0px var(--md-cx, 360px) var(--md-cy, 0px) 0px round 2px);
|
||||||
|
transform-origin: calc(var(--md-cw, 180px) / 2) 50%;
|
||||||
|
transform: perspective(900px) rotateX(0deg) rotateY(0deg) translate(0px, 0px) scale(1);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
transition:
|
||||||
|
clip-path .38s cubic-bezier(.4, 0, .2, 1),
|
||||||
|
transform .38s cubic-bezier(.4, 0, .2, 1),
|
||||||
|
opacity .12s ease .26s,
|
||||||
|
visibility 0s linear .38s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-detail.md-flip {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
clip-path: inset(0px 0px var(--md-cy, 0px) var(--md-cx, 360px) round 2px);
|
||||||
|
transform-origin: calc(100% - var(--md-cw, 180px) / 2) 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-detail.is-open {
|
||||||
|
clip-path: inset(0px 0px 0px 0px round 6px);
|
||||||
|
transform: perspective(900px) rotateX(var(--md-rx, 0deg)) rotateY(var(--md-ry, 0deg)) translate(var(--md-mx, 0px), var(--md-my, -6px)) scale(1.02);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
transform: scale(1);
|
pointer-events: auto;
|
||||||
|
transition:
|
||||||
|
clip-path .55s cubic-bezier(.2, .9, .25, 1),
|
||||||
|
transform .6s cubic-bezier(.34, 1.35, .64, 1),
|
||||||
|
opacity 0s,
|
||||||
|
visibility 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mod-card.is-open .ib-icon,
|
.mod-detail.is-open.md-tracking {
|
||||||
.mod-card.is-open h4 {
|
transition:
|
||||||
opacity: 0;
|
clip-path .55s cubic-bezier(.2, .9, .25, 1),
|
||||||
|
transform .18s ease-out,
|
||||||
|
opacity 0s,
|
||||||
|
visibility 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Full-width auto-scrolling modalidade marquee */
|
/* Left column: looks exactly like the card it came from. */
|
||||||
.mod-scroll {
|
.md-card {
|
||||||
overflow: hidden;
|
flex: 0 0 var(--md-cw, 180px);
|
||||||
padding: 8px 0 10px;
|
min-height: var(--md-ch, 200px);
|
||||||
}
|
box-sizing: border-box;
|
||||||
|
|
||||||
.mod-track {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
width: max-content;
|
flex-direction: column;
|
||||||
animation: modMarquee 220s linear infinite;
|
align-items: center;
|
||||||
/* slow fallback; JS tunes it to ~18px/s */
|
justify-content: flex-start;
|
||||||
will-change: transform;
|
padding-top: var(--md-pad, 0px);
|
||||||
|
text-align: center;
|
||||||
|
background: #fff;
|
||||||
|
border-right: 4px solid var(--md-color, var(--fitme-accent));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pause while hovering, focusing inside, or reading an open card */
|
.md-flip .md-card { border-right: 0; border-left: 4px solid var(--md-color, var(--fitme-accent)); }
|
||||||
.mod-scroll:hover .mod-track,
|
|
||||||
.mod-scroll:focus-within .mod-track,
|
.md-card .ib-icon {
|
||||||
.mod-scroll.is-paused .mod-track {
|
width: 72px;
|
||||||
animation-play-state: paused;
|
height: 72px;
|
||||||
|
border-radius: var(--fitme-radius);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--fitme-accent);
|
||||||
|
margin: 0 auto 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Trailing margin (not flex gap) keeps every copy the same width, so a
|
.md-card .ib-icon img { max-width: 44px; max-height: 44px; }
|
||||||
translateX(-50%) of the duplicated track loops perfectly seamlessly. */
|
|
||||||
.mod-scroll .mod-card {
|
.md-card h3 {
|
||||||
flex: 0 0 auto;
|
font-size: 1.15rem;
|
||||||
width: 180px;
|
margin: 0 .5rem;
|
||||||
min-height: 200px;
|
color: var(--fitme-accent-dark);
|
||||||
margin: 0 1rem 0 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes modMarquee {
|
.md-body {
|
||||||
from {
|
flex: 1 1 auto;
|
||||||
transform: translateX(0);
|
padding: 20px 24px 20px 22px;
|
||||||
}
|
max-height: 320px;
|
||||||
|
|
||||||
to {
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Keep the reveal panel inside the card footprint so it isn't clipped by the
|
|
||||||
scroller's overflow (which can't be visible on one axis only). Top-align +
|
|
||||||
scroll so long descriptions don't get their first lines hidden by vertical
|
|
||||||
centering (you can't scroll back above a centered overflowing flex item). */
|
|
||||||
.mod-scroll .mod-tooltip {
|
|
||||||
inset: 0;
|
|
||||||
min-height: 0;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
align-items: flex-start;
|
opacity: 0;
|
||||||
|
transform: translateX(-10px);
|
||||||
|
transition: opacity .12s ease, transform .12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-flip .md-body { transform: translateX(10px); }
|
||||||
|
|
||||||
|
.mod-detail.is-open .md-body {
|
||||||
|
opacity: 1;
|
||||||
|
transform: none;
|
||||||
|
transition: opacity .3s ease .18s, transform .35s ease .18s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-name {
|
||||||
|
display: block;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 900;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--fitme-dark);
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
padding-right: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-text {
|
||||||
|
font-size: .9rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
color: var(--fitme-text-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-text p:last-child { margin-bottom: 0; }
|
||||||
|
|
||||||
|
.md-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 2;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--fitme-text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Touch / narrow: a centred fixed card over a dimmed page; main.js flies it
|
||||||
|
out of the card with a FLIP animation. */
|
||||||
|
@media (max-width: 767px), (hover: none) {
|
||||||
|
.mod-detail,
|
||||||
|
.mod-detail.md-flip {
|
||||||
|
position: fixed;
|
||||||
|
top: var(--md-top, 20vh);
|
||||||
|
left: 16px;
|
||||||
|
right: 16px;
|
||||||
|
width: auto;
|
||||||
|
max-width: 540px;
|
||||||
|
min-height: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-height: calc(100vh - 48px);
|
||||||
|
flex-direction: row;
|
||||||
|
clip-path: none;
|
||||||
|
transform: none;
|
||||||
|
transform-origin: top left;
|
||||||
|
transition: opacity .15s ease, visibility 0s linear .15s;
|
||||||
|
}
|
||||||
|
.mod-detail.is-open {
|
||||||
|
clip-path: none;
|
||||||
|
transform: none;
|
||||||
|
transition: opacity 0s, visibility 0s;
|
||||||
|
}
|
||||||
|
.mod-detail-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 29;
|
||||||
|
background: rgba(0, 0, 0, .55);
|
||||||
|
}
|
||||||
|
.md-close { display: block; }
|
||||||
|
.md-card { flex-basis: 120px; min-height: 0; padding: 18px 6px; justify-content: center; }
|
||||||
|
.md-flip .md-card { border-left: 0; border-right: 4px solid var(--md-color, var(--fitme-accent)); }
|
||||||
|
.md-card .ib-icon { width: 60px; height: 60px; margin-bottom: .8rem; }
|
||||||
|
.md-card h3 { font-size: .95rem; }
|
||||||
|
.md-body,
|
||||||
|
.md-flip .md-body { max-height: calc(100vh - 48px); padding: 16px 16px 16px 14px; transform: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 339px) {
|
||||||
|
.mod-detail, .mod-detail.md-flip { flex-direction: column; }
|
||||||
|
.md-card { flex-basis: auto; border-right: 0; border-bottom: 4px solid var(--md-color, var(--fitme-accent)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ── Horário "save as JPG" button + capture-clone export board ─────────────── */
|
/* ── Horário "save as JPG" button + capture-clone export board ─────────────── */
|
||||||
.schedule-dl-btn {
|
.schedule-dl-btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -3110,7 +3276,8 @@ h3.contact-title {
|
|||||||
position:fixed descendants — which would pin the mobile detail card inside
|
position:fixed descendants — which would pin the mobile detail card inside
|
||||||
its own tile. Reveal the team with opacity only. */
|
its own tile. Reveal the team with opacity only. */
|
||||||
#equipa.revealed,
|
#equipa.revealed,
|
||||||
#equipa .trainer-item.revealed {
|
#equipa .trainer-item.revealed,
|
||||||
|
#modalidades.revealed {
|
||||||
animation-name: fitmeFadeOnly;
|
animation-name: fitmeFadeOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3395,10 +3562,6 @@ h3.contact-title {
|
|||||||
.td-photo img { min-height: 0; height: 160px; }
|
.td-photo img { min-height: 0; height: 160px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.trainer-detail,
|
|
||||||
.trainer-item.is-open .trainer-detail { transition-duration: .01ms !important; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ══════════════════════════════════════════════════════════════════════════
|
/* ══════════════════════════════════════════════════════════════════════════
|
||||||
Mensalidades — cartões de preço
|
Mensalidades — cartões de preço
|
||||||
|
|||||||
@@ -80,75 +80,295 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ── Modalidades: clickable cards + CSS-animated marquee ────────────────────
|
/* ── Modalidades: scrollable strip + detail card ────────────────────────────
|
||||||
Cards reveal their description on click (one open at a time; Esc / outside
|
.mod-scroll is a native horizontal scroller, so touch, trackpad and
|
||||||
click closes; Enter/Space for keyboard users). For the auto-scroll we move
|
shift+wheel just work; on top of that: mouse drag, arrow buttons, plain
|
||||||
the cards into an inner .mod-track, duplicate the set once, and let a CSS
|
wheel → sideways, and a slow auto-glide (~20px/s) that pauses while anyone
|
||||||
transform animation glide it from 0 to -50% forever — a GPU-driven loop that
|
interacts and picks up again a moment later. The card set is cloned once so
|
||||||
actually moves (the old scrollLeft approach was defeated by sub-pixel
|
the glide loops: past the first copy we jump back by exactly its width.
|
||||||
rounding). It pauses on hover / focus and while a card is open. */
|
(The position is kept in a float — assigning scrollLeft += 0.3 each frame
|
||||||
|
rounds to nothing — and written back every frame.)
|
||||||
|
Cards with a description open .mod-detail, one shared panel outside the
|
||||||
|
scroller (so overflow doesn't clip it) that grows out of the card like the
|
||||||
|
team cards do, with the same 3D tilt toward the mouse; on touch it flies to
|
||||||
|
the centre of the screen instead. */
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
var scroller = document.querySelector('.mod-scroll');
|
var section = document.getElementById('modalidades');
|
||||||
|
var scroller = section && section.querySelector('.mod-scroll');
|
||||||
|
if (!scroller) return;
|
||||||
|
var panel = section.querySelector('.mod-detail');
|
||||||
|
var floating = window.matchMedia('(max-width: 767px), (hover: none)');
|
||||||
|
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||||
|
|
||||||
// Build the animated track: move originals in, then append a hidden clone set.
|
// ── Loop: clone the set once ──────────────────────────────────────────
|
||||||
if (scroller && !scroller.querySelector('.mod-track')) {
|
var originals = Array.prototype.slice.call(scroller.querySelectorAll('.mod-card'));
|
||||||
var track = document.createElement('div');
|
originals.forEach(function (c) {
|
||||||
track.className = 'mod-track';
|
var clone = c.cloneNode(true);
|
||||||
Array.prototype.slice.call(scroller.querySelectorAll('.mod-card'))
|
clone.setAttribute('aria-hidden', 'true');
|
||||||
.forEach(function (c) { track.appendChild(c); });
|
clone.setAttribute('tabindex', '-1');
|
||||||
Array.prototype.slice.call(track.children).forEach(function (c) {
|
clone.classList.add('is-clone');
|
||||||
var clone = c.cloneNode(true);
|
scroller.appendChild(clone);
|
||||||
clone.setAttribute('aria-hidden', 'true');
|
});
|
||||||
clone.setAttribute('tabindex', '-1');
|
function loopWidth() {
|
||||||
clone.removeAttribute('id');
|
// Distance from the first original to the first clone = one full set.
|
||||||
track.appendChild(clone);
|
var first = scroller.querySelector('.mod-card');
|
||||||
});
|
var firstClone = scroller.querySelector('.mod-card.is-clone');
|
||||||
scroller.appendChild(track);
|
return first && firstClone ? firstClone.offsetLeft - first.offsetLeft : 0;
|
||||||
// Gentle, constant ~18px/s regardless of how many modalidades there are.
|
}
|
||||||
// Measure after a frame so layout (card widths) is settled. Set with
|
function wrap() {
|
||||||
// !important so the reduced-motion .01ms!important override (active
|
var w = loopWidth();
|
||||||
// over Remote Desktop / Windows "animation effects" off) can't shrink
|
if (!w) return;
|
||||||
// the duration and turn the loop into a strobing blur.
|
if (scroller.scrollLeft >= w) { scroller.scrollLeft -= w; pos = scroller.scrollLeft; }
|
||||||
requestAnimationFrame(function () {
|
else if (scroller.scrollLeft <= 0 && (performance.now() - lastUser) < IDLE) { scroller.scrollLeft += w; pos = scroller.scrollLeft; }
|
||||||
var oneCopy = track.scrollWidth / 2;
|
|
||||||
var dur = oneCopy > 0 ? Math.max(60, Math.round(oneCopy / 18)) : 220;
|
|
||||||
track.style.setProperty('animation-duration', dur + 's', 'important');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var cards = document.querySelectorAll('.mod-card.has-info');
|
// ── Auto-glide ────────────────────────────────────────────────────────
|
||||||
if (!cards.length) return;
|
var SPEED = 20; // px per second
|
||||||
|
var IDLE = 2500; // ms after the last interaction before gliding again
|
||||||
function anyOpen() { return !!document.querySelector('.mod-card.is-open'); }
|
var pos = 0, last = 0, lastUser = -1e9, hovering = false; // -1e9: glide from the start
|
||||||
function syncPause() { if (scroller) scroller.classList.toggle('is-paused', anyOpen()); }
|
function busy() {
|
||||||
function closeAll(except) {
|
return hovering || dragging || panelOpen() || document.hidden ||
|
||||||
cards.forEach(function (card) {
|
(performance.now() - lastUser) < IDLE;
|
||||||
if (card === except) return;
|
|
||||||
card.classList.remove('is-open');
|
|
||||||
card.setAttribute('aria-expanded', 'false');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
function tick(t) {
|
||||||
|
var dt = last ? Math.min(64, t - last) : 0;
|
||||||
|
last = t;
|
||||||
|
if (!busy()) {
|
||||||
|
pos += SPEED * dt / 1000;
|
||||||
|
scroller.scrollLeft = pos;
|
||||||
|
wrap();
|
||||||
|
} else {
|
||||||
|
pos = scroller.scrollLeft;
|
||||||
|
}
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
function touched() { lastUser = performance.now(); }
|
||||||
|
|
||||||
cards.forEach(function (card) {
|
scroller.addEventListener('mouseenter', function () { hovering = true; });
|
||||||
card.addEventListener('click', function (e) {
|
scroller.addEventListener('mouseleave', function () { hovering = false; touched(); });
|
||||||
|
scroller.addEventListener('touchstart', touched, { passive: true });
|
||||||
|
scroller.addEventListener('touchmove', touched, { passive: true });
|
||||||
|
scroller.addEventListener('focusin', touched);
|
||||||
|
scroller.addEventListener('scroll', function () {
|
||||||
|
if (dragging || (performance.now() - lastUser) < IDLE) { touched(); wrap(); }
|
||||||
|
if (panelOpen() && !opening) closePanel();
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// Plain vertical wheel over the strip scrolls it sideways — but only while
|
||||||
|
// there is room, so the page keeps scrolling past it at the ends… which a
|
||||||
|
// looping strip never has, so: only when the pointer is actually on it.
|
||||||
|
scroller.addEventListener('wheel', function (e) {
|
||||||
|
if (floating.matches || Math.abs(e.deltaX) > Math.abs(e.deltaY) || e.shiftKey) { touched(); return; }
|
||||||
|
e.preventDefault();
|
||||||
|
touched();
|
||||||
|
scroller.scrollLeft += e.deltaY;
|
||||||
|
wrap();
|
||||||
|
}, { passive: false });
|
||||||
|
|
||||||
|
// ── Mouse drag ────────────────────────────────────────────────────────
|
||||||
|
var dragging = false, dragStartX = 0, dragStartLeft = 0, dragMoved = 0, suppressClick = false;
|
||||||
|
scroller.addEventListener('pointerdown', function (e) {
|
||||||
|
if (e.pointerType !== 'mouse' || e.button !== 0) return;
|
||||||
|
dragging = true; dragMoved = 0;
|
||||||
|
dragStartX = e.clientX; dragStartLeft = scroller.scrollLeft;
|
||||||
|
touched();
|
||||||
|
});
|
||||||
|
window.addEventListener('pointermove', function (e) {
|
||||||
|
if (!dragging) return;
|
||||||
|
var dx = e.clientX - dragStartX;
|
||||||
|
dragMoved = Math.max(dragMoved, Math.abs(dx));
|
||||||
|
if (dragMoved > 5) {
|
||||||
|
scroller.classList.add('is-dragging');
|
||||||
|
scroller.scrollLeft = dragStartLeft - dx;
|
||||||
|
wrap();
|
||||||
|
touched();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('pointerup', function () {
|
||||||
|
if (!dragging) return;
|
||||||
|
dragging = false;
|
||||||
|
suppressClick = dragMoved > 5;
|
||||||
|
scroller.classList.remove('is-dragging');
|
||||||
|
touched();
|
||||||
|
});
|
||||||
|
// A drag must not also count as a click on the card under the pointer.
|
||||||
|
scroller.addEventListener('click', function (e) {
|
||||||
|
if (suppressClick) { e.stopPropagation(); e.preventDefault(); suppressClick = false; }
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
// ── Arrow buttons ─────────────────────────────────────────────────────
|
||||||
|
function step(dir) {
|
||||||
|
var card = scroller.querySelector('.mod-card');
|
||||||
|
var by = card ? (card.offsetWidth + 16) * Math.max(1, Math.floor(scroller.clientWidth / (card.offsetWidth + 16)) - 1) : 400;
|
||||||
|
touched();
|
||||||
|
if (dir < 0 && scroller.scrollLeft - by < 0) { scroller.scrollLeft += loopWidth(); }
|
||||||
|
scroller.scrollBy({ left: dir * by, behavior: reduced.matches ? 'auto' : 'smooth' });
|
||||||
|
}
|
||||||
|
section.querySelectorAll('.mod-nav').forEach(function (b) {
|
||||||
|
b.addEventListener('click', function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
var open = card.classList.toggle('is-open');
|
closePanel();
|
||||||
card.setAttribute('aria-expanded', open ? 'true' : 'false');
|
step(b.classList.contains('mod-nav-prev') ? -1 : 1);
|
||||||
closeAll(card);
|
|
||||||
syncPause();
|
|
||||||
});
|
|
||||||
card.addEventListener('keydown', function (e) {
|
|
||||||
if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') {
|
|
||||||
e.preventDefault();
|
|
||||||
card.click();
|
|
||||||
} else if (e.key === 'Escape') {
|
|
||||||
card.classList.remove('is-open');
|
|
||||||
card.setAttribute('aria-expanded', 'false');
|
|
||||||
syncPause();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
document.addEventListener('click', function () { closeAll(null); syncPause(); });
|
|
||||||
|
// ── Detail panel ──────────────────────────────────────────────────────
|
||||||
|
if (!panel) return;
|
||||||
|
var source = null, opening = false, trackTimer = 0, backdrop = null;
|
||||||
|
var W = 540, GUTTER = 16;
|
||||||
|
function panelOpen() { return panel.classList.contains('is-open'); }
|
||||||
|
|
||||||
|
function fill(card) {
|
||||||
|
var img = card.querySelector('.ib-icon img');
|
||||||
|
var name = card.querySelector('h3').textContent;
|
||||||
|
var tpl = card.querySelector('template.mod-desc');
|
||||||
|
var pImg = panel.querySelector('.md-card img');
|
||||||
|
pImg.hidden = !img;
|
||||||
|
pImg.src = img ? img.src : '';
|
||||||
|
panel.querySelector('.md-card h3').textContent = name;
|
||||||
|
panel.querySelector('.md-name').textContent = name;
|
||||||
|
panel.querySelector('.md-text').innerHTML = tpl ? tpl.innerHTML : '';
|
||||||
|
panel.style.setProperty('--md-color', card.getAttribute('data-color') || '');
|
||||||
|
panel.setAttribute('aria-label', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desktop: lay the panel's left column exactly over the card.
|
||||||
|
function place(card) {
|
||||||
|
var sr = section.getBoundingClientRect(), cr = card.getBoundingClientRect();
|
||||||
|
var vw = document.documentElement.clientWidth;
|
||||||
|
var w = Math.min(W, vw - GUTTER * 2);
|
||||||
|
var flip = cr.left + w > vw - GUTTER;
|
||||||
|
var x = (flip ? cr.right - w : cr.left) - sr.left;
|
||||||
|
var icon = card.querySelector('.ib-icon');
|
||||||
|
panel.classList.toggle('md-flip', flip);
|
||||||
|
panel.style.setProperty('--md-w', w + 'px');
|
||||||
|
panel.style.setProperty('--md-cw', cr.width + 'px');
|
||||||
|
panel.style.setProperty('--md-ch', cr.height + 'px');
|
||||||
|
panel.style.setProperty('--md-pad', (icon ? icon.offsetTop : 0) + 'px');
|
||||||
|
panel.style.setProperty('--md-x', x + 'px');
|
||||||
|
panel.style.setProperty('--md-y', (cr.top - sr.top) + 'px');
|
||||||
|
panel.style.setProperty('--md-cx', Math.max(0, w - cr.width) + 'px');
|
||||||
|
panel.style.setProperty('--md-cy', Math.max(0, panel.offsetHeight - cr.height) + 'px');
|
||||||
|
}
|
||||||
|
|
||||||
|
function flyFrom(card, opening) {
|
||||||
|
if (!panel.animate) return null;
|
||||||
|
var c = card.getBoundingClientRect(), p = panel.getBoundingClientRect();
|
||||||
|
if (!p.width || !p.height) return null;
|
||||||
|
var from = 'translate(' + (c.left - p.left) + 'px,' + (c.top - p.top) + 'px) scale(' +
|
||||||
|
(c.width / p.width) + ',' + (c.height / p.height) + ')';
|
||||||
|
var frames = [{ transform: from, opacity: .4 }, { transform: 'none', opacity: 1 }];
|
||||||
|
return panel.animate(opening ? frames : frames.reverse(), {
|
||||||
|
duration: opening ? 420 : 260,
|
||||||
|
easing: opening ? 'cubic-bezier(.2,.9,.25,1)' : 'cubic-bezier(.4,0,1,1)'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openPanel(card) {
|
||||||
|
if (source === card && panelOpen()) return;
|
||||||
|
closePanel(true);
|
||||||
|
source = card;
|
||||||
|
fill(card);
|
||||||
|
opening = true;
|
||||||
|
if (floating.matches) {
|
||||||
|
backdrop = document.createElement('div');
|
||||||
|
backdrop.className = 'mod-detail-backdrop';
|
||||||
|
backdrop.addEventListener('click', function () { closePanel(); });
|
||||||
|
section.appendChild(backdrop);
|
||||||
|
panel.classList.add('is-open');
|
||||||
|
panel.style.setProperty('--md-top', Math.max(24, (window.innerHeight - panel.offsetHeight) / 2) + 'px');
|
||||||
|
flyFrom(card, true);
|
||||||
|
} else {
|
||||||
|
panel.style.transition = 'none';
|
||||||
|
place(card);
|
||||||
|
void panel.offsetWidth;
|
||||||
|
panel.style.transition = '';
|
||||||
|
void panel.offsetWidth;
|
||||||
|
panel.classList.add('is-open');
|
||||||
|
card.classList.add('is-source');
|
||||||
|
trackTimer = setTimeout(function () { panel.classList.add('md-tracking'); }, 600);
|
||||||
|
}
|
||||||
|
panel.setAttribute('aria-hidden', 'false');
|
||||||
|
section.classList.add('has-open-detail');
|
||||||
|
setTimeout(function () { opening = false; }, 450);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePanel(instant) {
|
||||||
|
if (!panelOpen() && !source) return;
|
||||||
|
var card = source;
|
||||||
|
clearTimeout(trackTimer);
|
||||||
|
['--md-rx', '--md-ry', '--md-mx', '--md-my'].forEach(function (p) { panel.style.removeProperty(p); });
|
||||||
|
panel.classList.remove('md-tracking');
|
||||||
|
var done = function () {
|
||||||
|
panel.classList.remove('is-open');
|
||||||
|
if (card) card.classList.remove('is-source');
|
||||||
|
section.classList.remove('has-open-detail');
|
||||||
|
if (backdrop) { backdrop.remove(); backdrop = null; }
|
||||||
|
};
|
||||||
|
var anim = (!instant && floating.matches && card) ? flyFrom(card, false) : null;
|
||||||
|
if (anim) anim.onfinish = done;
|
||||||
|
else {
|
||||||
|
panel.classList.remove('is-open');
|
||||||
|
// Desktop: the card reappears once the panel has folded back onto it.
|
||||||
|
if (card) setTimeout(function () { if (source !== card) card.classList.remove('is-source'); }, instant ? 0 : 380);
|
||||||
|
section.classList.remove('has-open-detail');
|
||||||
|
if (backdrop) { backdrop.remove(); backdrop = null; }
|
||||||
|
}
|
||||||
|
panel.setAttribute('aria-hidden', 'true');
|
||||||
|
source = null;
|
||||||
|
touched();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open: hover (desktop, short intent delay) or click / tap / Enter.
|
||||||
|
var hoverTimer = 0;
|
||||||
|
scroller.addEventListener('mouseover', function (e) {
|
||||||
|
if (floating.matches || dragging) return;
|
||||||
|
var card = e.target.closest('.mod-card.has-info');
|
||||||
|
if (!card || card === source) return;
|
||||||
|
clearTimeout(hoverTimer);
|
||||||
|
hoverTimer = setTimeout(function () { if (!dragging) openPanel(card); }, 120);
|
||||||
|
});
|
||||||
|
scroller.addEventListener('mouseout', function (e) {
|
||||||
|
if (floating.matches) return;
|
||||||
|
var card = e.target.closest('.mod-card.has-info');
|
||||||
|
if (card && !card.contains(e.relatedTarget)) clearTimeout(hoverTimer);
|
||||||
|
});
|
||||||
|
// The panel sits over the card; leaving the panel closes it.
|
||||||
|
panel.addEventListener('mouseleave', function (e) {
|
||||||
|
if (floating.matches) return;
|
||||||
|
if (source && source.contains(e.relatedTarget)) return;
|
||||||
|
closePanel();
|
||||||
|
});
|
||||||
|
panel.addEventListener('mousemove', function (e) {
|
||||||
|
if (floating.matches || !panelOpen()) return;
|
||||||
|
var r = panel.getBoundingClientRect();
|
||||||
|
var dx = Math.max(-1, Math.min(1, (e.clientX - (r.left + r.width / 2)) / (r.width / 2)));
|
||||||
|
var dy = Math.max(-1, Math.min(1, (e.clientY - (r.top + r.height / 2)) / (r.height / 2)));
|
||||||
|
panel.style.setProperty('--md-ry', (dx * 5).toFixed(2) + 'deg');
|
||||||
|
panel.style.setProperty('--md-rx', (-dy * 5).toFixed(2) + 'deg');
|
||||||
|
panel.style.setProperty('--md-mx', (dx * 6).toFixed(1) + 'px');
|
||||||
|
panel.style.setProperty('--md-my', (dy * 6 - 6).toFixed(1) + 'px');
|
||||||
|
});
|
||||||
|
|
||||||
|
scroller.addEventListener('click', function (e) {
|
||||||
|
var card = e.target.closest('.mod-card.has-info');
|
||||||
|
if (!card) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
openPanel(card);
|
||||||
|
});
|
||||||
|
scroller.addEventListener('keydown', function (e) {
|
||||||
|
var card = e.target.closest('.mod-card.has-info');
|
||||||
|
if (card && (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar')) { e.preventDefault(); openPanel(card); }
|
||||||
|
});
|
||||||
|
panel.addEventListener('click', function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (e.target.closest('.md-close')) closePanel();
|
||||||
|
});
|
||||||
|
document.addEventListener('click', function () { if (panelOpen()) closePanel(); });
|
||||||
|
document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && panelOpen()) closePanel(); });
|
||||||
|
window.addEventListener('resize', function () { if (panelOpen()) closePanel(true); }, { passive: true });
|
||||||
|
window.addEventListener('scroll', function () { if (panelOpen() && !floating.matches && !opening) closePanel(); }, { passive: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ── Schedule day filter ─────────────────────────────────────────────────────
|
/* ── Schedule day filter ─────────────────────────────────────────────────────
|
||||||
@@ -307,7 +527,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
function flip(item, opening) {
|
function flip(item, opening) {
|
||||||
var card = item.querySelector('.trainer-detail');
|
var card = item.querySelector('.trainer-detail');
|
||||||
var pic = item.querySelector('.trainer-pic');
|
var pic = item.querySelector('.trainer-pic');
|
||||||
if (!card || !pic || !card.animate || reduced.matches) return null;
|
if (!card || !pic || !card.animate) return null;
|
||||||
var p = pic.getBoundingClientRect(), c = card.getBoundingClientRect();
|
var p = pic.getBoundingClientRect(), c = card.getBoundingClientRect();
|
||||||
if (!c.width || !c.height) return null;
|
if (!c.width || !c.height) return null;
|
||||||
var from = 'translate(' + (p.left - c.left) + 'px,' + (p.top - c.top) + 'px) scale(' +
|
var from = 'translate(' + (p.left - c.left) + 'px,' + (p.top - c.top) + 'px) scale(' +
|
||||||
@@ -376,7 +596,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
|
|
||||||
// Tilt + drift toward the pointer while open (desktop only).
|
// Tilt + drift toward the pointer while open (desktop only).
|
||||||
item.addEventListener('mousemove', function (e) {
|
item.addEventListener('mousemove', function (e) {
|
||||||
if (floating.matches || reduced.matches || !item.classList.contains('is-open')) return;
|
if (floating.matches || !item.classList.contains('is-open')) return;
|
||||||
var r = card.getBoundingClientRect();
|
var r = card.getBoundingClientRect();
|
||||||
var dx = Math.max(-1, Math.min(1, (e.clientX - (r.left + r.width / 2)) / (r.width / 2)));
|
var dx = Math.max(-1, Math.min(1, (e.clientX - (r.left + r.width / 2)) / (r.width / 2)));
|
||||||
var dy = Math.max(-1, Math.min(1, (e.clientY - (r.top + r.height / 2)) / (r.height / 2)));
|
var dy = Math.max(-1, Math.min(1, (e.clientY - (r.top + r.height / 2)) / (r.height / 2)));
|
||||||
|
|||||||
@@ -170,32 +170,51 @@ if ($team):
|
|||||||
// ── Modalidades ─────────────────────────────────────────────────────────────────
|
// ── Modalidades ─────────────────────────────────────────────────────────────────
|
||||||
if ($modalidades):
|
if ($modalidades):
|
||||||
?>
|
?>
|
||||||
<section id="modalidades" class="service-section">
|
<section id="modalidades" class="service-section" aria-labelledby="modalidades-title">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-md-center mb-4">
|
<div class="row justify-content-md-center mb-4">
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<div class="section-title text-center deco">
|
<div class="section-title text-center deco">
|
||||||
<span class="section-eyebrow">Treinos</span>
|
<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>
|
<p class="section-subtitle">Encontra a modalidade certa para ti.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Full-width horizontal scroll of modalidade cards -->
|
<?php /* Full-width strip of modalidade cards. It is a real horizontal
|
||||||
<div class="mod-scroll">
|
scroller (touch, trackpad/wheel, mouse drag, arrows) that also
|
||||||
<?php foreach($modalidades as $d): $desc = trim((string) ($d['descricao'] ?? '')); ?>
|
glides on its own until someone interacts (main.js). A card with a
|
||||||
<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']) . '"' : '' ?>>
|
description opens .mod-detail — one shared panel outside the
|
||||||
<div class="ib-icon fade-in">
|
scroller, so it isn't clipped — which grows out of the card. */ ?>
|
||||||
<img loading="lazy" src="<?= site_img_url($d['icon'], 'uploads/modalidades/icons') ?>" alt="<?= htmlspecialchars((string) $d['nome']) ?>">
|
<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>
|
</div>
|
||||||
<h4><?= htmlspecialchars((string) $d['nome']) ?></h4>
|
<?php endforeach; ?>
|
||||||
<?php if ($desc !== ''): ?>
|
</div>
|
||||||
<?php /* descricao is admin-authored rich text — sanitised allowlist HTML */ ?>
|
<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 class="mod-tooltip" role="tooltip"><span><?= clean_richtext($desc) ?></span></div>
|
</div>
|
||||||
<?php endif; ?>
|
<div class="mod-detail" role="dialog" aria-modal="false" aria-hidden="true">
|
||||||
|
<button type="button" class="md-close" aria-label="Fechar">×</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>
|
</div>
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><path d="M15 21V8h18v13"></path><path fill="currentColor" fill-rule="evenodd" d="M19 17h10l7 7v10l-7 7H19l-7-7V24zM16.5 22h15v7h-4v9h-7v-9h-4z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 356 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><path d="M15 21V8h18v13"></path><path fill="currentColor" d="M19 17h10l7 7v10l-7 7H19l-7-7V24z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 308 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><circle cx="34" cy="30" r="7"></circle><circle fill="currentColor" cx="34" cy="30" r="2"></circle><path d="M13 12h10M18 12v18h16M18 20h13M31 12l3 18M30 12h6M18 30l-6 11M34 30l5 11M6 41h36"></path></svg>
|
||||||
|
After Width: | Height: | Size: 401 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><path d="M18 24h12"></path><path fill="currentColor" d="M12 18h6v12h-6zM30 18h6v12h-6z"></path><path d="M10 11V7h28v7M34 11l4 4 4-4"></path><path d="M38 37v4H10v-7M6 37l4-4 4 4"></path></svg>
|
||||||
|
After Width: | Height: | Size: 390 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><path d="M22 4l14 17v10l-8 10h-8l-8-10V20l6 5z"></path><path fill="currentColor" d="M24 27l4 6-1 8h-6l-1-8z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 321 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><path d="M18 13h12l7 7v12l-7 7H18l-7-7V20z"></path><path fill="currentColor" d="M20 4h8v4h-8z"></path><path d="M24 8v5M24 26l5-5M37 12l3-3"></path></svg>
|
||||||
|
After Width: | Height: | Size: 352 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><circle fill="currentColor" cx="11" cy="25" r="3.5"></circle><path d="M24 38l-9-8M24 38l8-11 8 11M16 29l9-3M4 41h40"></path></svg>
|
||||||
|
After Width: | Height: | Size: 329 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><circle fill="currentColor" cx="12" cy="19" r="3.5"></circle><path d="M22 37l-7-12M22 37l16-15M15 26l11-9M4 41h40"></path></svg>
|
||||||
|
After Width: | Height: | Size: 327 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><circle fill="currentColor" cx="24" cy="8" r="3.5"></circle><path d="M6 17h36M24 14v14M24 28l-11 13M24 28l9 3v10"></path></svg>
|
||||||
|
After Width: | Height: | Size: 326 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><path d="M14 8h16l6 6v14l-5 6H14l-4-4V12z"></path><path d="M10 21h8l3 5"></path><path fill="currentColor" d="M14 34h18v6H14z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 338 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><path fill="currentColor" d="M24 8l6 11-6 11-6-11z"></path><path d="M19 30h-8L8 18l11 6"></path><path d="M29 30h8l3-12-11 6"></path><path d="M8 37h32"></path></svg>
|
||||||
|
After Width: | Height: | Size: 363 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><path fill="currentColor" d="M5 30h38l-3 6H8z"></path><path d="M11 36v5M37 36v5"></path><path d="M4 19h9l3-7 5 13 4-9 2 3h17"></path></svg>
|
||||||
|
After Width: | Height: | Size: 338 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><path d="M3 24h5M18 24h12M40 24h5"></path><path d="M8 17h5v14H8zM35 17h5v14h-5z"></path><path fill="currentColor" d="M13 12h5v24h-5zM30 12h5v24h-5z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 361 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" color="#141414" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter"><circle fill="currentColor" cx="21" cy="8" r="3.5"></circle><path d="M22 14l3 13M23 18l9-6 4-8M23 18l-10 5-3-6M25 27l-4 14M25 27l10 3 4 8"></path></svg>
|
||||||
|
After Width: | Height: | Size: 351 B |