fix(site): width/height nas imagens da home (auditoria 4.7)

125 imagens na home, 0 com srcset e 122 sem width/height — o browser
só sabia o tamanho depois de descarregar, o que causa layout shift.

Uploads são redimensionados a um limite de lado maior (functions_images.php),
não a uma dimensão fixa (fotos de equipa vão de 389x512 a 512x512, ícones
de oferta de 128x128 a 256x256), por isso as dimensões não dão para
hardcode: site_img_dims_attr() em functions_site.php lê o ficheiro real
com getimagesize() por pedido, tal como o gallery.php já fazia, e
devolve o par width="" height="" ou "" se o ficheiro não existir. SVG
não passa por getimagesize() (sem decoder embutido) — svg_img_dims_attr()
lê os atributos width/height (ícones fitme-*.svg, 48x48 fixo) ou cai
para o viewBox (logos do navbar, só têm viewBox).

Aplicado a: ícones da oferta, fotos de equipa (card + painel de detalhe),
ícone de modalidade em modalidade_icon_html() e no horário (index.php e
includes/horario.php), logo do navbar.

srcset ficou de fora: as imagens já vêm coartadas para pouco mais do que
o maior tamanho a que aparecem (fotos de equipa a 512px cabidas numa caixa
de no máx. 200px CSS com object-fit:cover; ícones a 128-256px mostrados a
44px) — é a folga normal para ecrãs retina, não desperdício a resolver
com múltiplas resoluções. E o layout é sempre CSS-driven (width/height:
100% + object-fit), por isso um srcset não mudaria o que é descarregado
por variação de viewport, só pouparia bytes nalguns casos — gerar 2-3
tamanhos por upload via GD seria trabalho a mais para o ganho real neste
site. Fica só o fix de dimensões, que é o que resolve o CLS que a
auditoria mediu.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
andreandClaude Sonnet 5 committed 2026-09-25 21:44:29 +01:00
1 parent a42b6d33c0
commit 595a37fd8b
4 files changed
+59 -9

No files matched your search

+5 -4
View File
@@ -98,7 +98,7 @@ if ($offer):
<div class="col-6 col-sm-4 col-lg-3">
<div class="icon-box-item">
<div class="ib-icon">
<img loading="lazy" decoding="async" src="<?= htmlspecialchars(site_img_url($o['o_img'], 'uploads/website/offer')) ?>" alt="<?= htmlspecialchars((string) $o['o_title']) ?>">
<img loading="lazy" decoding="async" <?= site_img_dims_attr($o['o_img'], 'uploads/website/offer') ?> src="<?= htmlspecialchars(site_img_url($o['o_img'], 'uploads/website/offer')) ?>" alt="<?= htmlspecialchars((string) $o['o_title']) ?>">
</div>
<h3><?= htmlspecialchars((string) $o['o_title']) ?></h3>
<p><?= htmlspecialchars((string) $o['o_description']) ?></p>
@@ -202,19 +202,20 @@ if ($team):
$t_bio = trim(strip_tags((string) ($d['description'] ?? ''))) !== '' ? clean_richtext((string) $d['description']) : '';
$t_detail = $t_skills || $t_certs || $t_bio !== '';
$t_img = htmlspecialchars(site_img_url($d['photo'], 'uploads/team/foto'));
$t_dims = site_img_dims_attr($d['photo'], 'uploads/team/foto');
$t_id = 'td-' . (int) $d['id'];
?>
<div class="col-6 col-md-4 col-lg-3" style="margin-top: 20px;">
<div class="trainer-item<?= $t_detail ? ' has-detail' : '' ?>"<?= $t_detail ? ' tabindex="0" role="button" aria-expanded="false" aria-controls="' . $t_id . '" aria-label="Ver detalhes de ' . htmlspecialchars((string) $d['name']) . '"' : '' ?>>
<div class="trainer-pic">
<img loading="lazy" decoding="async" src="<?= $t_img ?>" alt="<?= htmlspecialchars((string) $d['name']) ?>">
<img loading="lazy" decoding="async" <?= $t_dims ?> src="<?= $t_img ?>" alt="<?= htmlspecialchars((string) $d['name']) ?>">
</div>
<h3><?= htmlspecialchars((string) $d['name']) ?></h3>
<?php if ($t_title !== ''): ?><p class="trainer-title"><?= htmlspecialchars($t_title) ?></p><?php endif; ?>
<?php if ($t_detail): ?>
<div class="trainer-detail" id="<?= $t_id ?>">
<button type="button" class="td-close" aria-label="Fechar">&times;</button>
<div class="td-photo"><img loading="lazy" decoding="async" src="<?= $t_img ?>" alt=""></div>
<div class="td-photo"><img loading="lazy" decoding="async" <?= $t_dims ?> src="<?= $t_img ?>" alt=""></div>
<div class="td-body">
<strong class="td-name"><?= htmlspecialchars((string) $d['name']) ?></strong>
<?php if ($t_title !== ''): ?><span class="td-title"><?= htmlspecialchars($t_title) ?></span><?php endif; ?>
@@ -333,7 +334,7 @@ if (!in_array($default_day, $week_values, true)) { $default_day = $week_values[0
<div class="schedule_cell<?= $value === $default_day ? '' : ' sched-hide' ?>" data-day="<?= htmlspecialchars($value) ?>" data-section="<?= ((int) substr($time, 0, 2) < 12) ? 'manha' : 'tarde' ?>" style="grid-column:<?= $col ?>;grid-row:<?= $row_of[$time] ?>;">
<?php foreach ($grid[$value][$time] as $d): ?>
<div class="schedule_box" style="background-color:<?= htmlspecialchars((string) $d['cor']) ?>">
<img src="<?= htmlspecialchars(site_img_url($d['icon'], 'uploads/modalidades/icons')) ?>" alt="<?= htmlspecialchars((string) $d['nome']) ?>" decoding="async">
<img src="<?= htmlspecialchars(site_img_url($d['icon'], 'uploads/modalidades/icons')) ?>" alt="<?= htmlspecialchars((string) $d['nome']) ?>" <?= site_img_dims_attr($d['icon'], 'uploads/modalidades/icons') ?> decoding="async">
<a><b><?= htmlspecialchars((string) $d['nome']) ?></b></a>
<div class="schedule_info">
<?= htmlspecialchars((string) $d['hora_inicio']) ?>