feat(navbar): match head shows the other gym's brand, centred logo on CrossFit, mobile band

The match head is painted in the other gym's colours with a strip in this site's accent; below 992px it is a slashed band at the bottom of the open menu (menu height from the real header height). The CrossFit page gets the centred logo crossfade, the hero logo has a fixed 52px height, and the hero h1 shadow is softer with the frosted blur at .8.
This commit is contained in:
andre committed 2026-10-26 12:00:56 +00:00
1 parent 4b54a777b7
commit e7ab661f4c
5 files changed
+75 -23

No files matched your search

+1 -1
View File
@@ -79,7 +79,7 @@ Since 2026-09-25, one install serves several physical gyms: the `gyms` table, on
folder per gym (`/3070/`, `/<postal-code>/`), content rows carry `gym_id`, and the
backoffice has an "a editar" gym switcher (`admin_gym_id()` /
`admin_gym()` in `config/functions_gyms.php`). `/` and `/home` 301 to the default gym (`is_default`); the other published gyms appear as highlighted buttons (`.gym-switch-btn`) at the right end of the public navbar (`site/includes/navbar.php`). `site/gym_chooser.php` is no longer used.
Each gym has a brand colour (`gyms.brand_color`, `#RRGGBB`, migration `migration_20261013_gym_brand_color.sql`; gym 1 = green `#C2D500`, gym 2 = red): set in Gestão → Ginásios, emitted as `:root` overrides by `gym_brand_css()` in `site/includes/include.php`. New components must use `--fitme-accent*` / `--fitme-on-accent`, never a hard-coded colour; the CrossFit nav pill follows the brand colour too. The public navbar's bottom edge is an always-on, slow (14s) sweep in the brand colour (`.header-section::before/::after`, `nav-sweep`, static with reduced motion). On the home page (`$nav_center_logo` set in `site/index.php`, shared by `_open.php`) the header starts taller (desktop >= 992px) with a second, big (180px) copy of the logo centred (`.navbar-brand-hero`, `aria-hidden`); once `scrollY >= 40` (script in `navbar.php` toggles `.is-top`) the bar shrinks, the big logo fades out and the 150px left logo fades in (opacity crossfade, no movement). The hero gets `padding-top: 120px` on desktop so its event card clears the tall bar. On desktop the brand is absolute at the left and the links are centred on the full bar width; nav padding and logo width live in CSS, not inline. The other-gym buttons (`.gym-switch`) sit in the **match head**: an absolute dark block at the right end of the bar (full bar height, width follows its buttons), its left side cut on the brand slash (22px) with a 5px accent strip and an ember glow (`.gym-switch::before/::after`); logos inside it are lightness-flipped (`invert(1) hue-rotate(180deg)`) so dark lettering reads on the dark head. 992-1199px: links shrink (`.8125rem`, `--bs-navbar-nav-link-padding-x: .35rem`) so the 3070 menu clears the logo and head. Menu links are `.875rem` bold. Below 992px the open menu fills the screen under the bar (absolute, `100dvh - 60px`, links/buttons centred, opacity fade; not `position:fixed` because the header's backdrop-filter is its containing block). The bar is liquid glass in Chromium: `backdrop-filter: url(#nav-glass) blur(6px) saturate(180%)` (`#nav-glass` in `include.php`: blurred low-frequency `feTurbulence` -> `feDisplacementMap`, bends the page behind it), gated by `@supports (-webkit-app-region: drag)` (Chromium-only property); Safari/Firefox keep the plain blur. **Brand slash template:** the cut corners of the "in" box are one rule in `style.css` ("Brand slash (template)"): `class="fx-slash"` (both ends) or `fx-slash-start` (left only), size via `--slash` (default 4px); pseudo-elements are added to that rule's selector list. Used by the nav and section-title underlines and the CrossFit pill (`--slash: 8px`, no radius); never paste the polygon again. The hero subtitle (`.hero-text h1 span`, gym `hero_subtitle`) is a block strapline at `.42em` of the h1, tracked out. Logos (`.navbar-brand img`, `.gym-switch-logo`) get the same `#pt-noise` grain filter as h1/h2, and `.hero-event-go`, `.cta-btn`, `.btn-primary` joined the `--noise` button list. Section-title underlines (`.section-title.deco h2:after`) use the same slashed 4px bar as the nav underline. The hero event button text uses `--fitme-on-accent` (white on red). The nav underline (`.nav-link:after`) is a 4px bar 3px under the text with slashed ends (`clip-path`, like the "in" box of the FitMe in logo). The hero event card (`.hero-event`) is square (no radius on card, badge or button), taller (88px min) and has a defocus gradient: `::after` is a `backdrop-filter: blur()` layer masked left->right, sharp on the left and most blurred on the right; the card itself must not get filter/backdrop-filter/opacity/mask or the blur stops reaching the hero photo. Each gym can have a `logo` (same migration, plus `migration_20261026_gym_logo.sql`: the column was added to the 2026-10-13 file after it had already run on dev, so dev had no `logo` column until 2026-10-26 — never edit a migration that has run, add a new one); the header image and the logo are uploaded in Gestão → Ginásios (`gyms_save_image()` → `uploads/gyms/`, validated by content, SVG through `svg_is_safe()`), not typed as paths; `db_gyms_create/update` skip `brand_color`/`logo` while the migration isn't applied (`_gyms_cols()`); the navbar button for another gym is drawn in that gym's brand colour (`gym_brand_vars()` inline); with a logo (`gym_logo()`: the gym's own, or for the default gym the bundled `logo_black.svg`) the button shows the logo in full colour straight on the frosted navbar, with no face and no darkening on hover (`.gym-switch-btn--logo`: thin brand outline, hover = spinning ring only, no glow, a thin dark `drop-shadow` outline on the image for contrast (a white halo washed out the lime FitMe 3070 logo); name as alt), without one it shows the name. The navbar brand on each gym's page uses that gym's logo too. Uploads (header + logo) also accept JPEG XL (`.jxl`, detected by signature in `upload_image_ext()` when the caller allows `'jxl'`; the gyms form and gallery photos (`db_gallery_image_upload()`) do; `AddTyLine truncated
Each gym has a brand colour (`gyms.brand_color`, `#RRGGBB`, migration `migration_20261013_gym_brand_color.sql`; gym 1 = green `#C2D500`, gym 2 = red): set in Gestão → Ginásios, emitted as `:root` overrides by `gym_brand_css()` in `site/includes/include.php`. New components must use `--fitme-accent*` / `--fitme-on-accent`, never a hard-coded colour; the CrossFit nav pill follows the brand colour too. The public navbar's bottom edge is an always-on, slow (14s) sweep in the brand colour (`.header-section::before/::after`, `nav-sweep`, static with reduced motion). On the home page and the CrossFit page (`$nav_center_logo` set in `site/index.php` / `site/crossfit.php`, shared by `_open.php`; the CrossFit video header no longer prints its own logo, only a `visually-hidden` h1) the header starts taller (desktop >= 992px) with a second, big copy of the logo centred (fixed 52px height, so tall logos like the CrossFit one clear the links) (`.navbar-brand-hero`, `aria-hidden`); once `scrollY >= 40` (script in `navbar.php` toggles `.is-top`) the bar shrinks, the big logo fades out and the 150px left logo fades in (opacity crossfade, no movement). The hero gets `padding-top: 120px` on desktop so its event card clears the tall bar. On desktop the brand is absolute at the left and the links are centred on the full bar width; nav padding and logo width live in CSS, not inline. The other-gym buttons (`.gym-switch`) sit in the **match head**, a glimpse into the other site: painted in the first other gym's brand (`navbar.php` puts `gym_brand_vars()` of `$other_gyms[0]` on `.gym-switch`), its accent as the face with a shadow inside the cut, a 5px strip in THIS site's accent on the slash (`--site-accent`, resolved on `.header-section` before the inline vars swap `--fitme-accent`), and its logo in that gym's on-accent ink (`brightness(0)`, plus `invert(1)` with `.gym-switch--light-ink` when the on-accent is white). Paint is shared; geometry per breakpoint: desktop = absolute block at the right end of the bar (full height, width follows its buttons), left side cut on the brand slash (22px); below 992px = a full-width band across the bottom of the open menu, top edge slashed (18px). 992-1199px: links shrink (`.8125rem`, `--bs-navbar-nav-link-padding-x: .35rem`) so the 3070 menu clears the logo and head. Menu links are `.875rem` bold. Below 992px the open menu fills the screen under the bar (absolute, `100dvh - var(--nav-h)`, `--nav-h` = the header's real height, set by the `navbar.php` script, links/buttons centred, opacity fade; not `position:fixed` because the header's backdrop-filter is its containing block). The bar is liquid glass in Chromium: `backdrop-filter: url(#nav-glass) blur(6px) saturate(180%)` (`#nav-glass` in `include.php`: blurred low-frequency `feTurbulence` -> `feDisplacementMap`, bends the page behind it), gated by `@supports (-webkit-app-region: drag)` (Chromium-only property); Safari/Firefox keep the plain blur. **Brand slash template:** the cut corners of the "in" box are one rule in `style.css` ("Brand slash (template)"): `class="fx-slash"` (both ends) or `fx-slash-start` (left only), size via `--slash` (default 4px); pseudo-elements are added to that rule's selector list. Used by the nav and section-title underlines and the CrossFit pill (`--slash: 8px`, no radius); never paste the polygon again. The hero h1 shadow is soft (`0 1px 3px .3`, `0 2px 18px .18`) and the hero frosted-band blur (`.hero-section .container::before`) is at opacity .8. The hero subtitle (`.hero-text h1 span`, gym `hero_subtitle`) is a block strapline at `.42em` of the h1, tracked out. Logos (`.navbar-brand img`, `.gym-switch-logo`) get the same `#pt-noise` grain filter as h1/h2, and `.hero-event-go`, `.cta-btn`, `.btn-primary` joined the `--noise` button list. Section-title underlines (`.section-title.deco h2:after`) use the same slashed 4px bar as the nav underline. The hero event button text uses `--fitme-on-accent` (white on red). The nav underline (`.nav-link:after`) is a 4px bar 3px under the text with slashed ends (`clip-path`, like the "in" box of the FitMe in logo). The hero event card (`.hero-event`) is square (no radius on card, badge or button), taller (88px min) and has a defocus gradient: `::after` is a `backdrop-filter: blur()` layer masked left->right, sharp on the left and most blurred on the right; the card itself must not get filter/backdrop-filter/opacity/mask or the blur stops reaching the hero photo. Each gym can have a `logo` (same migration, plus `migration_20261026_gym_logo.sql`: the column was added to the 2026-10-13 file after it had already run on dev, so dev had no `logo` column until 2026-10-26 — never edit a migration that has run, add a new one); the header image and the logo are uploaded in Gestão → Ginásios (`gyms_save_image()` → `uploads/gyms/`, validated by content, SVG through `svg_is_safe()`), not typed as paths; `db_gyms_create/update` skip `brand_color`/`logo` while the migration isnLine truncated
An unpublished gym 404s publicly but previews for staff.
## Imagens / JPEG XL (2026-10-26)
+58 -19
View File
@@ -634,6 +634,31 @@ section[id] {
height: auto;
}
.navbar-brand-hero { display: none; }
/* Match head paint, shared by the desktop bar end and the mobile menu band
(geometry per breakpoint below): the OTHER gym's accent as the face, a strip
in THIS site's accent on the slashed edge, its logo in that gym's on-accent
ink. --site-accent is resolved on the header, before .gym-switch's inline
vars swap --fitme-accent for the other gym's. */
.header-section { --site-accent: var(--fitme-accent); }
.header-section .gym-switch::before,
.header-section .gym-switch::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
pointer-events: none;
}
.header-section .gym-switch::before { background: var(--site-accent); }
/* the logo in that gym's on-accent ink (dark on lime, white on red) */
.header-section .gym-switch .gym-switch-btn--logo .gym-switch-logo {
filter: url(#pt-noise) brightness(0) drop-shadow(0 1px 0 rgba(255, 255, 255, .25));
}
.header-section .gym-switch--light-ink .gym-switch-btn--logo .gym-switch-logo {
filter: url(#pt-noise) brightness(0) invert(1) drop-shadow(0 1px 1px rgba(0, 0, 0, .35));
}
/* the hover ring would vanish on its own accent: draw it in the ink */
.header-section .gym-switch .gym-switch-btn--logo { border-color: rgba(11, 11, 13, .35); }
.header-section .gym-switch--light-ink .gym-switch-btn--logo { border-color: rgba(255, 255, 255, .45); }
@media (min-width: 992px) {
.header-section .navbar {
padding: 0;
@@ -661,17 +686,20 @@ section[id] {
opacity: 0;
visibility: hidden;
}
.navbar-brand-hero img { width: 180px; height: auto; }
/* capped height too: the CrossFit logo is taller and would cover the links */
.navbar-brand-hero img { width: auto; height: 52px; max-width: none; }
.header-section .navbar-collapse {
min-height: 58px;
align-items: center;
}
.header-section .gym-switch { position: absolute; } /* the match head, below */
.header-section.is-top .navbar { padding-top: 62px; } /* room for the 180px logo */
/* Match head: the right end of the bar is a dark block behind the other-gym
button, its left side cut on the same slash as the underlines, with an
accent strip on the cut and an ember glow just inside it (the bar is the
matchstick, this is the head). ::before = accent strip, ::after = head. */
/* Match head: the right end of the bar is a block behind the other-gym
button, its left side cut on the same slash as the underlines (the bar is
the matchstick, this is the head). It is painted in the OTHER gym's brand
(navbar.php puts its vars on .gym-switch), a glimpse into that site: its
accent as the face, a strip in this site's accent on the cut and a shadow just inside it.
::before = strip, ::after = head. */
.header-section .gym-switch {
--slash: 22px;
top: 0;
@@ -684,22 +712,13 @@ section[id] {
}
.header-section .gym-switch::before,
.header-section .gym-switch::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
pointer-events: none;
clip-path: polygon(var(--slash) 0, 100% 0, 100% 100%, 0 100%); /* .fx-slash-start */
}
.header-section .gym-switch::before { left: -5px; background: var(--fitme-accent); }
.header-section .gym-switch::before { left: -5px; }
.header-section .gym-switch::after {
background:
radial-gradient(70% 90% at 10% 50%, rgba(var(--fitme-accent-rgb), .38), transparent 70%),
linear-gradient(180deg, #1b1b1f, #0b0b0d);
}
/* other-gym logos are drawn for a light bar: flip lightness, keep the hues */
.header-section .gym-switch .gym-switch-btn--logo .gym-switch-logo {
filter: url(#pt-noise) invert(1) hue-rotate(180deg) drop-shadow(0 1px 1px rgba(0, 0, 0, .4));
radial-gradient(70% 90% at 10% 50%, rgba(0, 0, 0, .28), transparent 70%),
linear-gradient(180deg, var(--fitme-accent), var(--fitme-accent-dark));
}
.header-section.is-top .navbar-brand { opacity: 0; visibility: hidden; }
.header-section.is-top .navbar-brand-hero { opacity: 1; visibility: visible; }
@@ -735,7 +754,26 @@ section[id] {
.header-section .navbar-nav { align-items: center; gap: .25rem; }
.header-section .navbar-nav .nav-link { font-size: 1.25rem; margin: 0 !important; }
.header-section .navbar-nav .nav-link::after { left: 0; right: 0; } /* no side padding here */
.header-section .gym-switch { justify-content: center; }
/* the match head as a band across the bottom of the menu, its top edge on the slash */
.header-section .navbar-collapse { padding-bottom: 96px; }
.header-section .gym-switch {
--slash: 18px;
justify-content: center;
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
padding: calc(var(--slash) + .9rem) 1rem 1.1rem !important;
}
.header-section .gym-switch::before,
.header-section .gym-switch::after { clip-path: polygon(0 var(--slash), 100% 0, 100% 100%, 0 100%); }
.header-section .gym-switch::before { top: -5px; }
.header-section .gym-switch::after {
background:
radial-gradient(90% 80% at 50% 0, rgba(0, 0, 0, .28), transparent 70%),
linear-gradient(180deg, var(--fitme-accent), var(--fitme-accent-dark));
}
}
@media (max-width: 991.98px) and (prefers-reduced-motion: reduce) {
.header-section .navbar-collapse, .header-section .navbar-collapse.collapsing { transition: none; }
@@ -860,6 +898,7 @@ section[id] {
.hero-section .container::before {
-webkit-backdrop-filter: blur(22px) saturate(1.1);
backdrop-filter: blur(22px) saturate(1.1);
opacity: .8;
}
.hero-section .container::after {
background:
@@ -880,7 +919,7 @@ section[id] {
font-weight: 900;
color: #fff;
/* tight + soft shadow so the lettering separates from the frosted band */
text-shadow: 0 2px 10px rgba(0, 0, 0, .55), 0 4px 28px rgba(0, 0, 0, .5), 0 6px 64px rgba(0, 0, 0, .45);
text-shadow: 0 1px 3px rgba(0, 0, 0, .3), 0 2px 18px rgba(0, 0, 0, .18);
line-height: 1.05;
letter-spacing: -.5px;
}
+1
View File
@@ -1,6 +1,7 @@
<?php
// Was Home::crossfit.
$current_uri_2 = 'crossfit';
$nav_center_logo = true; // big centred logo over the video, docks left on scroll (navbar.php)
// Old /home/crossfit links → the default gym's CrossFit page.
if (($_GET['gym'] ?? '') === '') {
+1 -1
View File
@@ -7,7 +7,7 @@
<div class="container h-100">
<div class="d-flex h-100 text-center align-items-center">
<div class="w-100 text-white">
<h1 class="display-3"><img src="<?= base_url('site/assets/img/logos/logo_crossfit_white.svg')?>" alt="CrossFit 3070 — box de CrossFit em Mira" style="opacity: .85; max-width: 320px;" decoding="async"></h1>
<h1 class="visually-hidden">CrossFit 3070 — box de CrossFit em Mira</h1>
<p class="lead mb-0"></p>
</div>
</div>
+14 -2
View File
@@ -66,8 +66,12 @@ $center_logo = !empty($nav_center_logo);
if ((int) $g['id'] !== (int) current_gym()['id']) $other_gyms[] = $g;
}
}
if ($other_gyms): ?>
<div class="gym-switch d-flex flex-wrap gap-2 ms-lg-auto py-2 py-lg-0 pe-lg-3">
if ($other_gyms):
// The match head (style.css) is painted in the first other gym's brand.
$head_vars = gym_brand_vars($other_gyms[0]);
$head_ink = str_contains($head_vars, 'on-accent:#FFFFFF') ? ' gym-switch--light-ink' : '';
?>
<div class="gym-switch<?= $head_ink ?> d-flex flex-wrap gap-2 ms-lg-auto py-2 py-lg-0 pe-lg-3" style="<?= htmlspecialchars($head_vars) ?>">
<?php foreach ($other_gyms as $g): ?>
<?php $g_logo = gym_logo($g); ?>
<a class="gym-switch-btn<?= $g_logo !== '' ? ' gym-switch-btn--logo' : '' ?>" href="<?= htmlspecialchars(gym_url($g)) ?>" style="<?= htmlspecialchars(gym_brand_vars($g)) ?>">
@@ -102,6 +106,14 @@ $center_logo = !empty($nav_center_logo);
dock();
}
/* ── Mobile menu fills the screen below the bar (style.css --nav-h) ──── */
var hdr = document.querySelector('.header-section');
if (hdr) {
var setNavH = function() { hdr.style.setProperty('--nav-h', hdr.offsetHeight + 'px'); };
window.addEventListener('resize', setNavH, { passive: true });
setNavH();
}
/* ── Eased smooth-scroll helper ──────────────────────────────────────── */
function smoothScrollTo(targetEl, duration) {
var start = window.scrollY;