diff --git a/site/assets/css/style.css b/site/assets/css/style.css index b0408f3..303c18d 100644 --- a/site/assets/css/style.css +++ b/site/assets/css/style.css @@ -670,12 +670,31 @@ section[id] { .hero-cta-row { display: flex; + flex-direction: column; /* the button, then the trial link as plain text under it */ + align-items: center; gap: 14px; - justify-content: center; - flex-wrap: wrap; margin-top: 2.5rem; } +/* "Marca uma aula experimental": text only, under the main button. */ +.hero-text-link { + background: none; + border: 0; + padding: 4px 8px; + color: #fff; + font: inherit; + font-weight: 600; + text-shadow: 0 1px 6px rgba(0, 0, 0, .6); + cursor: pointer; + transition: color .3s ease; +} +.hero-text-link:hover, +.hero-text-link:focus-visible { + color: var(--fitme-accent); + text-decoration: underline; + text-underline-offset: 4px; +} + .hero-cta-row .site-btn { margin-top: 0; } @@ -3544,782 +3563,50 @@ 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 , 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: ''; - 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%; +/* ── 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. */ +.gal-wrap { + --gal-h: 420px; + --gal-gap: 12px; 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: underline; - text-underline-offset: 3px; -} - -.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 (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 — masonry + lightbox (site/includes/gallery.php) - Without JS: a plain CSS grid (photos cropped to equal tiles). With JS - (.is-masonry): main.js positions every tile absolutely — shortest column - first, true aspect ratio, some tiles two columns wide — and sets the list's - height. 4 columns on desktop, 3 on tablets, 2 on phones. - ══════════════════════════════════════════════════════════════════════════ */ -.gal-masonry { - list-style: none; margin: 0 0 70px; - padding: 0; + -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-set { display: flex; flex: 0 0 auto; } +.gal-unit { + flex: 0 0 auto; + height: var(--gal-h); + margin-right: var(--gal-gap); +} +.gal-unit--full { width: calc(var(--gal-h) * .82); } +.gal-unit--grid { + width: calc(var(--gal-h) * 1.05); display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - grid-auto-rows: 170px; - grid-auto-flow: dense; - gap: 12px; + grid-template: 1fr 1fr / 1fr 1fr; + gap: var(--gal-gap); } +.gal-tile { position: relative; margin: 0; min-width: 0; min-height: 0; height: 100%; } -.gal-tile { position: relative; margin: 0; } -.gal-w { grid-column: span 2; } - -/* JS masonry: absolute tiles, sized and placed by main.js. */ -.gal-masonry.is-masonry { - display: block; - position: relative; -} - -.gal-masonry.is-masonry .gal-tile { - position: absolute; - top: 0; - left: 0; - transition: transform .5s cubic-bezier(.2, .8, .2, 1), width .5s cubic-bezier(.2, .8, .2, 1), height .5s cubic-bezier(.2, .8, .2, 1), opacity .6s ease; -} - -/* Entrance: tiles rise in one after another when the gallery comes into view - (main.js adds .is-in with a per-tile delay). */ -.gal-masonry.is-masonry .gal-tile:not(.is-in) { - opacity: 0; -} - -.gal-masonry.is-masonry .gal-tile .gal-link { - transition: transform .6s cubic-bezier(.2, .8, .2, 1); -} - -.gal-masonry.is-masonry .gal-tile:not(.is-in) .gal-link { - transform: translateY(24px) scale(.97); +@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 { @@ -4393,27 +3680,12 @@ h3.contact-title { .gal-link:hover::after { animation: none; } } -/* ── 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. ───────────────────────── */ +/* ── Photos at rest: 10% desaturated (team photos also get a light brand-colour + tint), both lifted on hover. ──────────────────────────────────────────── */ .gal-link img { filter: saturate(.9); } .gal-link:hover img, .gal-link:focus-visible img { filter: saturate(1) brightness(1.05); } -/* Gallery tint: a brand-colour gradient that fades from the bottom edge to fully - transparent, laid over the tile (the link's own pseudo-elements are taken by - the hover edge); it dissolves on hover. */ -.gal-tile::after { - content: ''; - position: absolute; - inset: 0; - border-radius: var(--fitme-radius); - background: linear-gradient(to top, rgba(var(--fitme-accent-rgb), .28) 0%, rgba(var(--fitme-accent-rgb), 0) 80%); - pointer-events: none; - transition: opacity .45s ease; -} -.gal-tile:hover::after, -.gal-tile:focus-within::after { opacity: 0; } .trainer-item .trainer-pic img { filter: saturate(.9); transition: filter .45s ease; } .trainer-item .trainer-pic::before { @@ -4460,14 +3732,6 @@ h3.contact-title { .md-card .ib-icon img[src*="yourfit"] { width: 106px; } } -@media (max-width: 991px) { - .gal-masonry { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: 150px; } -} - -@media (max-width: 575px) { - .gal-masonry { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: 120px; gap: 8px; margin-bottom: 48px; } -} - /* Lightbox */ .gal-lightbox { width: 100vw; @@ -4568,6 +3832,59 @@ h3.contact-title { @media (prefers-reduced-motion: reduce) { .gal-link img, .gal-lb-figure img { transition: none; animation: none; } - .gal-masonry.is-masonry .gal-tile, - .gal-masonry.is-masonry .gal-tile .gal-link { transition: none; } +} + +/* ── "In the middle of the screen" = hovered ───────────────────────────────────── + main.js adds .is-centered to the photo / card / icon that sits on the vertical + middle of the viewport (and, inside the sliding strips, on the horizontal + middle too), so scrolling past shows the same state as a hover. These mirror + the :hover rules above. ─────────────────────────────────────────────────── */ +.gal-tile.is-centered .gal-link img { transform: scale(1.06); filter: saturate(1) brightness(1.05); } +.gal-tile.is-centered .gal-link::before { opacity: 1; } +.gal-tile.is-centered .gal-link::after { opacity: 1; animation: pc-spin 3.2s linear infinite; } + +.trainer-item .trainer-pic.is-centered, +#powerRotation>div:nth-child(even) .trainer-pic.is-centered { + transform: rotate(0deg) translateY(-8px) scale(1.05); + border-color: transparent; + background: + linear-gradient(var(--fitme-dark), var(--fitme-dark)) padding-box, + 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) border-box; + box-shadow: 0 0 18px 2px rgba(var(--fitme-accent-rgb), .4), var(--fitme-shadow-lg); + animation: pc-spin 3.2s linear infinite; +} +.trainer-item .trainer-pic.is-centered img { filter: saturate(1); } +.trainer-item .trainer-pic.is-centered::before { opacity: 0; } + +.icon-box-item.is-centered { transform: translateY(-3px); } +.icon-box-item.is-centered .ib-icon { + background: color-mix(in srgb, var(--fitme-accent) 10%, #000); + color: #fff; + transform: scale(1.06); + box-shadow: 0 0 16px 1px rgba(var(--fitme-accent-rgb), .4); +} +.icon-box-item.is-centered .ib-icon::after { opacity: 1; animation: pc-spin 3.2s linear infinite; } +.icon-box-item.is-centered .ib-icon svg.mi { color: #fff; } +.icon-box-item.is-centered .ib-icon img { filter: brightness(0) invert(1); mix-blend-mode: normal; } +.icon-box-item.is-centered .ib-icon:has(img[src*="yourfit"]) { background: #fff; } +.icon-box-item.is-centered .ib-icon img[src*="yourfit"] { filter: none; } + +.price-card.is-centered { --pc-lift: -4px; --pc-bw: 2px; box-shadow: var(--fitme-shadow-lg); } +.price-card.is-centered::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.is-centered::after { opacity: .75; animation: pc-spin 3.2s linear infinite; } +@media (prefers-reduced-motion: reduce) { + .gal-tile.is-centered .gal-link::after, + .trainer-item .trainer-pic.is-centered, + .icon-box-item.is-centered .ib-icon::after, + .price-card.is-centered::before, .price-card.is-centered::after { animation: none; } } diff --git a/site/assets/js/main.js b/site/assets/js/main.js index 2fdec7c..060b724 100644 --- a/site/assets/js/main.js +++ b/site/assets/js/main.js @@ -710,10 +710,11 @@ document.addEventListener('DOMContentLoaded', function () { opens the instead: prev / next buttons, ← → keys, Esc, swipe on touch, and a click on the dark backdrop closes it. */ document.addEventListener('DOMContentLoaded', function () { - document.querySelectorAll('.gal-masonry').forEach(function (grid) { + document.querySelectorAll('.gal-marquee').forEach(function (grid) { var box = grid.parentElement.nextElementSibling; if (!box || !box.classList.contains('gal-lightbox') || typeof box.showModal !== 'function') return; - var links = Array.prototype.slice.call(grid.querySelectorAll('.gal-link')); + var all = Array.prototype.slice.call(grid.querySelectorAll('.gal-link')); + var links = all.filter(function (a) { return !a.hasAttribute('data-dup'); }); // the repeated copies open the same photo var img = box.querySelector('.gal-lb-figure img'); var count = box.querySelector('.gal-lb-count'); var current = 0; @@ -728,10 +729,10 @@ document.addEventListener('DOMContentLoaded', function () { count.textContent = (current + 1) + ' / ' + links.length; } - links.forEach(function (a, i) { + all.forEach(function (a) { a.addEventListener('click', function (e) { e.preventDefault(); - show(i); + show(+a.getAttribute('data-index') || 0); box.showModal(); }); }); @@ -744,7 +745,7 @@ document.addEventListener('DOMContentLoaded', function () { if (e.key === 'ArrowLeft') show(current - 1); else if (e.key === 'ArrowRight') show(current + 1); }); - box.addEventListener('close', function () { links[current].focus(); }); + box.addEventListener('close', function () { if (links[current]) links[current].focus(); }); var x0 = null; box.addEventListener('touchstart', function (e) { x0 = e.touches[0].clientX; }, { passive: true }); @@ -757,91 +758,32 @@ document.addEventListener('DOMContentLoaded', function () { }); }); -/* ── Galeria: masonry layout ───────────────────────────────────────────────── - A small hand-rolled masonry (no library): the grid is split into columns - (4 / 3 / 2 by width), and each tile — at its photo's true aspect ratio, from - the width/height attributes the PHP writes — is dropped into the shortest - column; tiles marked .gal-w take two adjacent columns (the pair whose taller - one is lowest), which is what staggers the rows. Tiles are absolutely - positioned with transforms, so a relayout on resize glides into place. - The first time the gallery scrolls into view the tiles rise in one by one. */ +/* ── Scroll "hover": what sits in the middle of the screen looks hovered ─────── + Photos, cards and icons get .is-centered while their box crosses the middle + of the viewport (±12% of its height); inside a sliding strip (gallery, + Modalidades) they must also cross the horizontal middle. Throttled, paused + while the tab is hidden, skipped with "reduce motion". The CSS mirrors the + :hover rules (style.css, "In the middle of the screen"). */ document.addEventListener('DOMContentLoaded', function () { - document.querySelectorAll('.gal-masonry').forEach(function (grid) { - var tiles = Array.prototype.slice.call(grid.querySelectorAll('.gal-tile')); - if (!tiles.length) return; - grid.classList.add('is-masonry'); - - function ratio(tile) { - var img = tile.querySelector('img'); - var w = +img.getAttribute('width') || img.naturalWidth || 4; - var h = +img.getAttribute('height') || img.naturalHeight || 3; - return h / w; - } - function layout() { - var width = grid.clientWidth; - var cols = width >= 900 ? 4 : width >= 520 ? 3 : 2; - var gap = width >= 520 ? 12 : 8; - var colW = (width - gap * (cols - 1)) / cols; - var heights = []; - for (var c = 0; c < cols; c++) heights.push(0); - - tiles.forEach(function (tile) { - var span = (tile.classList.contains('gal-w') && cols > 1) ? 2 : 1; - // Best column (or pair of columns) = lowest top edge. - var pick = function (span) { - var best = 0, bestTop = Infinity, waste = 0; - for (var i = 0; i <= cols - span; i++) { - var cells = heights.slice(i, i + span); - var top = Math.max.apply(null, cells); - if (top < bestTop - 0.5) { bestTop = top; best = i; waste = top - Math.min.apply(null, cells); } - } - return [best, bestTop, waste]; - }; - var p = pick(span); - // A wide tile whose two columns end at different heights would - // leave a hole under the shorter one — use a single column then. - if (span === 2 && p[2] > 60) { span = 1; p = pick(1); } - var best = p[0], bestTop = p[1]; - var w = colW * span + gap * (span - 1); - var h = Math.round(w * ratio(tile)); - tile.style.width = w + 'px'; - tile.style.height = h + 'px'; - tile.style.transform = 'translate(' + (best * (colW + gap)) + 'px,' + bestTop + 'px)'; - for (var j = best; j < best + span; j++) heights[j] = bestTop + h + gap; - }); - grid.style.height = Math.max(0, Math.max.apply(null, heights) - gap) + 'px'; - } - - layout(); - // Re-lay out whenever the grid's own width changes (window resize, but - // also a gallery that was laid out while hidden / zero-width). - var t, lastW = grid.clientWidth; - var relayout = function () { - if (grid.clientWidth === lastW) return; - lastW = grid.clientWidth; - clearTimeout(t); t = setTimeout(layout, 80); - }; - if ('ResizeObserver' in window) new ResizeObserver(relayout).observe(grid); - window.addEventListener('resize', relayout, { passive: true }); // belt and braces; relayout() is idempotent - // Re-run once images report their size, in case an attribute was missing. - tiles.forEach(function (tile) { - var img = tile.querySelector('img'); - if (!img.getAttribute('width')) img.addEventListener('load', layout, { once: true }); - }); - - // Staggered entrance. - var enter = function () { - tiles.forEach(function (tile, i) { - setTimeout(function () { tile.classList.add('is-in'); }, Math.min(i, 12) * 70); - }); - }; - if ('IntersectionObserver' in window) { - var io = new IntersectionObserver(function (entries) { - if (entries.some(function (e) { return e.isIntersecting; })) { enter(); io.disconnect(); } - }, { rootMargin: '0px 0px -80px 0px' }); - io.observe(grid); - } else { - enter(); - } + if (window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches) return; + var items = Array.prototype.slice.call(document.querySelectorAll( + '.gal-tile, .trainer-item .trainer-pic, .icon-box-item, .price-card')).map(function (el) { + return { el: el, strip: !!el.closest('.gal-marquee, .mod-scroll'), on: false }; }); + if (!items.length) return; + var last = 0; + function tick(now) { + requestAnimationFrame(tick); + if (document.hidden || now - last < 120) return; + last = now; + var vw = window.innerWidth, vh = window.innerHeight, cy = vh / 2, band = vh * 0.12; + items.forEach(function (it) { + var r = it.el.getBoundingClientRect(); + var on = r.bottom > 0 && r.top < vh && + r.top <= cy + band && r.bottom >= cy - band && + (!it.strip || (r.left <= vw / 2 && r.right >= vw / 2)); + if (on !== it.on) { it.on = on; it.el.classList.toggle('is-centered', on); } + }); + } + requestAnimationFrame(tick); }); diff --git a/site/includes/gallery.php b/site/includes/gallery.php index 4b9972c..bfbcae0 100644 --- a/site/includes/gallery.php +++ b/site/includes/gallery.php @@ -1,11 +1,11 @@ -// lightbox with prev / next, arrow keys, Esc and swipe (main.js). +// 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 lightbox with prev / +// next, arrow keys, Esc and swipe (main.js); 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. @@ -21,23 +21,48 @@ $gal_label = $gallery_h2 ?? 'Instalações'; - -
- += 4); + $take = $grid ? 4 : 1; + $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 +?> + diff --git a/site/index.php b/site/index.php index 0ef7a03..78babce 100644 --- a/site/index.php +++ b/site/index.php @@ -77,7 +77,7 @@ $hero_img = $gym['hero_image'] !== '' ? $gym['hero_image'] : 'site/assets/img


Inscreve-te Já! - +