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;
|
||||
}
|
||||
|
||||
*,
|
||||
/* 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,
|
||||
*:after {
|
||||
animation-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) ────────── */
|
||||
@@ -2307,136 +2304,305 @@ h3.contact-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* ── Modalidades: tap card to reveal its description ───────────────────────── */
|
||||
/* No separate icon — the whole card (icon + name) is the trigger. The
|
||||
description reveals as a clean panel laid over the card footprint, so the
|
||||
grid never reflows. Click-only (no hover), per request. */
|
||||
.mod-card {
|
||||
/* ── Modalidades: scrollable strip + detail card ─────────────────────────────
|
||||
.mod-scroll is a real horizontal scroller (touch, trackpad, wheel, mouse
|
||||
drag, arrow buttons). main.js glides it on its own until someone interacts,
|
||||
and loops it seamlessly over a cloned second set of cards. */
|
||||
/* 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;
|
||||
}
|
||||
|
||||
.mod-card.has-info {
|
||||
cursor: pointer;
|
||||
/* Soft fades at both ends, under the arrow buttons. */
|
||||
.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;
|
||||
}
|
||||
|
||||
.mod-card.has-info .ib-icon,
|
||||
.mod-card.has-info h4 {
|
||||
transition: opacity var(--fitme-transition);
|
||||
.mod-scroll::-webkit-scrollbar { display: none; }
|
||||
.mod-scroll.is-dragging { cursor: grabbing; user-select: none; }
|
||||
.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 {
|
||||
color: var(--fitme-accent-dark);
|
||||
}
|
||||
.mod-scroll .mod-card:first-child { margin-left: 1rem; }
|
||||
|
||||
.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 {
|
||||
box-shadow: 0 0 0 3px rgba(var(--fitme-accent-rgb), .35);
|
||||
border-radius: var(--fitme-radius);
|
||||
}
|
||||
|
||||
/* Subtle "tap for more" affordance under the name */
|
||||
.mod-card.has-info h4:after {
|
||||
content: 'Saber mais';
|
||||
display: block;
|
||||
margin-top: .35rem;
|
||||
font-size: .68rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--fitme-accent-dark);
|
||||
opacity: .85;
|
||||
}
|
||||
/* The card whose detail is open is covered by the panel — hide it so the
|
||||
panel reads as the card itself. */
|
||||
.mod-card.is-source { visibility: hidden; }
|
||||
|
||||
.mod-tooltip {
|
||||
/* Arrow buttons (desktop). Faded edges hint there is more to either side. */
|
||||
.mod-nav {
|
||||
position: absolute;
|
||||
inset: -10px -6px auto -6px;
|
||||
z-index: 6;
|
||||
min-height: calc(100% + 20px);
|
||||
top: 50%;
|
||||
z-index: 3;
|
||||
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;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
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);
|
||||
opacity: .9;
|
||||
transition: opacity var(--fitme-transition), transform var(--fitme-transition);
|
||||
}
|
||||
|
||||
.mod-tooltip span {
|
||||
display: block;
|
||||
.mod-nav:hover { opacity: 1; transform: scale(1.06); }
|
||||
.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;
|
||||
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-card.is-open h4 {
|
||||
opacity: 0;
|
||||
.mod-detail.is-open.md-tracking {
|
||||
transition:
|
||||
clip-path .55s cubic-bezier(.2, .9, .25, 1),
|
||||
transform .18s ease-out,
|
||||
opacity 0s,
|
||||
visibility 0s;
|
||||
}
|
||||
|
||||
/* Full-width auto-scrolling modalidade marquee */
|
||||
.mod-scroll {
|
||||
overflow: hidden;
|
||||
padding: 8px 0 10px;
|
||||
}
|
||||
|
||||
.mod-track {
|
||||
/* Left column: looks exactly like the card it came from. */
|
||||
.md-card {
|
||||
flex: 0 0 var(--md-cw, 180px);
|
||||
min-height: var(--md-ch, 200px);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
width: max-content;
|
||||
animation: modMarquee 220s linear infinite;
|
||||
/* slow fallback; JS tunes it to ~18px/s */
|
||||
will-change: transform;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
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 */
|
||||
.mod-scroll:hover .mod-track,
|
||||
.mod-scroll:focus-within .mod-track,
|
||||
.mod-scroll.is-paused .mod-track {
|
||||
animation-play-state: paused;
|
||||
.md-flip .md-card { border-right: 0; border-left: 4px solid var(--md-color, var(--fitme-accent)); }
|
||||
|
||||
.md-card .ib-icon {
|
||||
width: 72px;
|
||||
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
|
||||
translateX(-50%) of the duplicated track loops perfectly seamlessly. */
|
||||
.mod-scroll .mod-card {
|
||||
flex: 0 0 auto;
|
||||
width: 180px;
|
||||
min-height: 200px;
|
||||
margin: 0 1rem 0 0;
|
||||
.md-card .ib-icon img { max-width: 44px; max-height: 44px; }
|
||||
|
||||
.md-card h3 {
|
||||
font-size: 1.15rem;
|
||||
margin: 0 .5rem;
|
||||
color: var(--fitme-accent-dark);
|
||||
}
|
||||
|
||||
@keyframes modMarquee {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
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;
|
||||
.md-body {
|
||||
flex: 1 1 auto;
|
||||
padding: 20px 24px 20px 22px;
|
||||
max-height: 320px;
|
||||
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 ─────────────── */
|
||||
.schedule-dl-btn {
|
||||
display: inline-flex;
|
||||
@@ -3110,7 +3276,8 @@ h3.contact-title {
|
||||
position:fixed descendants — which would pin the mobile detail card inside
|
||||
its own tile. Reveal the team with opacity only. */
|
||||
#equipa.revealed,
|
||||
#equipa .trainer-item.revealed {
|
||||
#equipa .trainer-item.revealed,
|
||||
#modalidades.revealed {
|
||||
animation-name: fitmeFadeOnly;
|
||||
}
|
||||
|
||||
@@ -3395,10 +3562,6 @@ h3.contact-title {
|
||||
.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
|
||||
|
||||
@@ -80,75 +80,295 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Modalidades: clickable cards + CSS-animated marquee ────────────────────
|
||||
Cards reveal their description on click (one open at a time; Esc / outside
|
||||
click closes; Enter/Space for keyboard users). For the auto-scroll we move
|
||||
the cards into an inner .mod-track, duplicate the set once, and let a CSS
|
||||
transform animation glide it from 0 to -50% forever — a GPU-driven loop that
|
||||
actually moves (the old scrollLeft approach was defeated by sub-pixel
|
||||
rounding). It pauses on hover / focus and while a card is open. */
|
||||
/* ── Modalidades: scrollable strip + detail card ────────────────────────────
|
||||
.mod-scroll is a native horizontal scroller, so touch, trackpad and
|
||||
shift+wheel just work; on top of that: mouse drag, arrow buttons, plain
|
||||
wheel → sideways, and a slow auto-glide (~20px/s) that pauses while anyone
|
||||
interacts and picks up again a moment later. The card set is cloned once so
|
||||
the glide loops: past the first copy we jump back by exactly its width.
|
||||
(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 () {
|
||||
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.
|
||||
if (scroller && !scroller.querySelector('.mod-track')) {
|
||||
var track = document.createElement('div');
|
||||
track.className = 'mod-track';
|
||||
Array.prototype.slice.call(scroller.querySelectorAll('.mod-card'))
|
||||
.forEach(function (c) { track.appendChild(c); });
|
||||
Array.prototype.slice.call(track.children).forEach(function (c) {
|
||||
// ── Loop: clone the set once ──────────────────────────────────────────
|
||||
var originals = Array.prototype.slice.call(scroller.querySelectorAll('.mod-card'));
|
||||
originals.forEach(function (c) {
|
||||
var clone = c.cloneNode(true);
|
||||
clone.setAttribute('aria-hidden', 'true');
|
||||
clone.setAttribute('tabindex', '-1');
|
||||
clone.removeAttribute('id');
|
||||
track.appendChild(clone);
|
||||
});
|
||||
scroller.appendChild(track);
|
||||
// Gentle, constant ~18px/s regardless of how many modalidades there are.
|
||||
// Measure after a frame so layout (card widths) is settled. Set with
|
||||
// !important so the reduced-motion .01ms!important override (active
|
||||
// over Remote Desktop / Windows "animation effects" off) can't shrink
|
||||
// the duration and turn the loop into a strobing blur.
|
||||
requestAnimationFrame(function () {
|
||||
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');
|
||||
clone.classList.add('is-clone');
|
||||
scroller.appendChild(clone);
|
||||
});
|
||||
function loopWidth() {
|
||||
// Distance from the first original to the first clone = one full set.
|
||||
var first = scroller.querySelector('.mod-card');
|
||||
var firstClone = scroller.querySelector('.mod-card.is-clone');
|
||||
return first && firstClone ? firstClone.offsetLeft - first.offsetLeft : 0;
|
||||
}
|
||||
function wrap() {
|
||||
var w = loopWidth();
|
||||
if (!w) return;
|
||||
if (scroller.scrollLeft >= w) { scroller.scrollLeft -= w; pos = scroller.scrollLeft; }
|
||||
else if (scroller.scrollLeft <= 0 && (performance.now() - lastUser) < IDLE) { scroller.scrollLeft += w; pos = scroller.scrollLeft; }
|
||||
}
|
||||
|
||||
var cards = document.querySelectorAll('.mod-card.has-info');
|
||||
if (!cards.length) return;
|
||||
|
||||
function anyOpen() { return !!document.querySelector('.mod-card.is-open'); }
|
||||
function syncPause() { if (scroller) scroller.classList.toggle('is-paused', anyOpen()); }
|
||||
function closeAll(except) {
|
||||
cards.forEach(function (card) {
|
||||
if (card === except) return;
|
||||
card.classList.remove('is-open');
|
||||
card.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
// ── Auto-glide ────────────────────────────────────────────────────────
|
||||
var SPEED = 20; // px per second
|
||||
var IDLE = 2500; // ms after the last interaction before gliding again
|
||||
var pos = 0, last = 0, lastUser = -1e9, hovering = false; // -1e9: glide from the start
|
||||
function busy() {
|
||||
return hovering || dragging || panelOpen() || document.hidden ||
|
||||
(performance.now() - lastUser) < IDLE;
|
||||
}
|
||||
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) {
|
||||
card.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
var open = card.classList.toggle('is-open');
|
||||
card.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
closeAll(card);
|
||||
syncPause();
|
||||
});
|
||||
card.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') {
|
||||
scroller.addEventListener('mouseenter', function () { hovering = true; });
|
||||
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();
|
||||
card.click();
|
||||
} else if (e.key === 'Escape') {
|
||||
card.classList.remove('is-open');
|
||||
card.setAttribute('aria-expanded', 'false');
|
||||
syncPause();
|
||||
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();
|
||||
});
|
||||
document.addEventListener('click', function () { closeAll(null); syncPause(); });
|
||||
// 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();
|
||||
closePanel();
|
||||
step(b.classList.contains('mod-nav-prev') ? -1 : 1);
|
||||
});
|
||||
});
|
||||
|
||||
// ── 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 ─────────────────────────────────────────────────────
|
||||
@@ -307,7 +527,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
function flip(item, opening) {
|
||||
var card = item.querySelector('.trainer-detail');
|
||||
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();
|
||||
if (!c.width || !c.height) return null;
|
||||
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).
|
||||
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 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)));
|
||||
|
||||
@@ -170,33 +170,52 @@ 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>
|
||||
<h4><?= htmlspecialchars((string) $d['nome']) ?></h4>
|
||||
<h3><?= htmlspecialchars((string) $d['nome']) ?></h3>
|
||||
<?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>
|
||||
<template class="mod-desc"><?= clean_richtext($desc) ?></template>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?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">×</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>
|
||||
</section>
|
||||
<?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 |