feat(images): JPEG XL optimizer with webp/jpg fallback and <picture> serving
img_optimize() decodes .jxl with djxl or Imagick, resizes with GD, re-encodes with cjxl and writes foto.jxl.webp beside it; skips cleanly without a decoder. Gallery, hero, navbar logos and JSON-LD serve the JXL with the fallback, and trash/removal move the fallback with its .jxl.
This commit is contained in:
1 parent
8fe73eb5d6
commit
4f44bc1609
14 files changed
+260
-20
No files matched your search
@@ -4456,6 +4456,8 @@ h3.contact-title {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
/* .jxl images are <picture> (img_tag): let the <img> size against its parent as before. */
|
||||
.gal-link picture, .navbar-brand picture, .gym-switch-btn picture { display: contents; }
|
||||
.gal-link img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -722,7 +722,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
function show(i) {
|
||||
current = (i + links.length) % links.length;
|
||||
var src = links[current].querySelector('img');
|
||||
img.src = links[current].getAttribute('href');
|
||||
// The tile's chosen source (JXL inside <picture> where supported), else the link (fallback).
|
||||
img.src = (src && src.currentSrc) || links[current].getAttribute('href');
|
||||
img.alt = src ? src.alt : '';
|
||||
// Restart the fade-in for each photo.
|
||||
img.style.animation = 'none'; void img.offsetWidth; img.style.animation = '';
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
// tile (without dragging) opens it full size in a <dialog> lightbox with prev /
|
||||
// next, arrow keys, Esc and swipe; the repeated copies are hidden from assistive
|
||||
// tech and open the same photo.
|
||||
// Every format the admin upload accepts (functions_galerias.php), not just .jpg.
|
||||
$files = preg_grep('/\.(jpe?g|png|gif|webp|jxl)$/i', glob(rtrim($gallery_path, '/') . '/*') ?: []);
|
||||
$files = array_values($files ?: []);
|
||||
// Every format the admin upload accepts (functions_galerias.php), without the
|
||||
// .jxl.webp/.jpg fallbacks; a .jxl tile is <picture> (img_tag), the link goes to
|
||||
// the fallback and the lightbox shows whichever the tile picked (currentSrc).
|
||||
$files = img_list_dir($gallery_path);
|
||||
// Column span per tile, cycled: w = two columns, s = one.
|
||||
$gal_shapes = ['w', 's', 's', 's', 's', 'w', 's', 's', 's', 's', 's'];
|
||||
$gal_label = $gallery_h2 ?? 'Instalações';
|
||||
@@ -45,15 +46,12 @@ $gal_label = $gallery_h2 ?? 'Instalações';
|
||||
<div class="gal-unit <?= $u['grid'] ? 'gal-unit--grid' : 'gal-unit--full' ?>">
|
||||
<?php foreach ($u['idx'] as $gi):
|
||||
$f = $files[$gi];
|
||||
$src = base_url($f);
|
||||
$src = base_url(img_compat_rel($f));
|
||||
$alt = $gal_label . ' — FitMe 3070, foto ' . ($gi + 1); // distinct alt per photo
|
||||
$dim = @getimagesize($f) ?: [0, 0];
|
||||
?>
|
||||
<div class="gal-tile">
|
||||
<a href="<?= htmlspecialchars($src) ?>" class="gal-link" data-index="<?= $gi ?>"<?= $dup ? ' data-dup="1" tabindex="-1"' : '' ?> aria-label="Ampliar: <?= htmlspecialchars($alt) ?>">
|
||||
<img src="<?= htmlspecialchars($src) ?>" alt="<?= $dup ? '' : htmlspecialchars($alt) ?>"
|
||||
<?= $dim[0] ? 'width="' . (int) $dim[0] . '" height="' . (int) $dim[1] . '"' : '' ?>
|
||||
<?= $gi > 2 || $dup ? 'loading="lazy"' : '' ?> decoding="async" draggable="false">
|
||||
<?= img_tag($f, ['alt' => $dup ? '' : $alt, 'loading' => $gi > 2 || $dup ? 'lazy' : null, 'decoding' => 'async', 'draggable' => 'false']) ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
@@ -50,7 +50,9 @@ $canonical = $GLOBALS['_canonical_override'] ?? match ($page) {
|
||||
<link rel="dns-prefetch" href="https://forms.zohopublic.eu">
|
||||
<?php if (($current_uri_2 ?? '') === ''): ?>
|
||||
<!-- Homepage hero is the LCP image — start fetching it before the CSS/JS -->
|
||||
<link rel="preload" as="image" fetchpriority="high" href="<?= htmlspecialchars(base_url(($gym_meta['hero_image'] ?? '') !== '' ? $gym_meta['hero_image'] : 'site/assets/img/home.jpg')) ?>">
|
||||
<?php $pre_hero = ($gym_meta['hero_image'] ?? '') !== '' ? $gym_meta['hero_image'] : 'site/assets/img/home.jpg';
|
||||
// A .jxl hero (img_css_bg) is preloaded only where JXL is supported (type=); the rest fetch the fallback from the CSS. ?>
|
||||
<link rel="preload" as="image" fetchpriority="high" href="<?= htmlspecialchars(base_url($pre_hero)) ?>"<?= img_fallback_rel($pre_hero) !== '' ? ' type="image/jxl"' : '' ?>>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Fonts (Roboto self-hosted — see @font-face in style.css) -->
|
||||
|
||||
@@ -13,7 +13,11 @@ $logo_dims = svg_img_dims_attr(dirname(__DIR__, 2) . '/' . $logo_rel);
|
||||
<header class="header-section lazy">
|
||||
<nav class="navbar navbar-expand-lg navbar-light" style="padding-left:1rem; padding-top: 0; padding-bottom: 0;padding-right: 0; ">
|
||||
<a class="navbar-brand" href="<?= htmlspecialchars(gym_url(current_gym())) ?>">
|
||||
<?php if (img_fallback_rel($logo_rel) !== ''): ?>
|
||||
<?= img_tag($logo_rel, ['loading' => 'eager', 'decoding' => 'async', 'alt' => 'Logo ' . (current_gym()['name'] ?? 'FitMe 3070'), 'style' => 'width: 150px; height: auto;']) ?>
|
||||
<?php else: ?>
|
||||
<img loading="eager" decoding="async" alt="Logo <?= htmlspecialchars(current_gym()['name'] ?? 'FitMe 3070') ?>" src="<?= htmlspecialchars($logo) ?>" <?= $logo_dims ?> style="width: 150px; height: auto;">
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
@@ -63,7 +67,7 @@ $logo_dims = svg_img_dims_attr(dirname(__DIR__, 2) . '/' . $logo_rel);
|
||||
<?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)) ?>">
|
||||
<?php if ($g_logo !== ''): ?>
|
||||
<img class="gym-switch-logo" src="<?= htmlspecialchars(base_url($g_logo)) ?>" alt="<?= htmlspecialchars($g['name']) ?>" decoding="async">
|
||||
<?= img_tag($g_logo, ['class' => 'gym-switch-logo', 'alt' => $g['name'], 'decoding' => 'async', 'width' => null]) ?>
|
||||
<?php else: ?>
|
||||
<span><?= htmlspecialchars($g['name']) ?></span>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -7,7 +7,7 @@ $ld = [
|
||||
'@type' => 'ExerciseGym',
|
||||
'name' => $ld_gym['name'] !== '' ? $ld_gym['name'] : (($info['name'] ?? '') !== '' ? $info['name'] : 'FitMe 3070'),
|
||||
'url' => gym_url($ld_gym),
|
||||
'image' => base_url(($ld_gym['hero_image'] ?? '') !== '' ? $ld_gym['hero_image'] : 'site/assets/img/home.jpg'),
|
||||
'image' => base_url(img_compat_rel(($ld_gym['hero_image'] ?? '') !== '' ? $ld_gym['hero_image'] : 'site/assets/img/home.jpg')),
|
||||
'logo' => base_url('site/assets/img/logos/logo_black.svg'),
|
||||
];
|
||||
if (!empty($info['phone'])) $ld['telephone'] = site_tel($info['phone']);
|
||||
|
||||
+2
-2
@@ -51,7 +51,7 @@ $hero_img = $gym['hero_image'] !== '' ? $gym['hero_image'] : 'site/assets/img
|
||||
|
||||
<!-- Hero background is inline (no data-setbg) so it paints without waiting for JS;
|
||||
the set-bg class is kept for its cover/center styling. -->
|
||||
<section id="home" class="hero-section set-bg fade-in" style="background-image:url('<?= htmlspecialchars(base_url($hero_img), ENT_QUOTES) ?>')">
|
||||
<section id="home" class="hero-section set-bg fade-in" style="<?= htmlspecialchars(img_css_bg($hero_img), ENT_QUOTES) ?>">
|
||||
<div class="container">
|
||||
<div class="hero-text">
|
||||
<?php /* Eventos a decorrer — destacados no topo do header. Cada um liga
|
||||
@@ -117,7 +117,7 @@ if ($offer):
|
||||
<?php
|
||||
// ── Galeria (Instalações) ───────────────────────────────────────────────────────
|
||||
$gallery_path = !empty($gym['gallery_id']) ? 'uploads/gallery/' . (int) $gym['gallery_id'] . '/' : '';
|
||||
$gallery_files = ($on('galeria') && $gallery_path !== '') ? preg_grep('/\.(jpe?g|png|gif|webp|jxl)$/i', glob($gallery_path . '*') ?: []) : [];
|
||||
$gallery_files = ($on('galeria') && $gallery_path !== '') ? img_list_dir($gallery_path) : [];
|
||||
if ($gallery_files && count($gallery_files) > 0):
|
||||
$gallery_h2 = 'Conhece as nossas instalações';
|
||||
?>
|
||||
|
||||
Reference in new issue
Block a user