fix(site): restore CSS lost in 6c14e64; gallery click-and-drag; no link underline + colour/glow hover; brand-colour CrossFit pill; lighter trial link; no gloss on team photos

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
andreandClaude Sonnet 5.5 committed 2026-10-13 18:49:53 +01:00
1 parent 6c14e644b1
commit 3d2a696162
3 files changed
+871 -69

No files matched your search

+783 -56
View File
@@ -391,27 +391,30 @@ section[id] {
/* ── CrossFit nav pill (follows the gym's brand colour) ─────────────────────────────────────────────────────── */
.crossfit-nav-link {
color: var(--fitme-accent-ink) !important;
/* the gym's own brand colour (not the darker "ink" shade), filled so the text
stays readable on the light navbar: dark/white text via --fitme-on-accent */
background: var(--fitme-accent) !important;
color: var(--fitme-on-accent) !important;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .5px;
border: 2px solid var(--fitme-accent-ink);
border: 2px solid var(--fitme-accent);
border-radius: var(--fitme-radius);
padding: 6px 14px !important;
transition: background-color var(--fitme-transition), color var(--fitme-transition);
transition: background-color .4s ease, color .4s ease, border-color .4s ease;
}
.crossfit-nav-link:after {
display: none;
}
/* Hover and current-page state: filled pill, white text (must outrank the
/* Hover and current-page state: near-black face with white text (outranks the
generic .nav-item.active .nav-link transparent-background rule above) */
.crossfit-nav-link:hover,
.nav-item.active .crossfit-nav-link {
background: var(--fitme-accent) !important;
background: color-mix(in srgb, var(--fitme-accent) 10%, #000) !important;
border-color: var(--fitme-accent);
color: var(--fitme-on-accent) !important;
color: #fff !important;
}
/* ── Other-gym button (right end of the navbar) ────────────────────────────── */
@@ -683,7 +686,8 @@ section[id] {
padding: 4px 8px;
color: #fff;
font: inherit;
font-weight: 600;
font-weight: 300;
letter-spacing: .02em;
text-shadow: 0 1px 6px rgba(0, 0, 0, .6);
cursor: pointer;
transition: color .3s ease;
@@ -691,8 +695,7 @@ section[id] {
.hero-text-link:hover,
.hero-text-link:focus-visible {
color: var(--fitme-accent);
text-decoration: underline;
text-underline-offset: 4px;
text-decoration: none;
}
.hero-cta-row .site-btn {
@@ -990,10 +993,7 @@ section[id] {
position: absolute;
inset: 0;
border-radius: 0;
background:
radial-gradient(ellipse at 30% 20%, rgba(255, 255, 255, 0.25) 0%, transparent 50%),
radial-gradient(ellipse at 70% 80%, rgba(255, 255, 255, 0.08) 0%, transparent 45%),
linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, transparent 40%, rgba(0, 0, 0, 0.1) 100%);
background: none; /* no gloss gradient over the member photos */
pointer-events: none;
z-index: 1;
}
@@ -1013,13 +1013,6 @@ section[id] {
transform: rotate(0deg) translateY(-8px) scale(1.05);
}
.trainer-item .trainer-pic:hover:after {
background:
radial-gradient(ellipse at 30% 20%, rgba(255, 255, 255, 0.32) 0%, transparent 50%),
radial-gradient(ellipse at 70% 80%, rgba(255, 255, 255, 0.10) 0%, transparent 45%),
linear-gradient(135deg, rgba(255, 255, 255, 0.18) 0%, transparent 40%, rgba(0, 0, 0, 0.08) 100%);
}
/* Same hover as the buttons / Mensalidades cards: the frame's 4px edge turns
into a conic brand-colour sweep (--pc-a / pc-spin, shared) over a dimmed
base of the same colour, with a soft glow round it. The photo covers the
@@ -1996,7 +1989,7 @@ h3.contact-title {
.consent-check a {
color: #fff;
text-decoration: underline;
text-decoration: none;
}
.consent-check a:hover {
@@ -2769,7 +2762,7 @@ h3.contact-title {
padding: 12px 4px;
margin: -12px -4px;
min-height: 44px;
text-decoration: underline;
text-decoration: none;
transition: color var(--fitme-transition);
}
@@ -3399,7 +3392,7 @@ h3.contact-title {
cursor: pointer;
}
.event-consent input { margin-top: 3px; flex: 0 0 auto; }
.event-consent a { color: var(--fitme-accent-ink); text-decoration: underline; }
.event-consent a { color: var(--fitme-accent-ink); text-decoration: none; }
@media (max-width: 575px) {
.event-form-grid { grid-template-columns: 1fr; }
}
@@ -3563,29 +3556,756 @@ h3.contact-title {
.trainer-item.is-open { z-index: 31; }
/* ── Open detail cards (team + Modalidades): the same spinning brand edge ──────
A 3px ring cut out of ::after with a mask, shown while the card is open, plus
a soft brand glow added to the existing lift shadow. ─────────────────────── */
.mod-detail::after,
.trainer-detail::after {
content: '';
position: absolute;
inset: 0;
z-index: 5;
padding: 3px;
border-radius: inherit;
background: conic-gradient(from var(--pc-a), rgba(var(--fitme-accent-rgb), .35) 0deg, rgba(var(--fitme-accent-rgb), .35) 150deg,
var(--fitme-accent) 250deg, #f4ff9c 300deg, var(--fitme-accent) 330deg,
rgba(var(--fitme-accent-rgb), .35) 360deg);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
opacity: 0;
transition: opacity .45s ease;
pointer-events: none;
}
.mod-detail.is-open::after,
.trainer-item.is-open .trainer-detail::after { opacity: 1; animation: pc-spin 3.2s linear infinite; }
.mod-detail.is-open {
box-shadow: 0 12px 30px rgba(var(--fitme-dark-rgb), .16), 0 2px 6px rgba(var(--fitme-dark-rgb), .10),
0 0 22px 2px rgba(var(--fitme-accent-rgb), .35);
}
.trainer-item.is-open .trainer-detail {
box-shadow: 0 12px 30px rgba(0, 0, 0, .32), 0 2px 6px rgba(0, 0, 0, .18),
0 0 22px 2px rgba(var(--fitme-accent-rgb), .35);
}
@media (prefers-reduced-motion: reduce) {
.mod-detail.is-open::after,
.trainer-item.is-open .trainer-detail::after { animation: none; }
}
.td-photo {
flex: 0 0 var(--td-pw, 200px);
box-sizing: border-box;
border: 4px solid var(--fitme-accent);
background: var(--fitme-surface-2);
align-self: stretch;
}
.td-photo img {
width: 100%;
height: 100%;
min-height: calc(var(--td-ph, 200px) - 8px);
object-fit: cover;
object-position: top;
display: block;
}
.td-body {
flex: 1 1 auto;
padding: 18px 22px 18px 20px;
max-height: 340px;
overflow-y: auto;
opacity: 0;
transform: translateX(-10px);
transition: opacity .12s ease, transform .12s ease;
}
.td-flip .td-body { transform: translateX(10px); }
.trainer-item.is-open .td-body {
opacity: 1;
transform: none;
transition: opacity .3s ease .18s, transform .35s ease .18s;
}
.td-name {
display: block;
font-size: 1.15rem;
font-weight: 800;
color: var(--fitme-dark);
padding-right: 20px;
}
.td-title {
display: block;
font-size: .82rem;
font-weight: 700;
color: var(--fitme-accent-ink);
margin-bottom: .6rem;
}
.td-bio {
font-size: .88rem;
color: var(--fitme-text-soft);
margin-bottom: .6rem;
}
.td-bio p:last-child { margin-bottom: 0; }
.td-block + .td-block { margin-top: .55rem; }
.td-label {
display: block;
font-size: .68rem;
font-weight: 800;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--fitme-text-muted);
margin-bottom: .25rem;
}
.td-block ul {
list-style: none;
padding: 0;
margin: 0;
}
.td-block li {
position: relative;
padding-left: 14px;
font-size: .87rem;
line-height: 1.45;
color: var(--fitme-text);
}
.td-block li:before {
content: '';
position: absolute;
left: 0;
top: .55em;
width: 6px;
height: 6px;
border-radius: 1px;
background: var(--fitme-accent);
}
.td-close {
position: absolute;
top: 0;
right: 0;
z-index: 2;
width: 44px;
height: 44px;
align-items: center;
justify-content: center;
background: transparent;
border: 0;
font-size: 1.6rem;
line-height: 1;
color: var(--fitme-text-muted);
cursor: pointer;
display: none;
}
/* Touch / small screens: a centred fixed card over a dimmed page. The "grows
out of the tile" move is done in main.js with a FLIP animation instead of
the clip-path, since the card no longer sits on top of the tile. */
@media (max-width: 767px), (hover: none) {
.trainer-detail,
.trainer-detail.td-flip {
position: fixed;
top: var(--td-top, 20vh);
left: 16px;
right: 16px;
width: auto;
max-width: 560px;
min-height: 0;
margin: 0 auto;
max-height: calc(100dvh - 48px);
flex-direction: row;
clip-path: none;
transform: none;
transform-origin: top left;
transition: opacity .15s ease, visibility 0s linear .15s;
}
.trainer-item.is-open .trainer-detail {
clip-path: none;
transform: none;
transition: opacity 0s, visibility 0s;
}
.trainer-item.is-open:after {
content: '';
position: fixed;
inset: 0;
z-index: 29;
background: rgba(0, 0, 0, .55);
}
.td-close { display: flex; }
.td-photo { flex-basis: 34%; border-width: 3px; }
.td-photo img { min-height: 180px; }
.td-body,
.td-flip .td-body { max-height: calc(100dvh - 48px); padding: 16px 16px 16px 14px; transform: none; }
}
@media (max-width: 575px) {
.trainer-detail,
.trainer-detail.td-flip { flex-direction: column; }
.td-photo { max-width: none; flex-basis: auto; }
.td-photo img { min-height: 0; height: 160px; }
}
/* ══════════════════════════════════════════════════════════════════════════
Mensalidades — cartões de preço
══════════════════════════════════════════════════════════════════════════ */
/* Same matte-black metal texture as the reviews section / footer, instead of
the plain light surface — the two dark bands now bookend the page. */
.pricing-section {
padding: 90px 0 70px;
background-color: #141517;
background-image: var(--metal-layers);
background-size: var(--metal-sizes);
color: var(--fitme-ash);
}
.pricing-section .section-title h2 { color: #fff; }
.pricing-section .section-subtitle { color: var(--fitme-ash); }
.pricing-grid {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
gap: 24px;
}
/* Spinning green edge (hover). --pc-a is a registered <angle>, so the conic
gradient's start angle can be animated — an unregistered custom property
would just jump. The gradient is centred on the mouse (--pc-x/--pc-y from
main.js), so the sweep pivots around the pointer; the card also tilts and
drifts toward it (--pc-rx/ry/mx/my), like the team cards.
Layers behind the content: ::after = the blurred gradient (the glow),
::before = the card face with a 2px border whose border-box shows the crisp
gradient (padding-box/border-box backgrounds keep the rounded corners). */
@property --pc-a {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@keyframes pc-spin {
to { --pc-a: 360deg; }
}
.price-card {
--pc-face: #fff;
--pc-edge: var(--fitme-surface-2);
--pc-bw: 1px;
--pc-scale: 1;
position: relative;
z-index: 0;
flex: 0 1 270px;
display: flex;
flex-direction: column;
background: var(--pc-face);
border: 1px solid transparent;
border-radius: 6px;
padding: 30px 26px 26px;
box-shadow: var(--fitme-shadow);
transform: perspective(900px) rotateX(var(--pc-rx, 0deg)) rotateY(var(--pc-ry, 0deg))
translate(var(--pc-mx, 0px), calc(var(--pc-my, 0px) + var(--pc-lift, 0px))) scale(var(--pc-scale));
transition: transform .45s cubic-bezier(.34, 1.35, .64, 1), box-shadow var(--fitme-transition);
}
/* While the pointer is on it, follow quickly instead of springing. */
.price-card.pc-tracking {
transition: transform .15s ease-out, box-shadow var(--fitme-transition);
}
.price-card::before,
.price-card::after {
content: '';
position: absolute;
pointer-events: none;
}
.price-card::before {
inset: calc(-1 * var(--pc-bw));
z-index: -1;
border-radius: calc(6px + var(--pc-bw));
border: var(--pc-bw) solid transparent;
background:
linear-gradient(var(--pc-face), var(--pc-face)) padding-box,
linear-gradient(var(--pc-edge), var(--pc-edge)) border-box;
}
.price-card::after {
inset: -3px;
z-index: -2;
border-radius: 10px;
background: conic-gradient(from var(--pc-a) at var(--pc-x, 50%) var(--pc-y, 50%),
rgba(var(--fitme-accent-rgb), 0) 0deg, rgba(var(--fitme-accent-rgb), 0) 150deg,
var(--fitme-accent) 250deg, #f4ff9c 300deg, var(--fitme-accent) 330deg,
rgba(var(--fitme-accent-rgb), 0) 360deg);
filter: blur(12px);
opacity: 0;
transition: opacity .35s ease;
}
.price-card:hover {
--pc-lift: -4px;
--pc-bw: 2px;
box-shadow: var(--fitme-shadow-lg);
}
.price-card:hover::before {
background:
linear-gradient(var(--pc-face), var(--pc-face)) padding-box,
conic-gradient(from var(--pc-a) at var(--pc-x, 50%) var(--pc-y, 50%),
rgba(var(--fitme-accent-rgb), 0) 0deg, rgba(var(--fitme-accent-rgb), 0) 150deg,
var(--fitme-accent) 250deg, #f4ff9c 300deg, var(--fitme-accent) 330deg,
rgba(var(--fitme-accent-rgb), 0) 360deg) border-box;
animation: pc-spin 3.2s linear infinite;
}
.price-card:hover::after {
opacity: .75;
animation: pc-spin 3.2s linear infinite;
}
.price-card.is-featured {
--pc-face: var(--fitme-dark-2);
--pc-edge: var(--fitme-accent);
--pc-bw: 2px;
border-width: 2px;
color: var(--fitme-ash);
box-shadow: 0 18px 50px rgba(var(--fitme-dark-rgb), .28);
}
@media (min-width: 992px) {
.price-card.is-featured { --pc-scale: 1.04; }
}
@media (prefers-reduced-motion: reduce) {
.price-card:hover::before,
.price-card:hover::after { animation: none; }
}
.pc-badge {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -50%);
white-space: nowrap;
background: var(--fitme-accent);
color: var(--fitme-dark);
font-size: .72rem;
font-weight: 800;
letter-spacing: .12em;
text-transform: uppercase;
padding: 6px 14px;
border-radius: 999px;
}
.pc-badge i { font-size: .65rem; }
.price-card .pc-name {
font-size: 1.35rem;
font-weight: 900;
margin: 0 0 .35rem;
color: var(--fitme-dark);
}
.price-card.is-featured .pc-name { color: #fff; }
.pc-sub {
font-size: .9rem;
line-height: 1.45;
color: var(--fitme-text-muted);
margin: 0 0 1rem;
}
.price-card.is-featured .pc-sub { color: var(--fitme-ash); }
.pc-price {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 8px;
padding: .9rem 0 1rem;
border-top: 1px solid var(--fitme-surface-2);
border-bottom: 1px solid var(--fitme-surface-2);
margin-bottom: 1rem;
}
.price-card.is-featured .pc-price { border-color: rgba(255, 255, 255, .12); }
.pc-amount {
font-size: 2.3rem;
font-weight: 900;
line-height: 1;
color: var(--fitme-dark);
}
.pc-amount.is-text { font-size: 1.35rem; }
.price-card.is-featured .pc-amount { color: var(--fitme-accent); }
.pc-suffix {
font-size: .88rem;
color: var(--fitme-text-muted);
}
.price-card.is-featured .pc-suffix { color: var(--fitme-ash); }
.pc-features {
list-style: none;
padding: 0;
margin: 0 0 1.4rem;
flex: 1 1 auto;
}
.pc-features li {
display: flex;
gap: 10px;
font-size: .92rem;
line-height: 1.4;
padding: 4px 0;
color: var(--fitme-text-soft);
}
.price-card.is-featured .pc-features li { color: #fff; }
.pc-features i {
color: var(--fitme-accent-ink);
margin-top: 3px;
font-size: .8rem;
}
.price-card.is-featured .pc-features i { color: var(--fitme-accent); }
.price-card .pc-cta {
width: 100%;
border: none;
margin-top: auto;
}
.price-card:not(.is-featured) .pc-cta {
background: var(--fitme-dark-2);
color: var(--fitme-accent);
}
.price-card:not(.is-featured) .pc-cta:hover {
background: var(--fitme-dark-3);
box-shadow: none;
}
@media (max-width: 575px) {
.pricing-section { padding: 60px 0 50px; }
.price-card { flex-basis: 100%; }
}
/* ══════════════════════════════════════════════════════════════════════════
Reviews Google
══════════════════════════════════════════════════════════════════════════ */
/* Matte black "gym machine" metal, shared by the reviews section and the
footer: fine powder-coat grain (light + dark speckle from SVG turbulence),
a soft diffuse sheen like light on a frame tube, and barely-there brushed
streaks. All inline, no image requests. */
:root {
--metal-layers:
radial-gradient(120% 70% at 22% 0%, rgba(255, 255, 255, .055), transparent 60%),
linear-gradient(112deg, transparent 38%, rgba(255, 255, 255, .03) 50%, transparent 62%),
repeating-linear-gradient(0deg, rgba(255, 255, 255, .012) 0 1px, transparent 1px 3px),
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.1' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .09 -.03'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"),
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='d'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='1' seed='7' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .22 -.06'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23d)'/%3E%3C/svg%3E");
--metal-sizes: 100% 100%, 100% 100%, 100% 3px, 180px 180px, 140px 140px;
}
.reviews-section {
padding: 90px 0 60px;
background: var(--fitme-white);
}
.reviews-summary {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 6px 12px;
margin: 1rem 0 0;
color: var(--fitme-text-muted);
}
.reviews-summary strong {
font-size: 1.8rem;
font-weight: 900;
color: var(--fitme-text);
line-height: 1;
}
.rv-stars {
color: #FBBC04;
letter-spacing: 2px;
font-size: .95rem;
}
.reviews-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 22px;
}
.review-card {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
margin: 0;
background: var(--fitme-dark-2);
border: 1px solid var(--fitme-dark-3);
border-radius: 6px;
padding: 28px 24px 24px;
}
.rv-avatar {
width: 84px;
height: 84px;
border-radius: 50%;
overflow: hidden;
border: 3px solid var(--fitme-accent);
background: var(--fitme-dark-3);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: .8rem;
flex: 0 0 auto;
}
.rv-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.rv-avatar span {
font-size: 2rem;
font-weight: 900;
color: var(--fitme-accent);
}
.rv-author {
display: flex;
flex-direction: column;
font-size: 1.05rem;
font-weight: 800;
color: #fff;
}
.rv-author a { color: #fff; }
.rv-author a:hover { color: var(--fitme-accent); }
.rv-author small {
font-size: .78rem;
font-weight: 400;
color: var(--fitme-ash);
margin-top: 2px;
}
.rv-stars-lg {
font-size: 1.3rem;
letter-spacing: 3px;
margin: .6rem 0 .2rem;
}
.rv-text {
margin: .6rem 0 0;
font-size: .85rem;
line-height: 1.55;
color: var(--fitme-ash);
display: -webkit-box;
-webkit-line-clamp: 6;
-webkit-box-orient: vertical;
overflow: hidden;
}
.reviews-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 18px;
margin-top: 2rem;
}
/* Plain text link — "Deixa a tua review" stays the one highlighted action
(.site-btn), this is secondary. */
.reviews-link {
font-weight: 700;
color: var(--fitme-text-soft);
text-decoration: none;
}
.reviews-link:hover {
color: var(--fitme-text);
}
.reviews-attribution {
margin: 1.2rem 0 0;
text-align: center;
font-size: .75rem;
color: var(--fitme-text-muted);
opacity: .8;
}
/* ══════════════════════════════════════════════════════════════════════════
Modalidades — animated icons
The FitMe icon set is inlined as <svg class="mi" data-anim="…"> (see
modalidade_icon_html()). Every drawable element has pathLength="1", so a
stroke-dasharray of 1 lets the lines draw themselves: once when the card
scrolls into view, again on hover / focus, and in the detail panel as it
opens. On top of that each family has its own small movement:
lift — weights going up (Pump, CrossFit, FBB, Core, Total Workout)
punch — a jab (Fight, Bootcamp)
breathe — slow in / out (Mind, Yoga, Pilates)
bounce — hop with squash (Zumba, Jump, Pulse, Hybrid)
flicker — flame (FitBurn)
ride — pedalling rock (Cycling)
run — running bob (Run3070, On Ramp, Localizada)
══════════════════════════════════════════════════════════════════════════ */
.ib-icon svg.mi {
width: 44px;
height: 44px;
color: #141414;
overflow: visible;
transform-box: fill-box;
transform-origin: 50% 50%;
}
.mi [pathLength] {
stroke-dasharray: 1;
stroke-dashoffset: 0;
}
@keyframes mi-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes mi-draw-h { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
/* Draw-in once as the card appears (.revealed comes from the scroll observer). */
.mod-card.revealed .mi [pathLength] {
animation: mi-draw .9s cubic-bezier(.5, 0, .2, 1) both;
}
.mod-card.revealed .mi [pathLength]:nth-child(2) { animation-delay: .12s; }
.mod-card.revealed .mi [pathLength]:nth-child(3) { animation-delay: .24s; }
.mod-card.revealed .mi [pathLength]:nth-child(n+4) { animation-delay: .34s; }
/* Hover / focus / open panel: redraw (a second keyframe name restarts it). */
.mod-card:hover .mi [pathLength],
.mod-card:focus-visible .mi [pathLength],
.mod-detail.is-open .mi [pathLength] {
animation: mi-draw-h .6s cubic-bezier(.5, 0, .2, 1) both;
}
.mod-card:hover .mi [pathLength]:nth-child(2),
.mod-detail.is-open .mi [pathLength]:nth-child(2) { animation-delay: .08s; }
.mod-card:hover .mi [pathLength]:nth-child(n+3),
.mod-detail.is-open .mi [pathLength]:nth-child(n+3) { animation-delay: .16s; }
@keyframes mi-lift {
0%, 100% { transform: translateY(0); }
30% { transform: translateY(-6px); }
55% { transform: translateY(1px); }
75% { transform: translateY(-2px); }
}
@keyframes mi-punch {
0%, 100% { transform: translateX(0) rotate(0); }
25% { transform: translateX(-3px) rotate(-6deg); }
45% { transform: translateX(7px) rotate(4deg); }
65% { transform: translateX(-1px) rotate(0); }
}
@keyframes mi-breathe {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.13); }
}
@keyframes mi-bounce {
0%, 100% { transform: translateY(0) scale(1, 1); }
18% { transform: translateY(0) scale(1.12, .86); }
50% { transform: translateY(-9px) scale(.94, 1.07); }
80% { transform: translateY(0) scale(1.06, .94); }
}
@keyframes mi-flicker {
0%, 100% { transform: scale(1) skewX(0); }
20% { transform: scale(1.06, .93) skewX(-4deg); }
40% { transform: scale(.95, 1.09) skewX(3deg); }
60% { transform: scale(1.04, .97) skewX(-2deg); }
80% { transform: scale(.98, 1.05) skewX(2deg); }
}
@keyframes mi-ride {
0%, 100% { transform: translateX(0) rotate(0); }
25% { transform: translateX(-2px) rotate(-5deg); }
75% { transform: translateX(3px) rotate(3deg); }
}
@keyframes mi-run {
0%, 100% { transform: translateY(0) rotate(0); }
25% { transform: translateY(-4px) rotate(-6deg); }
50% { transform: translateY(0) rotate(0); }
75% { transform: translateY(-4px) rotate(6deg); }
}
.mod-card:hover .mi, .mod-card:focus-visible .mi, .mod-detail.is-open .mi {
animation-duration: .9s;
animation-timing-function: cubic-bezier(.45, 0, .25, 1);
animation-fill-mode: both;
animation-delay: .1s;
}
.mod-card:hover .mi[data-anim="lift"], .mod-card:focus-visible .mi[data-anim="lift"], .mod-detail.is-open .mi[data-anim="lift"] { animation-name: mi-lift; }
.mod-card:hover .mi[data-anim="punch"], .mod-card:focus-visible .mi[data-anim="punch"], .mod-detail.is-open .mi[data-anim="punch"] { animation-name: mi-punch; animation-duration: .7s; animation-iteration-count: 2; }
.mod-card:hover .mi[data-anim="breathe"], .mod-card:focus-visible .mi[data-anim="breathe"], .mod-detail.is-open .mi[data-anim="breathe"] { animation-name: mi-breathe; animation-duration: 1.8s; animation-iteration-count: 2; }
.mod-card:hover .mi[data-anim="bounce"], .mod-card:focus-visible .mi[data-anim="bounce"], .mod-detail.is-open .mi[data-anim="bounce"] { animation-name: mi-bounce; animation-duration: .75s; animation-iteration-count: 2; }
.mod-card:hover .mi[data-anim="flicker"], .mod-card:focus-visible .mi[data-anim="flicker"], .mod-detail.is-open .mi[data-anim="flicker"] { animation-name: mi-flicker; animation-iteration-count: 3; transform-origin: 50% 100%; }
.mod-card:hover .mi[data-anim="ride"], .mod-card:focus-visible .mi[data-anim="ride"], .mod-detail.is-open .mi[data-anim="ride"] { animation-name: mi-ride; animation-duration: .6s; animation-iteration-count: 3; }
.mod-card:hover .mi[data-anim="run"], .mod-card:focus-visible .mi[data-anim="run"], .mod-detail.is-open .mi[data-anim="run"] { animation-name: mi-run; animation-duration: .6s; animation-iteration-count: 3; }
/* Detail panel (desktop): icon + name start where they sit on the card, then
glide to the vertical centre of the taller panel column as it opens
(--md-pad-open from main.js). Phones centre it with their own layout. */
@media (min-width: 768px) and (hover: hover) {
.md-card {
transition: padding-top .38s cubic-bezier(.4, 0, .2, 1);
}
.mod-detail.is-open .md-card {
padding-top: var(--md-pad-open, var(--md-pad, 0px));
transition: padding-top .55s cubic-bezier(.2, .9, .25, 1) .05s;
}
}
@media (max-width: 767px) {
.ib-icon svg.mi { width: 34px; height: 34px; }
}
@media (prefers-reduced-motion: reduce) {
.mi, .mi [pathLength] { animation: none !important; }
}
/* ── Galeria — endless horizontal strip + lightbox (site/includes/gallery.php) ──
Full-height photos alternate with 2x2 grids of four. The strip is rendered
--gal-copies times and slides by exactly one copy, so the loop is seamless;
it pauses on hover / focus. With "reduce motion" it is a plain swipeable row. */
Full-height photos alternate with 2x2 grids of four. The strip is four copies
long; main.js auto-scrolls it, supports click-and-drag / swipe, and jumps by
exactly one copy to loop. */
.gal-wrap {
--gal-h: 420px;
--gal-gap: 12px;
overflow: hidden;
overflow-x: auto; /* scrolled by main.js (auto-advance + drag) and by touch */
overflow-y: hidden;
scrollbar-width: none;
margin: 0 0 70px;
cursor: grab;
user-select: none;
-webkit-user-select: none;
-webkit-mask-image: linear-gradient(to right, transparent 0, #000 5%, #000 95%, transparent 100%);
mask-image: linear-gradient(to right, transparent 0, #000 5%, #000 95%, transparent 100%);
}
.gal-track {
display: flex;
width: max-content;
animation: gal-scroll var(--gal-dur, 60s) linear infinite;
will-change: transform;
}
.gal-marquee:hover .gal-track,
.gal-marquee:focus-within .gal-track { animation-play-state: paused; }
@keyframes gal-scroll {
to { transform: translateX(calc(-100% / var(--gal-copies, 2))); }
}
.gal-wrap::-webkit-scrollbar { display: none; }
.gal-wrap.is-dragging { cursor: grabbing; }
.gal-wrap img { -webkit-user-drag: none; }
.gal-track { display: flex; width: max-content; }
.gal-set { display: flex; flex: 0 0 auto; }
.gal-unit {
flex: 0 0 auto;
@@ -3603,11 +4323,6 @@ h3.contact-title {
@media (max-width: 991px) { .gal-wrap { --gal-h: 340px; } }
@media (max-width: 575px) { .gal-wrap { --gal-h: 260px; --gal-gap: 8px; margin-bottom: 48px; } }
@media (prefers-reduced-motion: reduce) {
.gal-wrap { overflow-x: auto; -webkit-mask-image: none; mask-image: none; scrollbar-width: thin; }
.gal-track { animation: none; }
.gal-set[aria-hidden="true"] { display: none; }
}
.gal-link {
display: block;
@@ -3680,26 +4395,16 @@ h3.contact-title {
.gal-link:hover::after { animation: none; }
}
/* ── Photos at rest: 10% desaturated (team photos also get a light brand-colour
tint), both lifted on hover. ──────────────────────────────────────────── */
/* ── Photos at rest: 10% desaturated + a discreet brand-colour tint (gallery: a
gradient fading to transparent; team: a light multiply layer), both lifted on
hover. The tint follows each gym's brand colour. ───────────────────────── */
.gal-link img { filter: saturate(.9); }
.gal-link:hover img,
.gal-link:focus-visible img { filter: saturate(1) brightness(1.05); }
.trainer-item .trainer-pic img { filter: saturate(.9); transition: filter .45s ease; }
.trainer-item .trainer-pic::before {
content: '';
position: absolute;
inset: 0;
z-index: 1;
background: color-mix(in srgb, var(--fitme-accent) 14%, #fff);
mix-blend-mode: multiply;
transition: opacity .45s ease;
pointer-events: none;
}
.trainer-item .trainer-pic:hover img { filter: saturate(1); }
.trainer-item .trainer-pic:hover::before { opacity: 0; }
/* Official YOURFIT lock-ups are wide (≈4.5:1) PNGs. In the square icon box they
were squeezed to 44px wide while keeping their height attribute, so they came
@@ -3888,3 +4593,25 @@ h3.contact-title {
.icon-box-item.is-centered .ib-icon::after,
.price-card.is-centered::before, .price-card.is-centered::after { animation: none; }
}
/* ── Links: never underlined; hover = colour shift + glow ─────────────────────────
Bootstrap underlines every <a>. Links with their own hover colour keep it (their
selectors are more specific than the :where() below) and still get the glow.
--link-hover is the readable brand shade for the background it sits on: the
dark olive on light sections, the bright brand colour on the dark ones. */
:root { --link-hover: var(--fitme-accent-ink); }
.hero-section, .trainers-section, .pricing-section, .contact-section, .footer-section,
.map-section, .event-preview-bar { --link-hover: var(--fitme-accent); }
a { text-decoration: none; }
a:where(:not(.site-btn, .gym-switch-btn, .gal-link, .skip-link, .splide__arrow, .nav-link, .navbar-brand, .td-close)) {
transition: color .3s ease, text-shadow .3s ease;
}
a:where(:not(.site-btn, .gym-switch-btn, .gal-link, .skip-link, .splide__arrow, .nav-link, .navbar-brand, .td-close)):is(:hover, :focus-visible) {
color: var(--link-hover);
text-decoration: none;
text-shadow: 0 0 10px rgba(var(--fitme-accent-rgb), .6), 0 0 22px rgba(var(--fitme-accent-rgb), .3);
}
@media (prefers-reduced-motion: reduce) {
a { transition: none !important; }
}
+76
View File
@@ -787,3 +787,79 @@ document.addEventListener('DOMContentLoaded', function () {
}
requestAnimationFrame(tick);
});
/* ── Galeria: auto-scroll + click-and-drag ───────────────────────────────────
The strip (.gal-wrap) is four identical copies long. It advances by itself,
follows a mouse drag (touch uses the native swipe), pauses while you hover or
hold it and resumes a moment after, and whenever the position leaves the two
middle copies it jumps by exactly one copy — invisible, since every copy is the
same. A drag longer than a few px swallows the click, so it doesn't open the
lightbox. */
document.addEventListener('DOMContentLoaded', function () {
var reduce = window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches;
document.querySelectorAll('.gal-marquee').forEach(function (strip) {
var wrap = strip.parentElement;
var set = strip.querySelector('.gal-set');
if (!wrap || !set) return;
var loop = 0, pos = null, hold = false, resumeAt = 0, dragging = false, moved = 0, startX = 0, startLeft = 0, last = 0;
var SPEED = 40; // px per second
function measure() { loop = set.offsetWidth; }
function normalise() {
if (!loop) return;
if (wrap.scrollLeft >= loop * 2) wrap.scrollLeft -= loop;
else if (wrap.scrollLeft < loop) wrap.scrollLeft += loop;
}
function start() { measure(); wrap.scrollLeft = loop; }
start();
window.addEventListener('load', start);
window.addEventListener('resize', measure, { passive: true });
wrap.addEventListener('scroll', normalise, { passive: true });
wrap.addEventListener('mouseenter', function () { hold = true; });
wrap.addEventListener('mouseleave', function () { if (!dragging) { hold = false; resumeAt = performance.now() + 600; } });
wrap.addEventListener('focusin', function () { hold = true; });
wrap.addEventListener('focusout', function () { hold = false; });
wrap.addEventListener('touchstart', function () { hold = true; }, { passive: true });
wrap.addEventListener('touchend', function () { hold = false; resumeAt = performance.now() + 1800; }, { passive: true });
wrap.addEventListener('pointerdown', function (e) {
if (e.pointerType === 'touch' || e.button !== 0) return;
dragging = true; moved = 0;
startX = e.clientX; startLeft = wrap.scrollLeft;
wrap.classList.add('is-dragging');
wrap.setPointerCapture(e.pointerId);
});
wrap.addEventListener('pointermove', function (e) {
if (!dragging) return;
var dx = e.clientX - startX;
moved = Math.max(moved, Math.abs(dx));
wrap.scrollLeft = startLeft - dx;
});
function endDrag(e) {
if (!dragging) return;
dragging = false;
wrap.classList.remove('is-dragging');
try { wrap.releasePointerCapture(e.pointerId); } catch (err) {}
resumeAt = performance.now() + 1200;
}
wrap.addEventListener('pointerup', endDrag);
wrap.addEventListener('pointercancel', endDrag);
// After a real drag, the click that follows must not open the lightbox.
wrap.addEventListener('click', function (e) {
if (moved > 5) { e.preventDefault(); e.stopPropagation(); moved = 0; }
}, true);
function tick(now) {
requestAnimationFrame(tick);
var dt = last ? Math.min(now - last, 100) : 16; last = now;
if (reduce || document.hidden || dragging || hold || now < resumeAt) { pos = null; return; }
// scrollLeft only keeps whole pixels, so a ~0.6px/frame step would round
// away: carry the fraction in our own float and write it back.
if (pos === null || Math.abs(wrap.scrollLeft - pos) > 2) pos = wrap.scrollLeft;
pos += SPEED * dt / 1000;
wrap.scrollLeft = pos;
}
requestAnimationFrame(tick);
});
});
+12 -13
View File
@@ -1,11 +1,11 @@
<?php
// Gallery partial — an endless horizontal strip that scrolls by itself (home
// "instalações" and the CrossFit page). Pure CSS: full-height photos alternate
// with 2x2 grids of four; the strip is repeated so the loop is seamless and it
// pauses on hover / focus. With "reduce motion" it becomes a plain swipeable
// row. Clicking a tile opens it full size in a <dialog> lightbox with prev /
// next, arrow keys, Esc and swipe (main.js); the repeated copies are hidden
// from assistive tech and open the same photo.
// Gallery partial — an endless horizontal strip (home "instalações" and the
// CrossFit page): full-height photos alternate with 2x2 grids of four. main.js
// scrolls it by itself, lets you click-and-drag (mouse) or swipe (touch), pauses
// while you interact and loops seamlessly over the repeated copies. Clicking a
// tile (without dragging) opens it full size in a <dialog> lightbox with prev /
// next, arrow keys, Esc and swipe; the repeated copies are hidden from assistive
// tech and open the same photo.
$files = glob(rtrim($gallery_path, '/') . '/*.jpg');
if (!$files || !is_array($files)) $files = [];
// Column span per tile, cycled: w = two columns, s = one.
@@ -31,13 +31,12 @@ $gal_label = $gallery_h2 ?? 'Instalações';
$units[] = ['grid' => $grid, 'idx' => range($i, $i + $take - 1)];
$i += $take; $k++;
}
// The strip is rendered several times so the endless scroll has no gap: two
// copies is enough for a normal gallery, a short one needs four.
$copies = $n >= 8 ? 2 : 4;
$dur = max(30, $n * 5); // seconds for one pass over the originals
// The strip is rendered four times: main.js keeps the scroll position inside
// the middle copies and jumps by exactly one copy, so it loops without a seam.
$copies = 4;
?>
<div class="gal-wrap">
<div class="gal-marquee" style="--gal-copies: <?= $copies ?>; --gal-dur: <?= $dur * $copies ?>s">
<div class="gal-marquee">
<div class="gal-track">
<?php for ($c = 0; $c < $copies; $c++): $dup = $c > 0; ?>
<div class="gal-set"<?= $dup ? ' aria-hidden="true"' : '' ?>>
@@ -53,7 +52,7 @@ $gal_label = $gallery_h2 ?? 'Instalações';
<a href="<?= htmlspecialchars($src) ?>" class="gal-link" data-index="<?= $gi ?>"<?= $dup ? ' data-dup="1" tabindex="-1"' : '' ?> aria-label="Ampliar: <?= htmlspecialchars($alt) ?>">
<img src="<?= htmlspecialchars($src) ?>" alt="<?= $dup ? '' : htmlspecialchars($alt) ?>"
<?= $dim[0] ? 'width="' . (int) $dim[0] . '" height="' . (int) $dim[1] . '"' : '' ?>
<?= $gi > 2 || $dup ? 'loading="lazy"' : '' ?> decoding="async">
<?= $gi > 2 || $dup ? 'loading="lazy"' : '' ?> decoding="async" draggable="false">
</a>
</div>
<?php endforeach; ?>