Compare commits
2
Commits
cc677af92d
...
49adbd0ae1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49adbd0ae1 | ||
|
|
1eb2a61031 |
No files matched your search
@@ -82,6 +82,11 @@ backoffice has an "a editar" gym switcher (`admin_gym_id()` /
|
|||||||
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). 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 light `drop-shadow` halo on the image for contrast; 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; `AddType image/jxl` in `.htaccess`; GD can't resize it, so it is stored as uploaded; Chrome/Firefox may not display JXL, Safari does). The public gallery lists its folder by extension (`site/includes/gallery.php`, `site/index.php`: jpg/jpeg/png/gif/webp/jxl; before 2026-10-26 it only listed `*.jpg`, so PNG/WebP uploads never showed). Gallery photos are not in the scroll "hover" (`.is-centered`, main.js) any more; only a real hover lights them. Mensalidades (`.pricing-section::before`) has a slow (26s) drifting brand-colour glow over the metal texture (`pricing-drift`, static with reduced motion).
|
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). 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 light `drop-shadow` halo on the image for contrast; 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; `AddType image/jxl` in `.htaccess`; GD can't resize it, so it is stored as uploaded; Chrome/Firefox may not display JXL, Safari does). The public gallery lists its folder by extension (`site/includes/gallery.php`, `site/index.php`: jpg/jpeg/png/gif/webp/jxl; before 2026-10-26 it only listed `*.jpg`, so PNG/WebP uploads never showed). Gallery photos are not in the scroll "hover" (`.is-centered`, main.js) any more; only a real hover lights them. Mensalidades (`.pricing-section::before`) has a slow (26s) drifting brand-colour glow over the metal texture (`pricing-drift`, static with reduced motion).
|
||||||
An unpublished gym 404s publicly but previews for staff.
|
An unpublished gym 404s publicly but previews for staff.
|
||||||
|
|
||||||
|
## Imagens / JPEG XL (2026-10-26)
|
||||||
|
Fleet transition to JPEG XL (root `Y:\GitTea\CLAUDE.md`, "Images"); fitme3070 is the reference site. Uploads already accept `.jxl` (gyms header/logo, gallery photos). **To do:** `img_optimize()` (`config/functions_images.php`) still skips JXL because it is GD-only; it must resize/re-encode JXL (Imagick with JXL or `cjxl`/`djxl`, check what the dev `webdevops/php-apache:8.2` container and the VPS `fitme` image have) and write a `.webp`/`.jpg` fallback beside every `.jxl`; the gallery, hero and logos then serve `<picture>` with the JXL source and the fallback `<img>`, and the gallery listing groups files by base name. Every image input in the backoffice shows the current image next to it with a remove control (gyms form does; check events, modalidades, team, galerias, system settings).
|
||||||
|
|
||||||
|
The navbar only links sections the gym shows **and** has content for (`site_section_has_content()` in `config/functions_site.php`: Galeria = photos in its folder, Modalidades, Equipa = active members (`site_team_active()`, also used by the page), Horário, Mensalidades). The gallery section background is a 7% brand-colour tint, not white.
|
||||||
|
|
||||||
## platform-tools
|
## platform-tools
|
||||||
|
|
||||||
`shared/platform-tools` is a **git submodule** (pinned `branch = main` in
|
`shared/platform-tools` is a **git submodule** (pinned `branch = main` in
|
||||||
|
|||||||
@@ -46,8 +46,18 @@ function initDataTable(selector, opts) {
|
|||||||
if (!window.jQuery || !jQuery.fn || !jQuery.fn.DataTable) return null;
|
if (!window.jQuery || !jQuery.fn || !jQuery.fn.DataTable) return null;
|
||||||
var $table = jQuery(selector);
|
var $table = jQuery(selector);
|
||||||
if (!$table.length) return null;
|
if (!$table.length) return null;
|
||||||
|
// Listagem vazia: as páginas desenham uma única linha <td colspan> com a
|
||||||
|
// mensagem, que a DataTables rejeita (nº de colunas errado → erro). Tira-se
|
||||||
|
// a linha e a mensagem dela (com links) passa a ser o emptyTable.
|
||||||
|
var language = DATATABLES_PT_PT;
|
||||||
|
var $rows = $table.find('tbody > tr');
|
||||||
|
var $only = $rows.length === 1 ? $rows.children('td[colspan]') : jQuery();
|
||||||
|
if ($only.length === 1 && $rows.children().length === 1) {
|
||||||
|
language = jQuery.extend({}, DATATABLES_PT_PT, { emptyTable: $only.html() });
|
||||||
|
$rows.remove();
|
||||||
|
}
|
||||||
return $table.DataTable(jQuery.extend(true, {
|
return $table.DataTable(jQuery.extend(true, {
|
||||||
language: DATATABLES_PT_PT,
|
language: language,
|
||||||
pageLength: 25,
|
pageLength: 25,
|
||||||
lengthMenu: [[10, 25, 50, -1], [10, 25, 50, 'Todos']],
|
lengthMenu: [[10, 25, 50, -1], [10, 25, 50, 'Todos']],
|
||||||
responsive: true,
|
responsive: true,
|
||||||
|
|||||||
@@ -29,16 +29,37 @@ function site_info(PDO $pdo, ?int $gym_id = null): array {
|
|||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Whether the home page renders section $sec for $gym (same data checks as site/index.php). */
|
||||||
|
function site_section_has_content(array $gym, string $sec): bool {
|
||||||
|
$pdo = $GLOBALS['pdo'] ?? null;
|
||||||
|
$gid = (int) $gym['id'];
|
||||||
|
if (!$pdo && $sec !== 'galeria') return true;
|
||||||
|
return match ($sec) {
|
||||||
|
'modalidades' => (bool) site_modalidades($pdo, $gid),
|
||||||
|
'equipa' => (bool) site_team_active($pdo, $gid),
|
||||||
|
'horario' => (bool) site_horario($pdo, '', $gid),
|
||||||
|
'mensalidades' => (bool) site_memberships($pdo, $gid),
|
||||||
|
'galeria' => !empty($gym['gallery_id']) && preg_grep('/\.(jpe?g|png|gif|webp|jxl)$/i',
|
||||||
|
glob(dirname(__DIR__) . '/uploads/gallery/' . (int) $gym['gallery_id'] . '/*') ?: []),
|
||||||
|
default => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Team members shown on the site (status «ativo»). */
|
||||||
|
function site_team_active(PDO $pdo, ?int $gym_id = null): array {
|
||||||
|
return array_values(array_filter(site_team($pdo, $gym_id), fn($d) => ($d['status'] ?? '') === 'ativo'));
|
||||||
|
}
|
||||||
|
|
||||||
// Top navigation (label => absolute URL): anchors on the gym's own page, only
|
// Top navigation (label => absolute URL): anchors on the gym's own page, only
|
||||||
// for the sections that gym shows, plus its CrossFit page when it has one.
|
// for the sections that gym shows and has content for, plus its CrossFit page when it has one.
|
||||||
function site_navbar(?array $gym = null): array {
|
function site_navbar(?array $gym = null): array {
|
||||||
$gym = $gym ?? current_gym();
|
$gym = $gym ?? current_gym();
|
||||||
$items = ['Home' => ['#home', null]];
|
$items = ['Home' => ['#home', null]];
|
||||||
foreach (['Modalidades' => 'modalidades', 'Equipa' => 'equipa', 'Horário' => 'horario',
|
foreach (['Galeria' => 'galeria', 'Modalidades' => 'modalidades', 'Equipa' => 'equipa', 'Horário' => 'horario',
|
||||||
'Mensalidades' => 'mensalidades', 'Contactos' => 'contactos'] as $label => $sec) {
|
'Mensalidades' => 'mensalidades', 'Contactos' => 'contactos'] as $label => $sec) {
|
||||||
if (!gym_section_on($gym, $sec)) continue;
|
if (!gym_section_on($gym, $sec)) continue;
|
||||||
// No active plans → the section isn't rendered, so no link to it either.
|
// A section with nothing to show isn't rendered, so no link to it either.
|
||||||
if ($sec === 'mensalidades' && !empty($GLOBALS['pdo']) && !site_memberships($GLOBALS['pdo'], (int) $gym['id'])) continue;
|
if (!site_section_has_content($gym, $sec)) continue;
|
||||||
$items[$label] = ['#' . $sec, $sec];
|
$items[$label] = ['#' . $sec, $sec];
|
||||||
}
|
}
|
||||||
$nav = [];
|
$nav = [];
|
||||||
|
|||||||
+2
-2
@@ -27,7 +27,7 @@ require __DIR__ . '/_open.php'; // resolves $gym (404 if unknown / not visible
|
|||||||
$gid = (int) $gym['id'];
|
$gid = (int) $gym['id'];
|
||||||
$on = fn(string $key) => gym_section_on($gym, $key);
|
$on = fn(string $key) => gym_section_on($gym, $key);
|
||||||
$offer = $on('oferta') ? site_offer($pdo, $gid) : [];
|
$offer = $on('oferta') ? site_offer($pdo, $gid) : [];
|
||||||
$team = $on('equipa') ? site_team($pdo, $gid) : [];
|
$team = $on('equipa') ? site_team_active($pdo, $gid) : [];
|
||||||
$modalidades = $on('modalidades') ? site_modalidades($pdo, $gid) : [];
|
$modalidades = $on('modalidades') ? site_modalidades($pdo, $gid) : [];
|
||||||
$horario = $on('horario') ? site_horario($pdo, '', $gid) : [];
|
$horario = $on('horario') ? site_horario($pdo, '', $gid) : [];
|
||||||
$whours = $on('workhours') ? site_work_hours($pdo, $gid) : [];
|
$whours = $on('workhours') ? site_work_hours($pdo, $gid) : [];
|
||||||
@@ -121,7 +121,7 @@ $gallery_files = ($on('galeria') && $gallery_path !== '') ? preg_grep('/\.(jpe?g
|
|||||||
if ($gallery_files && count($gallery_files) > 0):
|
if ($gallery_files && count($gallery_files) > 0):
|
||||||
$gallery_h2 = 'Conhece as nossas instalações';
|
$gallery_h2 = 'Conhece as nossas instalações';
|
||||||
?>
|
?>
|
||||||
<section id="galeria" class="gallery-section" style="padding:70px 0 0;background:var(--fitme-white)">
|
<section id="galeria" class="gallery-section" style="padding:70px 0 0;background:color-mix(in srgb, var(--fitme-accent) 7%, #fff)">
|
||||||
<?php include __DIR__ . '/includes/gallery.php'; ?>
|
<?php include __DIR__ . '/includes/gallery.php'; ?>
|
||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
Reference in new issue
Block a user