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 pill (follows the gym's brand colour) ─────────────────────────────────────────────────────── */
.crossfit-nav-link { .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; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: .5px; letter-spacing: .5px;
border: 2px solid var(--fitme-accent-ink); border: 2px solid var(--fitme-accent);
border-radius: var(--fitme-radius); border-radius: var(--fitme-radius);
padding: 6px 14px !important; 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 { .crossfit-nav-link:after {
display: none; 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) */ generic .nav-item.active .nav-link transparent-background rule above) */
.crossfit-nav-link:hover, .crossfit-nav-link:hover,
.nav-item.active .crossfit-nav-link { .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); border-color: var(--fitme-accent);
color: var(--fitme-on-accent) !important; color: #fff !important;
} }
/* ── Other-gym button (right end of the navbar) ────────────────────────────── */ /* ── Other-gym button (right end of the navbar) ────────────────────────────── */
@@ -683,7 +686,8 @@ section[id] {
padding: 4px 8px; padding: 4px 8px;
color: #fff; color: #fff;
font: inherit; font: inherit;
font-weight: 600; font-weight: 300;
letter-spacing: .02em;
text-shadow: 0 1px 6px rgba(0, 0, 0, .6); text-shadow: 0 1px 6px rgba(0, 0, 0, .6);
cursor: pointer; cursor: pointer;
transition: color .3s ease; transition: color .3s ease;
@@ -691,8 +695,7 @@ section[id] {
.hero-text-link:hover, .hero-text-link:hover,
.hero-text-link:focus-visible { .hero-text-link:focus-visible {
color: var(--fitme-accent); color: var(--fitme-accent);
text-decoration: underline; text-decoration: none;
text-underline-offset: 4px;
} }
.hero-cta-row .site-btn { .hero-cta-row .site-btn {
@@ -990,10 +993,7 @@ section[id] {
position: absolute; position: absolute;
inset: 0; inset: 0;
border-radius: 0; border-radius: 0;
background: background: none; /* no gloss gradient over the member photos */
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%);
pointer-events: none; pointer-events: none;
z-index: 1; z-index: 1;
} }
@@ -1013,13 +1013,6 @@ section[id] {
transform: rotate(0deg) translateY(-8px) scale(1.05); 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 /* 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 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 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 { .consent-check a {
color: #fff; color: #fff;
text-decoration: underline; text-decoration: none;
} }
.consent-check a:hover { .consent-check a:hover {
@@ -2769,7 +2762,7 @@ h3.contact-title {
padding: 12px 4px; padding: 12px 4px;
margin: -12px -4px; margin: -12px -4px;
min-height: 44px; min-height: 44px;
text-decoration: underline; text-decoration: none;
transition: color var(--fitme-transition); transition: color var(--fitme-transition);
} }
@@ -3399,7 +3392,7 @@ h3.contact-title {
cursor: pointer; cursor: pointer;
} }
.event-consent input { margin-top: 3px; flex: 0 0 auto; } .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) { @media (max-width: 575px) {
.event-form-grid { grid-template-columns: 1fr; } .event-form-grid { grid-template-columns: 1fr; }
} }
@@ -3563,29 +3556,756 @@ h3.contact-title {
.trainer-item.is-open { z-index: 31; } .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) ── /* ── Galeria — endless horizontal strip + lightbox (site/includes/gallery.php) ──
Full-height photos alternate with 2x2 grids of four. The strip is rendered Full-height photos alternate with 2x2 grids of four. The strip is four copies
--gal-copies times and slides by exactly one copy, so the loop is seamless; long; main.js auto-scrolls it, supports click-and-drag / swipe, and jumps by
it pauses on hover / focus. With "reduce motion" it is a plain swipeable row. */ exactly one copy to loop. */
.gal-wrap { .gal-wrap {
--gal-h: 420px; --gal-h: 420px;
--gal-gap: 12px; --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; 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%); -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%); mask-image: linear-gradient(to right, transparent 0, #000 5%, #000 95%, transparent 100%);
} }
.gal-track { .gal-wrap::-webkit-scrollbar { display: none; }
display: flex; .gal-wrap.is-dragging { cursor: grabbing; }
width: max-content; .gal-wrap img { -webkit-user-drag: none; }
animation: gal-scroll var(--gal-dur, 60s) linear infinite; .gal-track { display: flex; width: max-content; }
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-set { display: flex; flex: 0 0 auto; } .gal-set { display: flex; flex: 0 0 auto; }
.gal-unit { .gal-unit {
flex: 0 0 auto; flex: 0 0 auto;
@@ -3603,11 +4323,6 @@ h3.contact-title {
@media (max-width: 991px) { .gal-wrap { --gal-h: 340px; } } @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 (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 { .gal-link {
display: block; display: block;
@@ -3680,26 +4395,16 @@ h3.contact-title {
.gal-link:hover::after { animation: none; } .gal-link:hover::after { animation: none; }
} }
/* ── Photos at rest: 10% desaturated (team photos also get a light brand-colour /* ── Photos at rest: 10% desaturated + a discreet brand-colour tint (gallery: a
tint), both lifted on hover. ──────────────────────────────────────────── */ 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 img { filter: saturate(.9); }
.gal-link:hover img, .gal-link:hover img,
.gal-link:focus-visible img { filter: saturate(1) brightness(1.05); } .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 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 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 /* 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 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, .icon-box-item.is-centered .ib-icon::after,
.price-card.is-centered::before, .price-card.is-centered::after { animation: none; } .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); 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 <?php
// Gallery partial — an endless horizontal strip that scrolls by itself (home // Gallery partial — an endless horizontal strip (home "instalações" and the
// "instalações" and the CrossFit page). Pure CSS: full-height photos alternate // CrossFit page): full-height photos alternate with 2x2 grids of four. main.js
// with 2x2 grids of four; the strip is repeated so the loop is seamless and it // scrolls it by itself, lets you click-and-drag (mouse) or swipe (touch), pauses
// pauses on hover / focus. With "reduce motion" it becomes a plain swipeable // while you interact and loops seamlessly over the repeated copies. Clicking a
// row. Clicking a tile opens it full size in a <dialog> lightbox with prev / // tile (without dragging) opens it full size in a <dialog> lightbox with prev /
// next, arrow keys, Esc and swipe (main.js); the repeated copies are hidden // next, arrow keys, Esc and swipe; the repeated copies are hidden from assistive
// from assistive tech and open the same photo. // tech and open the same photo.
$files = glob(rtrim($gallery_path, '/') . '/*.jpg'); $files = glob(rtrim($gallery_path, '/') . '/*.jpg');
if (!$files || !is_array($files)) $files = []; if (!$files || !is_array($files)) $files = [];
// Column span per tile, cycled: w = two columns, s = one. // 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)]; $units[] = ['grid' => $grid, 'idx' => range($i, $i + $take - 1)];
$i += $take; $k++; $i += $take; $k++;
} }
// The strip is rendered several times so the endless scroll has no gap: two // The strip is rendered four times: main.js keeps the scroll position inside
// copies is enough for a normal gallery, a short one needs four. // the middle copies and jumps by exactly one copy, so it loops without a seam.
$copies = $n >= 8 ? 2 : 4; $copies = 4;
$dur = max(30, $n * 5); // seconds for one pass over the originals
?> ?>
<div class="gal-wrap"> <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"> <div class="gal-track">
<?php for ($c = 0; $c < $copies; $c++): $dup = $c > 0; ?> <?php for ($c = 0; $c < $copies; $c++): $dup = $c > 0; ?>
<div class="gal-set"<?= $dup ? ' aria-hidden="true"' : '' ?>> <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) ?>"> <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) ?>" <img src="<?= htmlspecialchars($src) ?>" alt="<?= $dup ? '' : htmlspecialchars($alt) ?>"
<?= $dim[0] ? 'width="' . (int) $dim[0] . '" height="' . (int) $dim[1] . '"' : '' ?> <?= $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> </a>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>