Compare commits
3 Commits
feat/gitea
...
perf/man-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab76efa050 | ||
|
|
9ab2beb3ea | ||
|
|
65919d4a6c |
@@ -57,8 +57,8 @@ Site-wide content is a singleton row reached via `SiteSetting::instance()`. Publ
|
|||||||
|
|
||||||
- Baselines are committed `.snap` files under `tests/.pest/snapshots/Browser/VisualRegressionTest/`.
|
- Baselines are committed `.snap` files under `tests/.pest/snapshots/Browser/VisualRegressionTest/`.
|
||||||
- `tests/Browser/Screenshots/` is gitignored — it only holds diff output.
|
- `tests/Browser/Screenshots/` is gitignored — it only holds diff output.
|
||||||
- Regenerate with `composer visual:update`.
|
- `composer visual:update` is the sanctioned command, but on macOS it writes baselines CI rejects. Use `scripts/test/visual-update-ci.sh`, which runs it inside the Linux runner built from `docker/ci-runner.Dockerfile`.
|
||||||
- **Baselines are CI-parity artifacts.** CI runs the browser suite against a `docker build`-produced FrankenPHP container (see the `browser` job in `.github/workflows/ci.yml`), so baselines regenerated on macOS against a local server will be rejected by CI. Commit `4578457` exists because of this.
|
- **Baselines are Linux-parity artifacts.** Pest Browser does not use FrankenPHP or `artisan serve` — it serves the Laravel kernel from an in-process Amp server (`vendor/pestphp/pest-plugin-browser/src/Drivers/LaravelHttpServer.php`), so the FrankenPHP container the `browser` job starts is only a health check. What makes a baseline reproducible is the machine that renders it: Ubuntu 24.04, Playwright's Chromium, and Playwright's font packages (`StableScreenshot` forces `Arial`, which fontconfig resolves to Liberation Sans on Linux). Commit `4578457` exists because macOS renders text differently.
|
||||||
|
|
||||||
Determinism relies on three cooperating pieces:
|
Determinism relies on three cooperating pieces:
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,16 @@ use Throwable;
|
|||||||
|
|
||||||
final class ResponsiveImage
|
final class ResponsiveImage
|
||||||
{
|
{
|
||||||
/** @var list<int> */
|
/**
|
||||||
public const WIDTHS = [480, 960, 1440];
|
* 720 sits between the original 480 and 960 because that is where the common
|
||||||
|
* mobile viewport lands: 412 CSS px at a 1.75 device pixel ratio asks for
|
||||||
|
* ~721 px, so a full-width image used to jump straight to the 960 variant and
|
||||||
|
* pay for a third more pixels than it drew. Measured on the hero (MAN-109):
|
||||||
|
* 143 KiB at 960 in jpeg against 75 KiB at 720 in webp.
|
||||||
|
*
|
||||||
|
* @var list<int>
|
||||||
|
*/
|
||||||
|
public const WIDTHS = [480, 720, 960, 1440];
|
||||||
|
|
||||||
public static function generate(string $path, ?string $disk = null): void
|
public static function generate(string $path, ?string $disk = null): void
|
||||||
{
|
{
|
||||||
@@ -47,6 +55,26 @@ final class ResponsiveImage
|
|||||||
};
|
};
|
||||||
|
|
||||||
$filesystem->put($variantPath, (string) $encoded);
|
$filesystem->put($variantPath, (string) $encoded);
|
||||||
|
|
||||||
|
// A webp sibling for every variant. The MAN-109 audit measured the
|
||||||
|
// hero as the LCP element on every mobile page, at 143 KiB for a
|
||||||
|
// 960 px jpeg — webp carries the same picture for roughly a third of
|
||||||
|
// that. `x-media.image` offers these through a <source> so a browser
|
||||||
|
// that cannot decode webp still gets the original format.
|
||||||
|
if ($extension === 'webp') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$webp = $manager->read($contents);
|
||||||
|
|
||||||
|
if ($webp->width() > $width) {
|
||||||
|
$webp->scale(width: $width);
|
||||||
|
}
|
||||||
|
|
||||||
|
$filesystem->put(
|
||||||
|
self::webpVariantPath($path, $width),
|
||||||
|
(string) $webp->toWebp(quality: 80)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,13 +83,13 @@ final class ResponsiveImage
|
|||||||
$filesystem = self::filesystem($disk);
|
$filesystem = self::filesystem($disk);
|
||||||
|
|
||||||
foreach (self::WIDTHS as $width) {
|
foreach (self::WIDTHS as $width) {
|
||||||
$variantPath = self::variantPath($path, $width);
|
foreach ([self::variantPath($path, $width), self::webpVariantPath($path, $width)] as $variantPath) {
|
||||||
|
|
||||||
if ($filesystem->exists($variantPath)) {
|
if ($filesystem->exists($variantPath)) {
|
||||||
$filesystem->delete($variantPath);
|
$filesystem->delete($variantPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function delete(string $path, ?string $disk = null): void
|
public static function delete(string $path, ?string $disk = null): void
|
||||||
{
|
{
|
||||||
@@ -93,6 +121,17 @@ final class ResponsiveImage
|
|||||||
return $directory === '' ? $variantName : $directory.'/'.$variantName;
|
return $directory === '' ? $variantName : $directory.'/'.$variantName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The webp sibling of a variant, named by appending rather than replacing the
|
||||||
|
* extension. Uploads are stored under a UUID so a collision is already
|
||||||
|
* unlikely, but `photo-480.jpg.webp` cannot collide with the webp variant of
|
||||||
|
* a `photo.png` the way `photo-480.webp` would.
|
||||||
|
*/
|
||||||
|
public static function webpVariantPath(string $path, int $width): string
|
||||||
|
{
|
||||||
|
return self::variantPath($path, $width).'.webp';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return list<array{path: string, width: int}>
|
* @return list<array{path: string, width: int}>
|
||||||
*/
|
*/
|
||||||
@@ -115,6 +154,32 @@ final class ResponsiveImage
|
|||||||
return $variants;
|
return $variants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The webp variants that exist for a path. Empty when the media predates
|
||||||
|
* `media:generate-variants` running with webp support, which is why
|
||||||
|
* `x-media.image` treats the <source> as optional rather than assuming it.
|
||||||
|
*
|
||||||
|
* @return list<array{path: string, width: int}>
|
||||||
|
*/
|
||||||
|
public static function availableWebpVariants(string $path, ?string $disk = null): array
|
||||||
|
{
|
||||||
|
$filesystem = self::filesystem($disk);
|
||||||
|
$variants = [];
|
||||||
|
|
||||||
|
foreach (self::WIDTHS as $width) {
|
||||||
|
$variantPath = self::webpVariantPath($path, $width);
|
||||||
|
|
||||||
|
if ($filesystem->exists($variantPath)) {
|
||||||
|
$variants[] = [
|
||||||
|
'path' => $variantPath,
|
||||||
|
'width' => $width,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $variants;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{width: int, height: int}|null
|
* @return array{width: int, height: int}|null
|
||||||
*/
|
*/
|
||||||
|
|||||||
61
docker/ci-runner.Dockerfile
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Linux runner that reproduces CI's rendering environment for the visual
|
||||||
|
# regression baselines. Not part of the application image and never deployed.
|
||||||
|
#
|
||||||
|
# Why this exists: the baselines are pixel artifacts of the machine that
|
||||||
|
# rendered them. Pest Browser serves the Laravel kernel from an in-process Amp
|
||||||
|
# server (vendor/pestphp/pest-plugin-browser/src/Drivers/LaravelHttpServer.php),
|
||||||
|
# so FrankenPHP is not in the picture — what differs between a developer's Mac
|
||||||
|
# and CI is the OS, the Chromium build and the font stack. Regenerating on
|
||||||
|
# macOS produces baselines CI rejects, which is the whole reason commit
|
||||||
|
# 4578457 exists. Before this file the recipe lived only as a checklist in
|
||||||
|
# tasks.md and the image had to be reconstructed by archaeology.
|
||||||
|
#
|
||||||
|
# Mirrors the `browser` job in .github/workflows/ci.yml: Ubuntu 24.04,
|
||||||
|
# PHP 8.4 with the same extension list, Node 22, and Playwright's own system
|
||||||
|
# dependencies (which is where fonts-liberation comes from — StableScreenshot
|
||||||
|
# forces `Arial`, and on Linux fontconfig resolves that to the
|
||||||
|
# metric-compatible Liberation Sans).
|
||||||
|
#
|
||||||
|
# Driven by scripts/test/visual-update-ci.sh; see that script for usage.
|
||||||
|
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
|
PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
git \
|
||||||
|
gnupg \
|
||||||
|
software-properties-common \
|
||||||
|
unzip \
|
||||||
|
&& add-apt-repository -y ppa:ondrej/php \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
php8.4-cli \
|
||||||
|
php8.4-bcmath \
|
||||||
|
php8.4-curl \
|
||||||
|
php8.4-gd \
|
||||||
|
php8.4-intl \
|
||||||
|
php8.4-mbstring \
|
||||||
|
php8.4-pgsql \
|
||||||
|
php8.4-sqlite3 \
|
||||||
|
php8.4-xml \
|
||||||
|
php8.4-zip \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
||||||
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
# System libraries and fonts only. The browser binary itself is installed at
|
||||||
|
# run time so its revision matches whatever playwright version package-lock
|
||||||
|
# resolves, exactly as CI's `npx playwright install chromium --with-deps` does.
|
||||||
|
RUN npx --yes playwright@1.62 install-deps chromium \
|
||||||
|
&& rm -rf /root/.npm
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
CMD ["bash"]
|
||||||
409
docs/evidence/lighthouse/2026-08-10-local-antes.md
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
# Lighthouse — local
|
||||||
|
|
||||||
|
- Origem: `http://127.0.0.1:8000`
|
||||||
|
- Lighthouse: 12.8.2
|
||||||
|
- Navegador: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/151.0.0.0 Safari/537.36`
|
||||||
|
- Coletado em: 2026-08-10T15:55:03.101Z
|
||||||
|
- Execuções por página/preset: 3 (reportada a de LCP mediano)
|
||||||
|
- Commit: `2e43fde`
|
||||||
|
- Seeder: `ContentSeeder`
|
||||||
|
|
||||||
|
Metas SPEC §6.6: LCP ≤ 2,5 s · CLS ≤ 0,1 · INP ≤ 200 ms · zero erro de console.
|
||||||
|
|
||||||
|
| página | preset | perf | a11y | BP | SEO | LCP | FCP | CLS | TBT | TTFB servidor | LCP min–max |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| contato | desktop | 100 | 100 | 100 | 100 | 0.53 s | 0.37 s | 0.000 | 0 ms | 55 ms | 0.53 s – 0.53 s |
|
||||||
|
| contato | mobile | 97 | 100 | 100 | 100 | 2.55 s | 1.50 s | 0.000 | 0 ms | 97 ms | 2.55 s – 2.56 s |
|
||||||
|
| home | desktop | 99 | 100 | 100 | 100 | 0.87 s | 0.37 s | 0.000 | 0 ms | 75 ms | 0.87 s – 0.87 s |
|
||||||
|
| home | mobile | 83 | 100 | 100 | 100 | 4.58 s | 1.51 s | 0.000 | 0 ms | 217 ms | 4.36 s – 4.58 s |
|
||||||
|
| portfolio-detalhe | desktop | 99 | 100 | 100 | 100 | 0.97 s | 0.37 s | 0.000 | 0 ms | 89 ms | 0.97 s – 0.97 s |
|
||||||
|
| portfolio-detalhe | mobile | 87 | 100 | 100 | 100 | 3.98 s | 1.51 s | 0.000 | 0 ms | 126 ms | 3.98 s – 4.05 s |
|
||||||
|
| portfolio | desktop | 99 | 100 | 100 | 100 | 0.83 s | 0.37 s | 0.000 | 0 ms | 187 ms | 0.63 s – 0.83 s |
|
||||||
|
| portfolio | mobile | 87 | 100 | 100 | 100 | 3.98 s | 1.51 s | 0.000 | 0 ms | 108 ms | 3.91 s – 4.20 s |
|
||||||
|
| servicos | desktop | 100 | 100 | 100 | 100 | 0.77 s | 0.37 s | 0.000 | 0 ms | 124 ms | 0.77 s – 0.77 s |
|
||||||
|
| servicos | mobile | 89 | 100 | 100 | 100 | 3.68 s | 1.51 s | 0.000 | 0 ms | 78 ms | 3.61 s – 3.68 s |
|
||||||
|
| sobre | desktop | 100 | 100 | 100 | 100 | 0.61 s | 0.37 s | 0.000 | 0 ms | 48 ms | 0.57 s – 0.61 s |
|
||||||
|
| sobre | mobile | 94 | 100 | 100 | 100 | 3.01 s | 1.51 s | 0.000 | 0 ms | 133 ms | 2.87 s – 3.01 s |
|
||||||
|
|
||||||
|
## Decomposição do LCP
|
||||||
|
|
||||||
|
### contato — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<h1 class="text-headline font-medium tracking-tight text-amare-text">`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.00 s |
|
||||||
|
| Load Time | 0.00 s |
|
||||||
|
| Render Delay | 0.41 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 83 KiB | 0.07 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-xCe6GkOx.woff` | Font | 30 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DZU8xF9o.woff` | Font | 30 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-maQnHeKB.woff` | Font | 28 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.07 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.06 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.06 s |
|
||||||
|
| `/build/assets/app-Cswasu1n.css` | Stylesheet | 10 KiB | 0.06 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.10 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### contato — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<h1 class="text-headline font-medium tracking-tight text-amare-text">`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 0.00 s |
|
||||||
|
| Load Time | 0.00 s |
|
||||||
|
| Render Delay | 2.10 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 83 KiB | 0.11 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-xCe6GkOx.woff` | Font | 30 KiB | 0.12 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DZU8xF9o.woff` | Font | 30 KiB | 0.12 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-maQnHeKB.woff` | Font | 28 KiB | 0.12 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.11 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.11 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.11 s |
|
||||||
|
| `/build/assets/app-Cswasu1n.css` | Stylesheet | 10 KiB | 0.11 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Render blocking requests | 0.60 s | — |
|
||||||
|
| Improve image delivery | 0.45 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.15 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
|
||||||
|
### home — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/og/og-default-960.jpg" srcset="/storage/content/og/og-default-480.jpg 480w, /storage/content/og/og-defaul…" size`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.45 s |
|
||||||
|
| Load Time | 0.03 s |
|
||||||
|
| Render Delay | 0.27 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/og/og-default-960.jpg` | Image | 143 KiB | 0.09 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-960.jpg` | Image | 142 KiB | 0.10 s |
|
||||||
|
| `/storage/content/portfolio/lancamento-verano-cover-960.jpg` | Image | 106 KiB | 0.10 s |
|
||||||
|
| `/storage/content/portfolio/mini-wedding-marina-cover-960.jpg` | Image | 100 KiB | 0.10 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 83 KiB | 0.08 s |
|
||||||
|
| `/brand/mark-on-light.webp` | Image | 42 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-xCe6GkOx.woff` | Font | 30 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DZU8xF9o.woff` | Font | 30 KiB | 0.09 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.25 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.05 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### home — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/og/og-default-960.jpg" srcset="/storage/content/og/og-default-480.jpg 480w, /storage/content/og/og-defaul…" size`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 3.30 s |
|
||||||
|
| Load Time | 0.11 s |
|
||||||
|
| Render Delay | 0.72 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/og/og-default-960.jpg` | Image | 143 KiB | 0.23 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-960.jpg` | Image | 142 KiB | 0.25 s |
|
||||||
|
| `/storage/content/portfolio/lancamento-verano-cover-960.jpg` | Image | 106 KiB | 0.25 s |
|
||||||
|
| `/storage/content/portfolio/mini-wedding-marina-cover-960.jpg` | Image | 100 KiB | 0.25 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 83 KiB | 0.23 s |
|
||||||
|
| `/brand/mark-on-light.webp` | Image | 42 KiB | 0.23 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-xCe6GkOx.woff` | Font | 30 KiB | 0.24 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DZU8xF9o.woff` | Font | 30 KiB | 0.24 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 1.35 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.30 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### portfolio-detalhe — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/portfolio/casamento-ana-lucas-cover-…" srcset="/storage/content/portfolio/casamento-ana-lucas-cover-480.jpg 480w`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.54 s |
|
||||||
|
| Load Time | 0.04 s |
|
||||||
|
| Render Delay | 0.26 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-1440.jpg` | Image | 287 KiB | 0.10 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-gallery-1-960.jpg` | Image | 209 KiB | 0.11 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-gallery-2-960.jpg` | Image | 172 KiB | 0.11 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 83 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-xCe6GkOx.woff` | Font | 30 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DZU8xF9o.woff` | Font | 30 KiB | 0.11 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-maQnHeKB.woff` | Font | 28 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.10 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.20 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.05 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### portfolio-detalhe — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/portfolio/casamento-ana-lucas-cover-…" srcset="/storage/content/portfolio/casamento-ana-lucas-cover-480.jpg 480w`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 2.62 s |
|
||||||
|
| Load Time | 0.06 s |
|
||||||
|
| Render Delay | 0.85 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-gallery-1-960.jpg` | Image | 209 KiB | 0.16 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-gallery-2-960.jpg` | Image | 172 KiB | 0.16 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-960.jpg` | Image | 142 KiB | 0.14 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 83 KiB | 0.14 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-xCe6GkOx.woff` | Font | 30 KiB | 0.15 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DZU8xF9o.woff` | Font | 30 KiB | 0.15 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-maQnHeKB.woff` | Font | 28 KiB | 0.15 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.14 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 1.20 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.15 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### portfolio — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/portfolio/casamento-ana-lucas-cover-…" srcset="/storage/content/portfolio/casamento-ana-lucas-cover-480.jpg 480w`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.19 s |
|
||||||
|
| Load Delay | 0.51 s |
|
||||||
|
| Load Time | 0.01 s |
|
||||||
|
| Render Delay | 0.12 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-960.jpg` | Image | 142 KiB | 0.23 s |
|
||||||
|
| `/storage/content/portfolio/lancamento-verano-cover-960.jpg` | Image | 106 KiB | 0.23 s |
|
||||||
|
| `/storage/content/portfolio/mini-wedding-marina-cover-960.jpg` | Image | 100 KiB | 0.23 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 83 KiB | 0.20 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-xCe6GkOx.woff` | Font | 30 KiB | 0.23 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DZU8xF9o.woff` | Font | 30 KiB | 0.23 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-maQnHeKB.woff` | Font | 28 KiB | 0.22 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.21 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.25 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### portfolio — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/portfolio/casamento-ana-lucas-cover-…" srcset="/storage/content/portfolio/casamento-ana-lucas-cover-480.jpg 480w`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 2.68 s |
|
||||||
|
| Load Time | 0.09 s |
|
||||||
|
| Render Delay | 0.76 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-960.jpg` | Image | 142 KiB | 0.13 s |
|
||||||
|
| `/storage/content/portfolio/lancamento-verano-cover-960.jpg` | Image | 106 KiB | 0.13 s |
|
||||||
|
| `/storage/content/portfolio/mini-wedding-marina-cover-960.jpg` | Image | 100 KiB | 0.13 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 83 KiB | 0.12 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-xCe6GkOx.woff` | Font | 30 KiB | 0.13 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DZU8xF9o.woff` | Font | 30 KiB | 0.13 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-maQnHeKB.woff` | Font | 28 KiB | 0.13 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.12 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 1.35 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.15 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### servicos — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/services/casamentos-960.jpg" srcset="/storage/content/services/casamentos-480.jpg 480w, /storage/content/servic…`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.13 s |
|
||||||
|
| Load Delay | 0.49 s |
|
||||||
|
| Load Time | 0.03 s |
|
||||||
|
| Render Delay | 0.12 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/services/casamentos-960.jpg` | Image | 107 KiB | 0.15 s |
|
||||||
|
| `/storage/content/services/eventos-corporativos-960.jpg` | Image | 100 KiB | 0.15 s |
|
||||||
|
| `/storage/content/services/celebracoes-intimistas-960.jpg` | Image | 83 KiB | 0.15 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 83 KiB | 0.14 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-xCe6GkOx.woff` | Font | 30 KiB | 0.15 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DZU8xF9o.woff` | Font | 30 KiB | 0.15 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-maQnHeKB.woff` | Font | 28 KiB | 0.15 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.14 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.20 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.05 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### servicos — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/services/casamentos-960.jpg" srcset="/storage/content/services/casamentos-480.jpg 480w, /storage/content/servic…`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 2.22 s |
|
||||||
|
| Load Time | 0.07 s |
|
||||||
|
| Render Delay | 0.94 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/services/casamentos-960.jpg` | Image | 107 KiB | 0.12 s |
|
||||||
|
| `/storage/content/services/eventos-corporativos-960.jpg` | Image | 100 KiB | 0.12 s |
|
||||||
|
| `/storage/content/services/celebracoes-intimistas-960.jpg` | Image | 83 KiB | 0.12 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 83 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-xCe6GkOx.woff` | Font | 30 KiB | 0.12 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DZU8xF9o.woff` | Font | 30 KiB | 0.12 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-maQnHeKB.woff` | Font | 28 KiB | 0.12 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.09 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 1.20 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.15 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### sobre — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/about/about-image-960.jpg" srcset="/storage/content/about/about-image-480.jpg 480w, /storage/content/about/ab…" `
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.25 s |
|
||||||
|
| Load Time | 0.01 s |
|
||||||
|
| Render Delay | 0.22 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 83 KiB | 0.06 s |
|
||||||
|
| `/storage/content/about/about-image-960.jpg` | Image | 60 KiB | 0.07 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-xCe6GkOx.woff` | Font | 30 KiB | 0.06 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DZU8xF9o.woff` | Font | 30 KiB | 0.06 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-maQnHeKB.woff` | Font | 28 KiB | 0.06 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.06 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.05 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.05 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.10 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.05 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### sobre — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/about/about-image-960.jpg" srcset="/storage/content/about/about-image-480.jpg 480w, /storage/content/about/ab…" `
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 1.94 s |
|
||||||
|
| Load Time | 0.07 s |
|
||||||
|
| Render Delay | 0.54 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 83 KiB | 0.14 s |
|
||||||
|
| `/storage/content/about/about-image-960.jpg` | Image | 60 KiB | 0.16 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-xCe6GkOx.woff` | Font | 30 KiB | 0.15 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DZU8xF9o.woff` | Font | 30 KiB | 0.15 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-maQnHeKB.woff` | Font | 28 KiB | 0.16 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.14 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.14 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.14 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.90 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.30 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
407
docs/evidence/lighthouse/2026-08-10-local-depois.md
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
# Lighthouse — local-sizes
|
||||||
|
|
||||||
|
- Origem: `http://127.0.0.1:8000`
|
||||||
|
- Lighthouse: 12.8.2
|
||||||
|
- Navegador: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/151.0.0.0 Safari/537.36`
|
||||||
|
- Coletado em: 2026-08-10T17:38:33.741Z
|
||||||
|
- Execuções por página/preset: 3 (reportada a de LCP mediano)
|
||||||
|
- Commit: `9ab2beb` (o script gravou `65919d4`, o HEAD no momento da coleta; a árvore medida é a que virou `9ab2beb`)
|
||||||
|
- Seeder: `ContentSeeder`
|
||||||
|
|
||||||
|
Metas SPEC §6.6: LCP ≤ 2,5 s · CLS ≤ 0,1 · INP ≤ 200 ms · zero erro de console.
|
||||||
|
|
||||||
|
| página | preset | perf | a11y | BP | SEO | LCP | FCP | CLS | TBT | TTFB servidor | LCP min–max |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| contato | desktop | 100 | 100 | 100 | 100 | 0.36 s | 0.25 s | 0.000 | 0 ms | 91 ms | 0.36 s – 0.37 s |
|
||||||
|
| contato | mobile | 100 | 100 | 100 | 100 | 1.50 s | 0.92 s | 0.000 | 0 ms | 66 ms | 1.50 s – 1.51 s |
|
||||||
|
| home | desktop | 100 | 100 | 100 | 100 | 0.53 s | 0.25 s | 0.000 | 0 ms | 110 ms | 0.53 s – 0.54 s |
|
||||||
|
| home | mobile | 98 | 100 | 100 | 100 | 2.49 s | 0.92 s | 0.000 | 0 ms | 77 ms | 2.48 s – 2.57 s |
|
||||||
|
| portfolio-detalhe | desktop | 100 | 100 | 100 | 100 | 0.65 s | 0.25 s | 0.000 | 0 ms | 81 ms | 0.65 s – 0.66 s |
|
||||||
|
| portfolio-detalhe | mobile | 99 | 100 | 100 | 100 | 2.18 s | 0.90 s | 0.000 | 0 ms | 77 ms | 2.18 s – 2.18 s |
|
||||||
|
| portfolio | desktop | 100 | 100 | 100 | 100 | 0.36 s | 0.25 s | 0.000 | 0 ms | 72 ms | 0.36 s – 0.49 s |
|
||||||
|
| portfolio | mobile | 100 | 100 | 100 | 100 | 1.58 s | 0.91 s | 0.000 | 0 ms | 71 ms | 1.58 s – 1.58 s |
|
||||||
|
| servicos | desktop | 100 | 100 | 100 | 100 | 0.36 s | 0.24 s | 0.000 | 0 ms | 59 ms | 0.36 s – 0.38 s |
|
||||||
|
| servicos | mobile | 99 | 100 | 100 | 100 | 2.03 s | 0.90 s | 0.000 | 0 ms | 62 ms | 1.58 s – 2.03 s |
|
||||||
|
| sobre | desktop | 100 | 100 | 100 | 100 | 0.36 s | 0.25 s | 0.000 | 0 ms | 51 ms | 0.36 s – 0.37 s |
|
||||||
|
| sobre | mobile | 100 | 100 | 100 | 100 | 1.58 s | 0.91 s | 0.000 | 0 ms | 93 ms | 1.58 s – 1.58 s |
|
||||||
|
|
||||||
|
## Decomposição do LCP
|
||||||
|
|
||||||
|
### contato — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<h1 class="text-headline font-medium tracking-tight text-amare-text">`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.00 s |
|
||||||
|
| Load Time | 0.00 s |
|
||||||
|
| Render Delay | 0.24 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.10 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.10 s |
|
||||||
|
| `/build/assets/app-8wBXH8kj.css` | Stylesheet | 10 KiB | 0.10 s |
|
||||||
|
| `/contato` | Document | 5 KiB | 0.09 s |
|
||||||
|
| `/build/assets/app-Cr9w0NCu.js` | Script | 2 KiB | 0.10 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### contato — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<h1 class="text-headline font-medium tracking-tight text-amare-text">`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 0.00 s |
|
||||||
|
| Load Time | 0.00 s |
|
||||||
|
| Render Delay | 1.05 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.07 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.07 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.08 s |
|
||||||
|
| `/build/assets/app-8wBXH8kj.css` | Stylesheet | 10 KiB | 0.08 s |
|
||||||
|
| `/contato` | Document | 5 KiB | 0.07 s |
|
||||||
|
| `/build/assets/app-Cr9w0NCu.js` | Script | 2 KiB | 0.08 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### home — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/og/og-default-720.jpg.webp" srcset="/storage/content/og/og-default-480.jpg 480w, /storage/content/og/og-defaul…"`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.25 s |
|
||||||
|
| Load Time | 0.03 s |
|
||||||
|
| Render Delay | 0.12 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/og/og-default-720.jpg.webp` | Image | 74 KiB | 0.13 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-720.jpg.webp` | Image | 67 KiB | 0.14 s |
|
||||||
|
| `/storage/content/portfolio/lancamento-verano-cover-720.jpg.webp` | Image | 45 KiB | 0.14 s |
|
||||||
|
| `/storage/content/portfolio/mini-wedding-marina-cover-720.jpg.webp` | Image | 43 KiB | 0.14 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.12 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.12 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.12 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.12 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.10 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### home — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/og/og-default-720.jpg.webp" srcset="/storage/content/og/og-default-480.jpg 480w, /storage/content/og/og-defaul…"`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 1.24 s |
|
||||||
|
| Load Time | 0.10 s |
|
||||||
|
| Render Delay | 0.69 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/og/og-default-720.jpg.webp` | Image | 74 KiB | 0.09 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-720.jpg.webp` | Image | 67 KiB | 0.10 s |
|
||||||
|
| `/storage/content/portfolio/lancamento-verano-cover-720.jpg.webp` | Image | 45 KiB | 0.10 s |
|
||||||
|
| `/storage/content/portfolio/mini-wedding-marina-cover-720.jpg.webp` | Image | 43 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.09 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.09 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.45 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### portfolio-detalhe — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/portfolio/casamento-ana-lucas-cover-…" srcset="/storage/content/portfolio/casamento-ana-lucas-cover-480.jpg 480w`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.28 s |
|
||||||
|
| Load Time | 0.03 s |
|
||||||
|
| Render Delay | 0.22 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-1440.jpg.webp` | Image | 210 KiB | 0.09 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-gallery-1-720.jpg.webp` | Image | 105 KiB | 0.10 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-gallery-2-720.jpg.webp` | Image | 88 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.09 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.09 s |
|
||||||
|
| `/build/assets/app-8wBXH8kj.css` | Stylesheet | 10 KiB | 0.09 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.10 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### portfolio-detalhe — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/portfolio/casamento-ana-lucas-cover-…" srcset="/storage/content/portfolio/casamento-ana-lucas-cover-480.jpg 480w`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 1.11 s |
|
||||||
|
| Load Time | 0.04 s |
|
||||||
|
| Render Delay | 0.57 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-gallery-1-720.jpg.webp` | Image | 105 KiB | 0.09 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-gallery-2-720.jpg.webp` | Image | 88 KiB | 0.09 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-720.jpg.webp` | Image | 67 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.08 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.09 s |
|
||||||
|
| `/build/assets/app-8wBXH8kj.css` | Stylesheet | 10 KiB | 0.09 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.45 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### portfolio — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/portfolio/casamento-ana-lucas-cover-…" srcset="/storage/content/portfolio/casamento-ana-lucas-cover-480.jpg 480w`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.14 s |
|
||||||
|
| Load Time | 0.01 s |
|
||||||
|
| Render Delay | 0.09 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-720.jpg.webp` | Image | 67 KiB | 0.09 s |
|
||||||
|
| `/storage/content/portfolio/lancamento-verano-cover-720.jpg.webp` | Image | 45 KiB | 0.09 s |
|
||||||
|
| `/storage/content/portfolio/mini-wedding-marina-cover-720.jpg.webp` | Image | 43 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.08 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.08 s |
|
||||||
|
| `/build/assets/app-8wBXH8kj.css` | Stylesheet | 10 KiB | 0.08 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### portfolio — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/portfolio/casamento-ana-lucas-cover-…" srcset="/storage/content/portfolio/casamento-ana-lucas-cover-480.jpg 480w`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 0.66 s |
|
||||||
|
| Load Time | 0.04 s |
|
||||||
|
| Render Delay | 0.42 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-720.jpg.webp` | Image | 67 KiB | 0.09 s |
|
||||||
|
| `/storage/content/portfolio/lancamento-verano-cover-720.jpg.webp` | Image | 45 KiB | 0.09 s |
|
||||||
|
| `/storage/content/portfolio/mini-wedding-marina-cover-720.jpg.webp` | Image | 43 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.08 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.08 s |
|
||||||
|
| `/build/assets/app-8wBXH8kj.css` | Stylesheet | 10 KiB | 0.08 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### servicos — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/services/casamentos-720.jpg.webp" srcset="/storage/content/services/casamentos-480.jpg 480w, /storage/content/se`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.14 s |
|
||||||
|
| Load Time | 0.01 s |
|
||||||
|
| Render Delay | 0.10 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/services/casamentos-720.jpg.webp` | Image | 49 KiB | 0.08 s |
|
||||||
|
| `/storage/content/services/eventos-corporativos-720.jpg.webp` | Image | 39 KiB | 0.08 s |
|
||||||
|
| `/storage/content/services/celebracoes-intimistas-720.jpg.webp` | Image | 28 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.07 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.06 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.06 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.07 s |
|
||||||
|
| `/build/assets/app-8wBXH8kj.css` | Stylesheet | 10 KiB | 0.07 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### servicos — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/services/casamentos-720.jpg.webp" srcset="/storage/content/services/casamentos-480.jpg 480w, /storage/content/se`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 1.08 s |
|
||||||
|
| Load Time | 0.07 s |
|
||||||
|
| Render Delay | 0.43 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/services/casamentos-720.jpg.webp` | Image | 49 KiB | 0.08 s |
|
||||||
|
| `/storage/content/services/eventos-corporativos-720.jpg.webp` | Image | 39 KiB | 0.08 s |
|
||||||
|
| `/storage/content/services/celebracoes-intimistas-720.jpg.webp` | Image | 28 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.07 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.07 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.07 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.07 s |
|
||||||
|
| `/build/assets/app-8wBXH8kj.css` | Stylesheet | 10 KiB | 0.07 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.45 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.15 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### sobre — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/about/about-image-720.jpg.webp" srcset="/storage/content/about/about-image-480.jpg 480w, /storage/content/about/`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.11 s |
|
||||||
|
| Load Time | 0.01 s |
|
||||||
|
| Render Delay | 0.12 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.06 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.06 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.06 s |
|
||||||
|
| `/storage/content/about/about-image-720.jpg.webp` | Image | 22 KiB | 0.07 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.06 s |
|
||||||
|
| `/build/assets/app-8wBXH8kj.css` | Stylesheet | 10 KiB | 0.06 s |
|
||||||
|
| `/sobre` | Document | 5 KiB | 0.05 s |
|
||||||
|
| `/build/assets/app-Cr9w0NCu.js` | Script | 2 KiB | 0.06 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### sobre — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/about/about-image-720.jpg.webp" srcset="/storage/content/about/about-image-480.jpg 480w, /storage/content/about/`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 0.77 s |
|
||||||
|
| Load Time | 0.02 s |
|
||||||
|
| Render Delay | 0.33 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.10 s |
|
||||||
|
| `/storage/content/about/about-image-720.jpg.webp` | Image | 22 KiB | 0.11 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.10 s |
|
||||||
|
| `/build/assets/app-8wBXH8kj.css` | Stylesheet | 10 KiB | 0.10 s |
|
||||||
|
| `/sobre` | Document | 5 KiB | 0.09 s |
|
||||||
|
| `/build/assets/app-Cr9w0NCu.js` | Script | 2 KiB | 0.10 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
@@ -0,0 +1,407 @@
|
|||||||
|
# Lighthouse — local-pos
|
||||||
|
|
||||||
|
- Origem: `http://127.0.0.1:8000`
|
||||||
|
- Lighthouse: 12.8.2
|
||||||
|
- Navegador: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/151.0.0.0 Safari/537.36`
|
||||||
|
- Coletado em: 2026-08-10T16:20:48.372Z
|
||||||
|
- Execuções por página/preset: 3 (reportada a de LCP mediano)
|
||||||
|
- Commit: `65919d4` (o script gravou `2e43fde`, o HEAD no momento da coleta; a árvore medida é a que virou `65919d4`)
|
||||||
|
- Seeder: `ContentSeeder`
|
||||||
|
|
||||||
|
Metas SPEC §6.6: LCP ≤ 2,5 s · CLS ≤ 0,1 · INP ≤ 200 ms · zero erro de console.
|
||||||
|
|
||||||
|
| página | preset | perf | a11y | BP | SEO | LCP | FCP | CLS | TBT | TTFB servidor | LCP min–max |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| contato | desktop | 100 | 100 | 100 | 100 | 0.36 s | 0.25 s | 0.000 | 0 ms | 20 ms | 0.36 s – 0.37 s |
|
||||||
|
| contato | mobile | 100 | 100 | 100 | 100 | 1.51 s | 0.92 s | 0.000 | 0 ms | 46 ms | 1.50 s – 1.51 s |
|
||||||
|
| home | desktop | 100 | 100 | 100 | 100 | 0.67 s | 0.25 s | 0.000 | 0 ms | 87 ms | 0.67 s – 0.67 s |
|
||||||
|
| home | mobile | 92 | 100 | 100 | 100 | 3.39 s | 0.92 s | 0.000 | 0 ms | 88 ms | 3.38 s – 3.46 s |
|
||||||
|
| portfolio-detalhe | desktop | 100 | 100 | 100 | 100 | 0.79 s | 0.25 s | 0.000 | 0 ms | 74 ms | 0.79 s – 0.79 s |
|
||||||
|
| portfolio-detalhe | mobile | 95 | 100 | 100 | 100 | 3.01 s | 0.91 s | 0.000 | 0 ms | 89 ms | 2.93 s – 3.01 s |
|
||||||
|
| portfolio | desktop | 100 | 100 | 100 | 100 | 0.65 s | 0.25 s | 0.000 | 0 ms | 73 ms | 0.37 s – 0.65 s |
|
||||||
|
| portfolio | mobile | 100 | 100 | 100 | 100 | 1.58 s | 0.91 s | 0.000 | 0 ms | 69 ms | 1.58 s – 3.24 s |
|
||||||
|
| servicos | desktop | 100 | 100 | 100 | 100 | 0.36 s | 0.25 s | 0.000 | 0 ms | 121 ms | 0.36 s – 0.59 s |
|
||||||
|
| servicos | mobile | 97 | 100 | 100 | 100 | 2.63 s | 0.90 s | 0.000 | 0 ms | 73 ms | 1.58 s – 2.63 s |
|
||||||
|
| sobre | desktop | 100 | 100 | 100 | 100 | 0.37 s | 0.25 s | 0.000 | 0 ms | 97 ms | 0.36 s – 0.45 s |
|
||||||
|
| sobre | mobile | 100 | 100 | 100 | 100 | 1.58 s | 0.91 s | 0.000 | 0 ms | 48 ms | 1.58 s – 1.96 s |
|
||||||
|
|
||||||
|
## Decomposição do LCP
|
||||||
|
|
||||||
|
### contato — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<h1 class="text-headline font-medium tracking-tight text-amare-text">`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.00 s |
|
||||||
|
| Load Time | 0.00 s |
|
||||||
|
| Render Delay | 0.24 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.03 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.03 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.03 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.03 s |
|
||||||
|
| `/build/assets/app-Cswasu1n.css` | Stylesheet | 10 KiB | 0.03 s |
|
||||||
|
| `/contato` | Document | 5 KiB | 0.02 s |
|
||||||
|
| `/build/assets/app-Cr9w0NCu.js` | Script | 2 KiB | 0.03 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### contato — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<h1 class="text-headline font-medium tracking-tight text-amare-text">`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 0.00 s |
|
||||||
|
| Load Time | 0.00 s |
|
||||||
|
| Render Delay | 1.05 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.06 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.05 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.05 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.06 s |
|
||||||
|
| `/build/assets/app-Cswasu1n.css` | Stylesheet | 10 KiB | 0.06 s |
|
||||||
|
| `/contato` | Document | 5 KiB | 0.05 s |
|
||||||
|
| `/build/assets/app-Cr9w0NCu.js` | Script | 2 KiB | 0.06 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### home — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/og/og-default-960.jpg" srcset="/storage/content/og/og-default-480.jpg 480w, /storage/content/og/og-defaul…" size`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.33 s |
|
||||||
|
| Load Time | 0.03 s |
|
||||||
|
| Render Delay | 0.19 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/og/og-default-960.jpg` | Image | 143 KiB | 0.10 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-960.jpg` | Image | 142 KiB | 0.11 s |
|
||||||
|
| `/storage/content/portfolio/lancamento-verano-cover-960.jpg` | Image | 106 KiB | 0.11 s |
|
||||||
|
| `/storage/content/portfolio/mini-wedding-marina-cover-960.jpg` | Image | 100 KiB | 0.11 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.10 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.10 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.15 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### home — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/og/og-default-960.jpg" srcset="/storage/content/og/og-default-480.jpg 480w, /storage/content/og/og-defaul…" size`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 1.88 s |
|
||||||
|
| Load Time | 0.17 s |
|
||||||
|
| Render Delay | 0.88 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/og/og-default-960.jpg` | Image | 143 KiB | 0.10 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-960.jpg` | Image | 142 KiB | 0.12 s |
|
||||||
|
| `/storage/content/portfolio/lancamento-verano-cover-960.jpg` | Image | 106 KiB | 0.12 s |
|
||||||
|
| `/storage/content/portfolio/mini-wedding-marina-cover-960.jpg` | Image | 100 KiB | 0.12 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.10 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.10 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.90 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.15 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### portfolio-detalhe — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/portfolio/casamento-ana-lucas-cover-…" srcset="/storage/content/portfolio/casamento-ana-lucas-cover-480.jpg 480w`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.43 s |
|
||||||
|
| Load Time | 0.03 s |
|
||||||
|
| Render Delay | 0.21 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-1440.jpg` | Image | 287 KiB | 0.08 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-gallery-1-960.jpg` | Image | 209 KiB | 0.09 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-gallery-2-960.jpg` | Image | 172 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.08 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.08 s |
|
||||||
|
| `/build/assets/app-Cswasu1n.css` | Stylesheet | 10 KiB | 0.08 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.10 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### portfolio-detalhe — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/portfolio/casamento-ana-lucas-cover-…" srcset="/storage/content/portfolio/casamento-ana-lucas-cover-480.jpg 480w`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 1.63 s |
|
||||||
|
| Load Time | 0.06 s |
|
||||||
|
| Render Delay | 0.87 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-gallery-1-960.jpg` | Image | 209 KiB | 0.11 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-gallery-2-960.jpg` | Image | 172 KiB | 0.11 s |
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-960.jpg` | Image | 142 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.10 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.10 s |
|
||||||
|
| `/build/assets/app-Cswasu1n.css` | Stylesheet | 10 KiB | 0.10 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.90 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.15 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### portfolio — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/portfolio/casamento-ana-lucas-cover-…" srcset="/storage/content/portfolio/casamento-ana-lucas-cover-480.jpg 480w`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.28 s |
|
||||||
|
| Load Time | 0.05 s |
|
||||||
|
| Render Delay | 0.19 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-960.jpg` | Image | 142 KiB | 0.10 s |
|
||||||
|
| `/storage/content/portfolio/lancamento-verano-cover-960.jpg` | Image | 106 KiB | 0.10 s |
|
||||||
|
| `/storage/content/portfolio/mini-wedding-marina-cover-960.jpg` | Image | 100 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.08 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.08 s |
|
||||||
|
| `/build/assets/app-Cswasu1n.css` | Stylesheet | 10 KiB | 0.08 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.20 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### portfolio — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/portfolio/casamento-ana-lucas-cover-…" srcset="/storage/content/portfolio/casamento-ana-lucas-cover-480.jpg 480w`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 0.64 s |
|
||||||
|
| Load Time | 0.03 s |
|
||||||
|
| Render Delay | 0.46 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/portfolio/casamento-ana-lucas-cover-960.jpg` | Image | 142 KiB | 0.09 s |
|
||||||
|
| `/storage/content/portfolio/lancamento-verano-cover-960.jpg` | Image | 106 KiB | 0.09 s |
|
||||||
|
| `/storage/content/portfolio/mini-wedding-marina-cover-960.jpg` | Image | 100 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.08 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.08 s |
|
||||||
|
| `/build/assets/app-Cswasu1n.css` | Stylesheet | 10 KiB | 0.08 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### servicos — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/services/casamentos-960.jpg" srcset="/storage/content/services/casamentos-480.jpg 480w, /storage/content/servic…`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.12 s |
|
||||||
|
| Load Delay | 0.18 s |
|
||||||
|
| Load Time | 0.01 s |
|
||||||
|
| Render Delay | 0.06 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/services/casamentos-960.jpg` | Image | 107 KiB | 0.14 s |
|
||||||
|
| `/storage/content/services/eventos-corporativos-960.jpg` | Image | 100 KiB | 0.14 s |
|
||||||
|
| `/storage/content/services/celebracoes-intimistas-960.jpg` | Image | 83 KiB | 0.14 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.13 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.13 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.13 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.13 s |
|
||||||
|
| `/build/assets/app-Cswasu1n.css` | Stylesheet | 10 KiB | 0.13 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### servicos — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/services/casamentos-960.jpg" srcset="/storage/content/services/casamentos-480.jpg 480w, /storage/content/servic…`
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 1.50 s |
|
||||||
|
| Load Time | 0.11 s |
|
||||||
|
| Render Delay | 0.56 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/services/casamentos-960.jpg` | Image | 107 KiB | 0.09 s |
|
||||||
|
| `/storage/content/services/eventos-corporativos-960.jpg` | Image | 100 KiB | 0.09 s |
|
||||||
|
| `/storage/content/services/celebracoes-intimistas-960.jpg` | Image | 83 KiB | 0.09 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.08 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.08 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.08 s |
|
||||||
|
| `/build/assets/app-Cswasu1n.css` | Stylesheet | 10 KiB | 0.08 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Improve image delivery | 0.60 s | — |
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### sobre — desktop
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/about/about-image-960.jpg" srcset="/storage/content/about/about-image-480.jpg 480w, /storage/content/about/ab…" `
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.13 s |
|
||||||
|
| Load Delay | 0.15 s |
|
||||||
|
| Load Time | 0.00 s |
|
||||||
|
| Render Delay | 0.09 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/about/about-image-960.jpg` | Image | 60 KiB | 0.12 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.11 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.10 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.10 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.11 s |
|
||||||
|
| `/build/assets/app-Cswasu1n.css` | Stylesheet | 10 KiB | 0.11 s |
|
||||||
|
| `/sobre` | Document | 4 KiB | 0.10 s |
|
||||||
|
| `/build/assets/app-Cr9w0NCu.js` | Script | 2 KiB | 0.11 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
|
### sobre — mobile
|
||||||
|
|
||||||
|
Elemento de LCP: `<img src="http://127.0.0.1:8000/storage/content/about/about-image-960.jpg" srcset="/storage/content/about/about-image-480.jpg 480w, /storage/content/about/ab…" `
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0.45 s |
|
||||||
|
| Load Delay | 0.57 s |
|
||||||
|
| Load Time | 0.10 s |
|
||||||
|
| Render Delay | 0.46 s |
|
||||||
|
|
||||||
|
Requests concluídas até o LCP, mais pesadas primeiro:
|
||||||
|
|
||||||
|
| recurso | tipo | transferido | fim |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `/storage/content/about/about-image-960.jpg` | Image | 60 KiB | 0.07 s |
|
||||||
|
| `/build/assets/eb-garamond-600-normal-DHwxsLHv.woff2` | Font | 25 KiB | 0.06 s |
|
||||||
|
| `/build/assets/eb-garamond-500-normal-DehAIUv0.woff2` | Font | 25 KiB | 0.06 s |
|
||||||
|
| `/build/assets/eb-garamond-400-normal-BCNrxLz_.woff2` | Font | 24 KiB | 0.06 s |
|
||||||
|
| `/brand/lockup-on-light.webp` | Image | 14 KiB | 0.06 s |
|
||||||
|
| `/build/assets/app-Cswasu1n.css` | Stylesheet | 10 KiB | 0.06 s |
|
||||||
|
| `/sobre` | Document | 4 KiB | 0.05 s |
|
||||||
|
| `/build/assets/app-Cr9w0NCu.js` | Script | 2 KiB | 0.06 s |
|
||||||
|
|
||||||
|
| insight com falha | ganho estimado de LCP | bytes |
|
||||||
|
|---|---|---|
|
||||||
|
| Use efficient cache lifetimes | 0.00 s | — |
|
||||||
|
| Improve image delivery | 0.00 s | — |
|
||||||
|
| LCP request discovery | 0.00 s | — |
|
||||||
|
| Network dependency tree | 0.00 s | — |
|
||||||
|
| Render blocking requests | 0.00 s | — |
|
||||||
|
|
||||||
207
docs/evidence/lighthouse/README.md
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
# Lighthouse — MAN-109
|
||||||
|
|
||||||
|
Medições versionadas porque a rodada anterior (PR #34) sobreviveu apenas como
|
||||||
|
uma tabela digitada à mão num comentário do Linear: `storage/app/lighthouse` é
|
||||||
|
gitignored, então não havia contra o quê comparar. Os relatórios brutos pesam
|
||||||
|
~48 MB por passada e continuam fora do repositório; o que fica versionado são os
|
||||||
|
resumos, que já carregam a decomposição do LCP e a lista de requests até o LCP.
|
||||||
|
|
||||||
|
## Como reproduzir
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Sobe a imagem de produção. O seed roda no host, não no container:
|
||||||
|
# ContentSeeder é no-op fora de local/staging/testing (database/seeders/ContentSeeder.php:44),
|
||||||
|
# então sob APP_ENV=production ele não semeia nada e o Lighthouse mede páginas vazias.
|
||||||
|
docker build -t amare-app:man109 .
|
||||||
|
php artisan migrate --force
|
||||||
|
php artisan db:seed --class=ContentSeeder --force
|
||||||
|
php artisan media:generate-variants # sem isso o LCP da home infla ~2,5 s
|
||||||
|
docker run -d --name amare-web -p 8000:8000 -e APP_ENV=production ... amare-app:man109
|
||||||
|
|
||||||
|
TARGET=local bash scripts/perf/lighthouse.sh storage/app/lighthouse
|
||||||
|
```
|
||||||
|
|
||||||
|
Cada página é auditada 3 vezes por preset e o relatório de LCP mediano é o
|
||||||
|
reportado — nunca a média. O LCP se move alguns décimos entre execuções na mesma
|
||||||
|
build, e uma única execução não sustenta comparação.
|
||||||
|
|
||||||
|
## Resultado
|
||||||
|
|
||||||
|
Preset mobile padrão do Lighthouse 12.8.2: throttling simulado de 150 ms de RTT,
|
||||||
|
~1,6 Mbps, CPU 4× mais lenta. Chrome estável do sistema (não o Chromium do
|
||||||
|
Playwright), imagem de produção local, seeder `ContentSeeder`.
|
||||||
|
|
||||||
|
Antes: commit `2e43fde`. As colunas intermediárias mostram cada correção
|
||||||
|
isoladamente, medida com uma passada completa antes da seguinte entrar.
|
||||||
|
|
||||||
|
| página | preset | antes | + fontes e marca | + webp e 720w | + sizes correto | perf antes → depois | bytes antes → depois |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| contato | desktop | 0.53 s | 0.36 s | 0.37 s | **0.36 s** | 100 → 100 | 262 → 106 KiB |
|
||||||
|
| contato | mobile | 2.55 s | 1.51 s | 1.51 s | **1.50 s** | 97 → 100 | 262 → 106 KiB |
|
||||||
|
| home | desktop | 0.87 s | 0.67 s | 0.53 s | **0.53 s** | 99 → 100 | 798 → 347 KiB |
|
||||||
|
| home | mobile | 4.58 s | 3.39 s | 2.87 s | **2.49 s** | 83 → 98 | 798 → 347 KiB |
|
||||||
|
| portfolio | desktop | 0.83 s | 0.65 s | 0.37 s | **0.36 s** | 99 → 100 | 609 → 259 KiB |
|
||||||
|
| portfolio | mobile | 3.98 s | 1.58 s | 2.64 s | **1.58 s** | 87 → 100 | 609 → 259 KiB |
|
||||||
|
| portfolio-detalhe | desktop | 0.97 s | 0.79 s | 0.66 s | **0.65 s** | 99 → 100 | 930 → 508 KiB |
|
||||||
|
| portfolio-detalhe | mobile | 3.98 s | 3.01 s | 2.71 s | **2.18 s** | 87 → 99 | 785 → 365 KiB |
|
||||||
|
| servicos | desktop | 0.77 s | 0.36 s | 0.37 s | **0.36 s** | 100 → 100 | 552 → 222 KiB |
|
||||||
|
| servicos | mobile | 3.68 s | 2.63 s | 1.58 s | **2.03 s** | 89 → 99 | 552 → 222 KiB |
|
||||||
|
| sobre | desktop | 0.61 s | 0.37 s | 0.37 s | **0.36 s** | 100 → 100 | 322 → 127 KiB |
|
||||||
|
| sobre | mobile | 3.01 s | 1.58 s | 1.60 s | **1.58 s** | 94 → 100 | 322 → 127 KiB |
|
||||||
|
|
||||||
|
FCP cai de 1,51 s para 0,91 s em todas as páginas no mobile. Acessibilidade,
|
||||||
|
boas práticas e SEO marcam 100 em todas as páginas nos dois presets, antes e
|
||||||
|
depois. CLS é 0,000 e TBT é 0 ms em todas — as metas de §6.6 para essas três
|
||||||
|
métricas já passavam e continuam passando.
|
||||||
|
|
||||||
|
**Contra a meta de LCP ≤ 2,5 s da §6.6: todas as páginas passam nos dois
|
||||||
|
presets.** Desktop com folga (máximo 0,65 s). No mobile o pior caso é a home a
|
||||||
|
2,49 s, ou seja **em cima da linha** — a pior das três execuções dela deu 2,57 s.
|
||||||
|
Tratar a home como aprovada por margem, não com folga.
|
||||||
|
|
||||||
|
Duas colunas intermediárias merecem leitura cuidadosa em vez de conclusão:
|
||||||
|
|
||||||
|
- `portfolio` mobile aparece pior na coluna do webp (2,64 s) do que na anterior
|
||||||
|
(1,58 s). É variância, não regressão: as três execuções daquela passada foram
|
||||||
|
1,59 / 2,64 / 2,78 s. A página é a mais instável do conjunto e a mediana pulou
|
||||||
|
de ponta. Na passada final as três deram 1,58 s.
|
||||||
|
- `servicos` mobile sobe de 1,58 s para 2,03 s da terceira para a quarta coluna,
|
||||||
|
pelo mesmo motivo (1,58 / 1,58 / 2,03).
|
||||||
|
|
||||||
|
É exatamente por isso que o script roda três vezes e reporta a mediana; ainda
|
||||||
|
assim, diferenças abaixo de meio segundo entre passadas não devem ser lidas como
|
||||||
|
efeito de uma correção.
|
||||||
|
|
||||||
|
Ressalva: a medição é local, então latência de origem e TLS não entram, e o
|
||||||
|
throttling de rede é simulado. Trate o LCP como piso, não como valor de campo.
|
||||||
|
|
||||||
|
## O que cada correção comprou
|
||||||
|
|
||||||
|
**Fontes servidas em dobro — 88 KiB fora do caminho crítico.** Bunny entrega
|
||||||
|
cada peso de EB Garamond em woff2 e woff, e o plugin de fontes emitia uma regra
|
||||||
|
`@font-face` para cada, woff2 primeiro e woff depois. Duas regras com a mesma
|
||||||
|
família, peso, estilo e unicode-range fazem a **última** vencer: o navegador
|
||||||
|
renderizava a partir dos woff e descartava os woff2 pré-carregados.
|
||||||
|
|
||||||
|
A prova está no log de rede da home antes da correção: 3 woff em prioridade
|
||||||
|
`VeryHigh` (88 KiB) — a prioridade mais alta da página, à frente do elemento de
|
||||||
|
LCP — somados a 3 woff2 em `High` (74 KiB) que só foram baixados porque estavam
|
||||||
|
em `<link rel="preload">`. 162 KiB de tráfego de fonte para 74 KiB de fonte útil.
|
||||||
|
|
||||||
|
woff2 é suportado por todo navegador que este site atende desde 2016, então as
|
||||||
|
regras woff não eram fallback e sim peso morto. O plugin `amare:fonts-woff2-only`
|
||||||
|
em `vite.config.js` remove as regras do CSS e do manifest e tira os arquivos do
|
||||||
|
bundle. É o que derruba o FCP de 1,51 s para 0,91 s em todas as páginas.
|
||||||
|
|
||||||
|
**Ativos de marca reencodados — 102 KiB fora do caminho crítico.** O logotipo
|
||||||
|
era servido a 512 px de largura para renderizar em 48 px (lockup, cabeçalho e
|
||||||
|
rodapé) e 32 px (mark, home): 84 KiB + 42 KiB com `loading="eager"` em todas as
|
||||||
|
páginas. Reencodados a 3× do maior render — lockup 149×144 (14 KiB) e mark
|
||||||
|
191×96 (10 KiB) —, mantendo os mesmos nomes de arquivo para não invalidar cache.
|
||||||
|
As variantes `on-dark` foram reencodadas junto por consistência; nenhuma view as
|
||||||
|
usa hoje.
|
||||||
|
|
||||||
|
Isto absorve MAN-122: os 16 baselines visuais foram regenerados no runner Linux
|
||||||
|
(`scripts/test/visual-update-ci.sh`), e o diff é imperceptível a 2× de zoom —
|
||||||
|
mesma forma, mesma cor, só menos bytes.
|
||||||
|
|
||||||
|
Os arquivos versionados aqui são: `2026-08-10-local-antes.md` (commit `2e43fde`),
|
||||||
|
`2026-08-10-local-etapa-fontes-e-marca.md` (passada intermediária) e
|
||||||
|
`2026-08-10-local-depois.md` (estado final).
|
||||||
|
|
||||||
|
**Variantes WebP nas imagens de conteúdo.** Com fontes e marca resolvidas, o
|
||||||
|
elemento de LCP de toda página no mobile era a imagem do hero, e o Load Delay de
|
||||||
|
1,88 s era contenção de banda pura: 143 KiB de JPEG q82 a 960 px, com as três
|
||||||
|
capas do portfólio somando outros 348 KiB. `ResponsiveImage::generate()` passa a
|
||||||
|
escrever uma variante `.webp` ao lado de cada variante no formato original, e
|
||||||
|
`x-media.image` a oferece num `<source type="image/webp">`. O `<img>` continua
|
||||||
|
apontando para o formato original, então nada quebra em quem não decodifica webp,
|
||||||
|
e mídia antiga sem irmãos webp renderiza `<img>` puro como antes.
|
||||||
|
|
||||||
|
**`sizes` que descreve a realidade.** Nenhuma imagem do site ocupa a viewport
|
||||||
|
inteira: todas ficam dentro de `container-amare`, que reserva 1,5rem de padding
|
||||||
|
de cada lado. Declarar `100vw` fazia uma viewport de 412 px em DPR 1,75 pedir
|
||||||
|
721 px e pular para a variante de 960 para desenhar uma caixa de 637 px — errar
|
||||||
|
por um pixel custava um terço a mais de bytes. Com `calc(100vw - 3rem)` a home
|
||||||
|
passa a usar a variante de 720 (**74 KiB**, contra 143 KiB no início).
|
||||||
|
|
||||||
|
Foi também por isso que 720 entrou em `ResponsiveImage::WIDTHS`: sem ela o salto
|
||||||
|
de 480 para 960 é grande demais para a viewport mobile mais comum.
|
||||||
|
|
||||||
|
Decomposição final do LCP da home no mobile:
|
||||||
|
|
||||||
|
| fase | tempo |
|
||||||
|
|---|---|
|
||||||
|
| TTFB | 0,45 s |
|
||||||
|
| Load Delay | 1,24 s |
|
||||||
|
| Load Time | 0,10 s |
|
||||||
|
| Render Delay | 0,69 s |
|
||||||
|
|
||||||
|
## O ganho de imagem só aparece depois do deploy regenerar as variantes
|
||||||
|
|
||||||
|
Toda a medição acima usou `FILESYSTEM_DISK=local`. Em staging e produção o disco
|
||||||
|
é `r2`, e lá as variantes `.webp` e a largura 720 **ainda não existem** para a
|
||||||
|
mídia já publicada. Até `media:generate-variants` rodar, `availableWebpVariants()`
|
||||||
|
volta vazio, o `<source>` é omitido e o `srcset` do formato original perde a
|
||||||
|
entrada de 720w — ou seja, nenhum dos dois ganhos de imagem aparece.
|
||||||
|
|
||||||
|
O serviço `migrate` do `docker-compose.deploy.yml` roda
|
||||||
|
`php artisan media:generate-variants` sem condição a cada deploy, depois de
|
||||||
|
`migrate` e do seed, e o comando só pula um caminho quando o arquivo original não
|
||||||
|
existe (`MediaGenerateVariantsCommand`) — regenera mesmo quando já há variantes.
|
||||||
|
Então o primeiro deploy desta branch produz as variantes novas por conta própria.
|
||||||
|
|
||||||
|
Enquanto isso não acontece, há um custo sem contrapartida: `x-media.image` faz
|
||||||
|
agora **8 chamadas `exists()` por imagem** (4 larguras × 2 formatos) contra o
|
||||||
|
object store, no lugar de 3. Na home são ~32 round trips remotos por request em
|
||||||
|
vez de ~12. Isso agrava a hipótese não validada abaixo em vez de melhorá-la, e é
|
||||||
|
mais um motivo para cachear esses metadados.
|
||||||
|
|
||||||
|
## Se a home precisar de mais folga
|
||||||
|
|
||||||
|
O Load Delay de 1,24 s ainda é contenção: as três capas do portfólio somam
|
||||||
|
155 KiB e, embora sejam `loading="lazy"` e prioridade `Low`, o navegador as busca
|
||||||
|
porque entram no limiar de lazy loading da viewport emulada. Os levers restantes,
|
||||||
|
em ordem de custo:
|
||||||
|
|
||||||
|
1. Baixar a qualidade webp de 80 para 75 (medido: 109 → 92 KiB na imagem do
|
||||||
|
hero a 960 px). Barato, mas mexe na qualidade de imagem de uma marca cujo
|
||||||
|
posicionamento é acabamento editorial — decisão de produto, não de engenharia.
|
||||||
|
2. Reduzir o Render Delay de 0,69 s, que agora é a segunda maior fatia e é
|
||||||
|
trabalho de main thread, não de rede.
|
||||||
|
3. FrankenPHP worker mode para o TTFB de 0,45 s. Tem gatilho objetivo em
|
||||||
|
SPEC §22 e não deve ser puxado antes dele.
|
||||||
|
|
||||||
|
## Cobertura que este trabalho não tem
|
||||||
|
|
||||||
|
Os testes de regressão visual nunca exercitam `srcset` nem `<picture>`: nem
|
||||||
|
`ContentSeeder` nem `VisualContentSeeder` geram variantes, e `media:generate-variants`
|
||||||
|
não roda no runner visual. Naquele ambiente `availableVariants()` volta vazio e o
|
||||||
|
componente renderiza `<img>` puro — foi por isso que os 16 baselines não mudaram
|
||||||
|
com a introdução do `<picture>`. A cobertura do caminho com variantes fica nos
|
||||||
|
testes de feature (`MediaImageComponentTest`), não nos baselines.
|
||||||
|
|
||||||
|
## Staging
|
||||||
|
|
||||||
|
Não medido. A origem de staging responde `303` para
|
||||||
|
`blocked.teams.cloudflare.com` a partir da rede corporativa da Creditas
|
||||||
|
("O conteúdo deste site viola a Política de Segurança da Informação"), inclusive
|
||||||
|
em `/up`. O preflight de HTTP 200 do script barra a execução antes de gastar
|
||||||
|
minutos auditando páginas de bloqueio.
|
||||||
|
|
||||||
|
Duas hipóteses seguem **não validadas** porque só existem com
|
||||||
|
`FILESYSTEM_DISK=r2`, que é configuração de staging e produção:
|
||||||
|
|
||||||
|
- `x-media.image` chama `ResponsiveImage::availableVariants()`,
|
||||||
|
`availableWebpVariants()` (8 `exists()` somados) e `ResponsiveImage::dimensions()`
|
||||||
|
(que baixa o arquivo inteiro) a cada render, sem cache. No disco `local` são
|
||||||
|
leituras de filesystem; no `r2` são ~9 round trips remotos por imagem no lado
|
||||||
|
servidor, ~36 na home. Teste discriminante: comparar o TTFB de `/contato`
|
||||||
|
(zero imagens) com o de `/portfolio` (N imagens). Se o TTFB escalar com a
|
||||||
|
contagem de imagens, está confirmado. **Este é o item mais urgente da lista**,
|
||||||
|
porque as variantes webp multiplicaram o número de chamadas.
|
||||||
|
- A mídia vem de `R2_URL`, uma origem cross-origin, e não há `preconnect` no
|
||||||
|
`<head>` — DNS e TLS entram antes do LCP.
|
||||||
|
|
||||||
|
Para medir: rodar `TARGET=staging BASE_URL=<origem> bash scripts/perf/lighthouse.sh`
|
||||||
|
de uma rede sem o filtro corporativo.
|
||||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 9.8 KiB |
@@ -31,9 +31,14 @@
|
|||||||
// dimensions, so it gets no attributes rather than wrong ones. The CSS
|
// dimensions, so it gets no attributes rather than wrong ones. The CSS
|
||||||
// classes still govern the rendered size in both cases — width/height only
|
// classes still govern the rendered size in both cases — width/height only
|
||||||
// give the browser the aspect ratio to reserve.
|
// give the browser the aspect ratio to reserve.
|
||||||
|
//
|
||||||
|
// The webp assets are encoded at 3x the largest rendered size (lockup at
|
||||||
|
// h-12 = 48 px, mark at h-8 = 32 px), which is why these are 149x144 and
|
||||||
|
// 191x96 rather than the 512-wide originals kept as png fallbacks. See
|
||||||
|
// tests/Feature/PublicSite/BrandAssetBudgetTest.php.
|
||||||
$intrinsic = $usesUploadedLogo
|
$intrinsic = $usesUploadedLogo
|
||||||
? []
|
? []
|
||||||
: ($mark ? ['width' => 512, 'height' => 257] : ['width' => 512, 'height' => 495]);
|
: ($mark ? ['width' => 191, 'height' => 96] : ['width' => 149, 'height' => 144]);
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
:alt="$settings->default_og_image_alt ?: $settings->brand_name"
|
:alt="$settings->default_og_image_alt ?: $settings->brand_name"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
fetchpriority="high"
|
fetchpriority="high"
|
||||||
sizes="(max-width: 768px) 100vw, 40vw"
|
sizes="(max-width: 768px) calc(100vw - 3rem), 40vw"
|
||||||
class="img-editorial h-full w-full object-cover"
|
class="img-editorial h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<x-media.image
|
<x-media.image
|
||||||
:path="$case->cover_image_path"
|
:path="$case->cover_image_path"
|
||||||
:alt="$case->cover_image_alt ?: $case->title"
|
:alt="$case->cover_image_alt ?: $case->title"
|
||||||
sizes="(max-width: 768px) 100vw, 50vw"
|
sizes="(max-width: 768px) calc(100vw - 3rem), 50vw"
|
||||||
class="img-editorial aspect-[4/3] w-full object-cover"
|
class="img-editorial aspect-[4/3] w-full object-cover"
|
||||||
/>
|
/>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
@props([
|
@props([
|
||||||
'path',
|
'path',
|
||||||
'alt',
|
'alt',
|
||||||
'sizes' => '(max-width: 768px) 100vw, 960px',
|
// No image on the site spans the full viewport: every one of them sits inside
|
||||||
|
// `container-amare`, which reserves 1.5rem of padding on each side. Claiming
|
||||||
|
// 100vw made a 412 px viewport at DPR 1.75 ask for 721 px and jump to the
|
||||||
|
// 960 variant for a box it draws at 637 px.
|
||||||
|
'sizes' => '(max-width: 768px) calc(100vw - 3rem), 960px',
|
||||||
'loading' => 'lazy',
|
'loading' => 'lazy',
|
||||||
'fetchpriority' => null,
|
'fetchpriority' => null,
|
||||||
'width' => null,
|
'width' => null,
|
||||||
@@ -18,15 +22,25 @@
|
|||||||
$diskName = $disk ?? PublicImageUploadRules::disk();
|
$diskName = $disk ?? PublicImageUploadRules::disk();
|
||||||
$filesystem = Storage::disk($diskName);
|
$filesystem = Storage::disk($diskName);
|
||||||
$src = $filesystem->url($path);
|
$src = $filesystem->url($path);
|
||||||
$variants = ResponsiveImage::availableVariants($path, $diskName);
|
|
||||||
$srcset = collect($variants)
|
$toSrcset = fn (array $variants): string => collect($variants)
|
||||||
->map(fn (array $variant): string => $filesystem->url($variant['path']).' '.$variant['width'].'w')
|
->map(fn (array $variant): string => $filesystem->url($variant['path']).' '.$variant['width'].'w')
|
||||||
->implode(', ');
|
->implode(', ');
|
||||||
|
|
||||||
|
$variants = ResponsiveImage::availableVariants($path, $diskName);
|
||||||
|
$srcset = $toSrcset($variants);
|
||||||
|
|
||||||
if ($srcset === '' && $filesystem->exists($path)) {
|
if ($srcset === '' && $filesystem->exists($path)) {
|
||||||
$srcset = null;
|
$srcset = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Offered ahead of the original format because webp carries the same picture
|
||||||
|
// for roughly a third of the bytes, and the MAN-109 audit found the hero
|
||||||
|
// image to be the LCP element on every page at mobile widths. Media uploaded
|
||||||
|
// before `media:generate-variants` learned to emit webp has no siblings, so
|
||||||
|
// the <source> is skipped rather than pointed at nothing.
|
||||||
|
$webpSrcset = $toSrcset(ResponsiveImage::availableWebpVariants($path, $diskName));
|
||||||
|
|
||||||
$dimensions = ($width === null || $height === null)
|
$dimensions = ($width === null || $height === null)
|
||||||
? ResponsiveImage::dimensions($path, $diskName)
|
? ResponsiveImage::dimensions($path, $diskName)
|
||||||
: null;
|
: null;
|
||||||
@@ -36,6 +50,13 @@
|
|||||||
$loadingValue = $loading;
|
$loadingValue = $loading;
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
|
{{-- `display: contents` keeps <picture> out of the layout: the callers style the
|
||||||
|
<img> with classes like `h-full w-full object-cover` that resolve against the
|
||||||
|
grid or flex parent, and an inline wrapper would break that. --}}
|
||||||
|
@if ($webpSrcset !== '')
|
||||||
|
<picture class="contents">
|
||||||
|
<source type="image/webp" srcset="{{ $webpSrcset }}" sizes="{{ $sizes }}">
|
||||||
|
@endif
|
||||||
<img
|
<img
|
||||||
src="{{ $src }}"
|
src="{{ $src }}"
|
||||||
@if ($srcset) srcset="{{ $srcset }}" sizes="{{ $sizes }}" @endif
|
@if ($srcset) srcset="{{ $srcset }}" sizes="{{ $sizes }}" @endif
|
||||||
@@ -47,3 +68,6 @@
|
|||||||
@if ($class) class="{{ $class }}" @endif
|
@if ($class) class="{{ $class }}" @endif
|
||||||
{{ $attributes->except(['path', 'alt', 'sizes', 'loading', 'width', 'height', 'disk', 'class']) }}
|
{{ $attributes->except(['path', 'alt', 'sizes', 'loading', 'width', 'height', 'disk', 'class']) }}
|
||||||
>
|
>
|
||||||
|
@if ($webpSrcset !== '')
|
||||||
|
</picture>
|
||||||
|
@endif
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
:path="$siteSettings->about_image_path"
|
:path="$siteSettings->about_image_path"
|
||||||
:alt="$siteSettings->about_image_alt ?: $siteSettings->brand_name"
|
:alt="$siteSettings->about_image_alt ?: $siteSettings->brand_name"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
sizes="(max-width: 768px) 100vw, 50vw"
|
sizes="(max-width: 768px) calc(100vw - 3rem), 50vw"
|
||||||
class="img-editorial aspect-[4/3] w-full object-cover"
|
class="img-editorial aspect-[4/3] w-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<x-media.image
|
<x-media.image
|
||||||
:path="$case->cover_image_path"
|
:path="$case->cover_image_path"
|
||||||
:alt="$case->cover_image_alt ?: $case->title"
|
:alt="$case->cover_image_alt ?: $case->title"
|
||||||
sizes="(max-width: 768px) 100vw, 50vw"
|
sizes="(max-width: 768px) calc(100vw - 3rem), 50vw"
|
||||||
class="img-editorial aspect-[4/3] w-full object-cover transition-transform duration-(--amare-duration-slow) ease-(--amare-ease-standard) motion-safe:hover:scale-[1.02]"
|
class="img-editorial aspect-[4/3] w-full object-cover transition-transform duration-(--amare-duration-slow) ease-(--amare-ease-standard) motion-safe:hover:scale-[1.02]"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
:path="$case->cover_image_path"
|
:path="$case->cover_image_path"
|
||||||
:alt="$case->cover_image_alt ?: $case->title"
|
:alt="$case->cover_image_alt ?: $case->title"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
sizes="(max-width: 1024px) 100vw, 1120px"
|
sizes="(max-width: 1024px) calc(100vw - 3rem), 1120px"
|
||||||
class="img-editorial aspect-[16/9] w-full object-cover"
|
class="img-editorial aspect-[16/9] w-full object-cover"
|
||||||
data-motion-beat="media"
|
data-motion-beat="media"
|
||||||
/>
|
/>
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
<x-media.image
|
<x-media.image
|
||||||
:path="$image->path"
|
:path="$image->path"
|
||||||
:alt="$image->alt_text"
|
:alt="$image->alt_text"
|
||||||
sizes="(max-width: 768px) 100vw, 50vw"
|
sizes="(max-width: 768px) calc(100vw - 3rem), 50vw"
|
||||||
class="img-editorial aspect-[4/3] w-full object-cover"
|
class="img-editorial aspect-[4/3] w-full object-cover"
|
||||||
/>
|
/>
|
||||||
@if (filled($image->caption))
|
@if (filled($image->caption))
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<x-media.image
|
<x-media.image
|
||||||
:path="$service->cover_image_path"
|
:path="$service->cover_image_path"
|
||||||
:alt="$service->cover_image_alt ?: $service->title"
|
:alt="$service->cover_image_alt ?: $service->title"
|
||||||
sizes="(max-width: 768px) 100vw, 40vw"
|
sizes="(max-width: 768px) calc(100vw - 3rem), 40vw"
|
||||||
class="img-editorial aspect-[16/10] w-full object-cover"
|
class="img-editorial aspect-[16/10] w-full object-cover"
|
||||||
/>
|
/>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -8,6 +8,18 @@
|
|||||||
# Usage:
|
# Usage:
|
||||||
# scripts/perf/lighthouse.sh [output-dir]
|
# scripts/perf/lighthouse.sh [output-dir]
|
||||||
#
|
#
|
||||||
|
# Environment:
|
||||||
|
# BASE_URL origin to audit (default http://127.0.0.1:8000)
|
||||||
|
# TARGET label for the output subtree (default local)
|
||||||
|
# RUNS runs per page and preset (default 3)
|
||||||
|
# PRESETS space-separated preset list (default "mobile desktop")
|
||||||
|
#
|
||||||
|
# Lighthouse LCP moves by a few tenths of a second between runs on the same
|
||||||
|
# build, so a single run cannot support a before/after comparison. Every page
|
||||||
|
# is audited RUNS times per preset and the run holding the median LCP is the
|
||||||
|
# one reported — averaging across runs would describe a page that never
|
||||||
|
# existed.
|
||||||
|
#
|
||||||
# Expects a site already answering on $BASE_URL. To raise one from scratch:
|
# Expects a site already answering on $BASE_URL. To raise one from scratch:
|
||||||
#
|
#
|
||||||
# docker build -t amare-app:ci .
|
# docker build -t amare-app:ci .
|
||||||
@@ -20,7 +32,11 @@
|
|||||||
# -v "$(pwd)/storage/app/public:/app/storage/app/public" \
|
# -v "$(pwd)/storage/app/public:/app/storage/app/public" \
|
||||||
# amare-app:ci
|
# amare-app:ci
|
||||||
#
|
#
|
||||||
# Seed content and generate the responsive variants first. Skipping
|
# Seed content and generate the responsive variants first, and run both from
|
||||||
|
# the host rather than inside the container. ContentSeeder guards itself with
|
||||||
|
# an allow-list of local/staging/testing (database/seeders/ContentSeeder.php),
|
||||||
|
# so under the container's APP_ENV=production it is a deliberate no-op and
|
||||||
|
# Lighthouse would end up measuring empty pages. Skipping
|
||||||
# `media:generate-variants` inflates LCP by roughly 2.5 s on the home page,
|
# `media:generate-variants` inflates LCP by roughly 2.5 s on the home page,
|
||||||
# because the originals are served at full size:
|
# because the originals are served at full size:
|
||||||
#
|
#
|
||||||
@@ -30,7 +46,11 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BASE_URL="${BASE_URL:-http://127.0.0.1:8000}"
|
BASE_URL="${BASE_URL:-http://127.0.0.1:8000}"
|
||||||
OUT_DIR="${1:-storage/app/lighthouse}"
|
BASE_URL="${BASE_URL%/}"
|
||||||
|
TARGET="${TARGET:-local}"
|
||||||
|
RUNS="${RUNS:-3}"
|
||||||
|
PRESETS="${PRESETS:-mobile desktop}"
|
||||||
|
OUT_DIR="${1:-storage/app/lighthouse}/${TARGET}"
|
||||||
|
|
||||||
# Lighthouse needs a Chrome binary. Playwright's is already on disk after
|
# Lighthouse needs a Chrome binary. Playwright's is already on disk after
|
||||||
# `npx playwright install chromium`; fall back to a system Chrome.
|
# `npx playwright install chromium`; fall back to a system Chrome.
|
||||||
@@ -45,23 +65,73 @@ fi
|
|||||||
|
|
||||||
mkdir -p "${OUT_DIR}"
|
mkdir -p "${OUT_DIR}"
|
||||||
|
|
||||||
PAGES=("/:home" "/servicos:servicos" "/portfolio:portfolio" "/sobre:sobre" "/contato:contato")
|
PAGES=(
|
||||||
|
"/:home"
|
||||||
|
"/servicos:servicos"
|
||||||
|
"/portfolio:portfolio"
|
||||||
|
"/portfolio/casamento-ana-lucas:portfolio-detalhe"
|
||||||
|
"/sobre:sobre"
|
||||||
|
"/contato:contato"
|
||||||
|
)
|
||||||
|
|
||||||
|
# A route that answers 404 still produces a Lighthouse report, and the error
|
||||||
|
# page is light enough to score well — the heaviest route on the site would be
|
||||||
|
# reported as excellent and nobody would notice. Refuse to measure anything
|
||||||
|
# that is not a 200 before spending minutes on the audit.
|
||||||
|
echo "preflight against ${BASE_URL}"
|
||||||
|
|
||||||
for entry in "${PAGES[@]}"; do
|
for entry in "${PAGES[@]}"; do
|
||||||
|
path="${entry%%:*}"
|
||||||
|
code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 "${BASE_URL}${path}" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ "${code}" != "200" ]]; then
|
||||||
|
echo "preflight failed: ${BASE_URL}${path} answered HTTP ${code:-<none>}, expected 200" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " ok ${path}"
|
||||||
|
done
|
||||||
|
|
||||||
|
for preset in ${PRESETS}; do
|
||||||
|
# The mobile preset is Lighthouse's default and rejects an explicit
|
||||||
|
# --preset flag, so only desktop is passed through.
|
||||||
|
preset_flags=()
|
||||||
|
if [[ "${preset}" != "mobile" ]]; then
|
||||||
|
preset_flags+=("--preset=${preset}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
for entry in "${PAGES[@]}"; do
|
||||||
path="${entry%%:*}"
|
path="${entry%%:*}"
|
||||||
name="${entry##*:}"
|
name="${entry##*:}"
|
||||||
|
|
||||||
echo "auditing ${name} (${BASE_URL}${path})"
|
for run in $(seq 1 "${RUNS}"); do
|
||||||
|
echo "auditing ${name} ${preset} run ${run}/${RUNS} (${BASE_URL}${path})"
|
||||||
|
|
||||||
# Default preset: simulated mobile throttling, 150 ms RTT, ~1.6 Mbps,
|
# Default preset: simulated mobile throttling, 150 ms RTT,
|
||||||
# 4x CPU slowdown. Add --preset=desktop for the desktop numbers.
|
# ~1.6 Mbps, 4x CPU slowdown.
|
||||||
|
# ${array[@]+...} keeps `set -u` from treating an empty array as
|
||||||
|
# unbound, which bash 3.2 (the macOS system bash) still does.
|
||||||
npx --yes lighthouse@12 "${BASE_URL}${path}" \
|
npx --yes lighthouse@12 "${BASE_URL}${path}" \
|
||||||
--quiet \
|
--quiet \
|
||||||
|
${preset_flags[@]+"${preset_flags[@]}"} \
|
||||||
--output=json --output=html \
|
--output=json --output=html \
|
||||||
--output-path="${OUT_DIR}/${name}-mobile" \
|
--output-path="${OUT_DIR}/${name}-${preset}-run${run}" \
|
||||||
--chrome-flags="--headless=new --no-sandbox"
|
--chrome-flags="--headless=new --no-sandbox"
|
||||||
|
done
|
||||||
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "reports written to ${OUT_DIR}"
|
echo "reports written to ${OUT_DIR}"
|
||||||
|
|
||||||
|
# The seeder is recorded because it decides the byte weight of every hero
|
||||||
|
# image: a before/after comparison across different fixtures measures nothing.
|
||||||
|
node scripts/perf/summarize-lighthouse.mjs "${OUT_DIR}" \
|
||||||
|
--target="${TARGET}" \
|
||||||
|
--base-url="${BASE_URL}" \
|
||||||
|
--commit="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)$(git diff --quiet HEAD 2>/dev/null || echo '+alterações não commitadas')" \
|
||||||
|
--seeder="${SEEDER:-ContentSeeder}" \
|
||||||
|
> "${OUT_DIR}/summary.md"
|
||||||
|
|
||||||
|
echo "summary written to ${OUT_DIR}/summary.md"
|
||||||
echo "SPEC.md §6.6 targets: LCP <= 2.5s, CLS <= 0.1, INP <= 200ms, 0 console errors"
|
echo "SPEC.md §6.6 targets: LCP <= 2.5s, CLS <= 0.1, INP <= 200ms, 0 console errors"
|
||||||
|
|||||||
249
scripts/perf/summarize-lighthouse.mjs
Executable file
@@ -0,0 +1,249 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
//
|
||||||
|
// Turns a directory of Lighthouse JSON reports into one reviewable markdown
|
||||||
|
// summary. Written because the numbers behind MAN-109 previously survived only
|
||||||
|
// as a hand-typed table in a Linear comment: `storage/app/lighthouse` is
|
||||||
|
// gitignored, so there was nothing to diff a later run against.
|
||||||
|
//
|
||||||
|
// For each page and preset the run holding the median LCP is the one reported.
|
||||||
|
// Metrics are never averaged across runs — every figure in the table comes
|
||||||
|
// from the same single navigation, so the LCP phase breakdown adds up.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// node scripts/perf/summarize-lighthouse.mjs <report-dir> [--target=local]
|
||||||
|
// [--base-url=...] [--commit=...] [--seeder=...]
|
||||||
|
|
||||||
|
import { readdirSync, readFileSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
const [dir, ...rest] = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (!dir) {
|
||||||
|
console.error('usage: summarize-lighthouse.mjs <report-dir> [--target=...]');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const flags = new Map(
|
||||||
|
rest
|
||||||
|
.filter((argument) => argument.startsWith('--'))
|
||||||
|
.map((argument) => {
|
||||||
|
const [key, ...value] = argument.replace(/^--/, '').split('=');
|
||||||
|
|
||||||
|
return [key, value.join('=')];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const ms = (value) => (typeof value === 'number' ? `${(value / 1000).toFixed(2)} s` : '—');
|
||||||
|
const kib = (value) => (typeof value === 'number' ? `${Math.round(value / 1024)} KiB` : '—');
|
||||||
|
const score = (value) => (typeof value === 'number' ? Math.round(value * 100) : '—');
|
||||||
|
const numeric = (audit) => (typeof audit?.numericValue === 'number' ? audit.numericValue : null);
|
||||||
|
|
||||||
|
/** Every nested details table in an audit, flattened. */
|
||||||
|
function nestedItems(audit) {
|
||||||
|
const items = audit?.details?.items ?? [];
|
||||||
|
|
||||||
|
return items.flatMap((item) => (Array.isArray(item?.items) ? item.items : [item]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function lcpElement(lhr) {
|
||||||
|
const item = nestedItems(lhr.audits?.['largest-contentful-paint-element']).find((entry) => entry?.node);
|
||||||
|
const snippet = item?.node?.snippet ?? item?.node?.selector ?? null;
|
||||||
|
|
||||||
|
return snippet ? snippet.replace(/\s+/g, ' ').slice(0, 160) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lcpPhases(lhr) {
|
||||||
|
return nestedItems(lhr.audits?.['largest-contentful-paint-element'])
|
||||||
|
.filter((entry) => typeof entry?.phase === 'string')
|
||||||
|
.map((entry) => ({ phase: entry.phase, timing: entry.timing ?? null }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Requests that finished before LCP, heaviest first — the contention evidence. */
|
||||||
|
function requestsBeforeLcp(lhr, lcp) {
|
||||||
|
const requests = lhr.audits?.['network-requests']?.details?.items ?? [];
|
||||||
|
|
||||||
|
return requests
|
||||||
|
.filter((request) => typeof request.networkEndTime === 'number' && (lcp === null || request.networkEndTime <= lcp + 50))
|
||||||
|
.filter((request) => (request.transferSize ?? 0) > 1024)
|
||||||
|
.sort((a, b) => (b.transferSize ?? 0) - (a.transferSize ?? 0))
|
||||||
|
.slice(0, 8)
|
||||||
|
.map((request) => ({
|
||||||
|
url: String(request.url ?? '').replace(/^https?:\/\/[^/]+/, ''),
|
||||||
|
type: request.resourceType ?? '—',
|
||||||
|
transferSize: request.transferSize ?? null,
|
||||||
|
endTime: request.networkEndTime ?? null,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Failing LH12 insights, with whatever savings estimate they carry. */
|
||||||
|
function insights(lhr) {
|
||||||
|
return Object.entries(lhr.audits ?? {})
|
||||||
|
.filter(([id, audit]) => id.endsWith('-insight') && typeof audit.score === 'number' && audit.score < 1)
|
||||||
|
.map(([id, audit]) => ({
|
||||||
|
id,
|
||||||
|
title: audit.title ?? id,
|
||||||
|
lcpSavings: audit.metricSavings?.LCP ?? null,
|
||||||
|
byteSavings: audit.details?.overallSavingsBytes ?? null,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => (b.lcpSavings ?? 0) - (a.lcpSavings ?? 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
const reports = new Map();
|
||||||
|
|
||||||
|
for (const file of readdirSync(dir).filter((name) => name.endsWith('.report.json')).sort()) {
|
||||||
|
const match = /^(.+)-(mobile|desktop)-run(\d+)\.report\.json$/.exec(file);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, page, preset] = match;
|
||||||
|
const key = `${page}::${preset}`;
|
||||||
|
const lhr = JSON.parse(readFileSync(join(dir, file), 'utf8'));
|
||||||
|
|
||||||
|
if (!reports.has(key)) {
|
||||||
|
reports.set(key, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
reports.get(key).push({ file, lhr });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reports.size === 0) {
|
||||||
|
console.error(`no Lighthouse JSON reports found in ${dir}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The run whose LCP is the median — reported whole, never blended. */
|
||||||
|
function medianRun(runs) {
|
||||||
|
const sorted = [...runs].sort(
|
||||||
|
(a, b) => (numeric(a.lhr.audits?.['largest-contentful-paint']) ?? Infinity)
|
||||||
|
- (numeric(b.lhr.audits?.['largest-contentful-paint']) ?? Infinity),
|
||||||
|
);
|
||||||
|
|
||||||
|
return sorted[Math.floor((sorted.length - 1) / 2)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = [];
|
||||||
|
const details = [];
|
||||||
|
|
||||||
|
for (const [key, runs] of [...reports.entries()].sort()) {
|
||||||
|
const [page, preset] = key.split('::');
|
||||||
|
const chosen = medianRun(runs);
|
||||||
|
const { lhr } = chosen;
|
||||||
|
const lcp = numeric(lhr.audits?.['largest-contentful-paint']);
|
||||||
|
const spread = runs
|
||||||
|
.map((run) => numeric(run.lhr.audits?.['largest-contentful-paint']))
|
||||||
|
.filter((value) => value !== null)
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
rows.push({
|
||||||
|
page,
|
||||||
|
preset,
|
||||||
|
performance: score(lhr.categories?.performance?.score),
|
||||||
|
accessibility: score(lhr.categories?.accessibility?.score),
|
||||||
|
bestPractices: score(lhr.categories?.['best-practices']?.score),
|
||||||
|
seo: score(lhr.categories?.seo?.score),
|
||||||
|
lcp,
|
||||||
|
cls: numeric(lhr.audits?.['cumulative-layout-shift']),
|
||||||
|
tbt: numeric(lhr.audits?.['total-blocking-time']),
|
||||||
|
fcp: numeric(lhr.audits?.['first-contentful-paint']),
|
||||||
|
ttfb: numeric(lhr.audits?.['server-response-time']),
|
||||||
|
runs: runs.length,
|
||||||
|
spread: spread.length > 1 ? [spread[0], spread[spread.length - 1]] : null,
|
||||||
|
file: chosen.file,
|
||||||
|
});
|
||||||
|
|
||||||
|
details.push({
|
||||||
|
page,
|
||||||
|
preset,
|
||||||
|
element: lcpElement(lhr),
|
||||||
|
phases: lcpPhases(lhr),
|
||||||
|
requests: requestsBeforeLcp(lhr, lcp),
|
||||||
|
insights: insights(lhr),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const first = reports.values().next().value[0].lhr;
|
||||||
|
|
||||||
|
const out = [];
|
||||||
|
|
||||||
|
out.push(`# Lighthouse — ${flags.get('target') ?? 'local'}`);
|
||||||
|
out.push('');
|
||||||
|
out.push(`- Origem: \`${flags.get('base-url') ?? '—'}\``);
|
||||||
|
out.push(`- Lighthouse: ${first.lighthouseVersion ?? '—'}`);
|
||||||
|
// Which Chrome ran the audit is part of the measurement: the stable system
|
||||||
|
// Chrome and Playwright's Chromium do not produce interchangeable numbers.
|
||||||
|
out.push(`- Navegador: \`${first.environment?.hostUserAgent ?? '—'}\``);
|
||||||
|
out.push(`- Coletado em: ${first.fetchTime ?? '—'}`);
|
||||||
|
out.push(`- Execuções por página/preset: ${rows[0]?.runs ?? '—'} (reportada a de LCP mediano)`);
|
||||||
|
|
||||||
|
if (flags.get('commit')) {
|
||||||
|
out.push(`- Commit: \`${flags.get('commit')}\``);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags.get('seeder')) {
|
||||||
|
out.push(`- Seeder: \`${flags.get('seeder')}\``);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push('');
|
||||||
|
out.push('Metas SPEC §6.6: LCP ≤ 2,5 s · CLS ≤ 0,1 · INP ≤ 200 ms · zero erro de console.');
|
||||||
|
out.push('');
|
||||||
|
out.push('| página | preset | perf | a11y | BP | SEO | LCP | FCP | CLS | TBT | TTFB servidor | LCP min–max |');
|
||||||
|
out.push('|---|---|---|---|---|---|---|---|---|---|---|---|');
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const spread = row.spread ? `${ms(row.spread[0])} – ${ms(row.spread[1])}` : '—';
|
||||||
|
|
||||||
|
out.push(
|
||||||
|
`| ${row.page} | ${row.preset} | ${row.performance} | ${row.accessibility} | ${row.bestPractices} `
|
||||||
|
+ `| ${row.seo} | ${ms(row.lcp)} | ${ms(row.fcp)} | ${typeof row.cls === 'number' ? row.cls.toFixed(3) : '—'} `
|
||||||
|
+ `| ${typeof row.tbt === 'number' ? `${Math.round(row.tbt)} ms` : '—'} `
|
||||||
|
+ `| ${typeof row.ttfb === 'number' ? `${Math.round(row.ttfb)} ms` : '—'} | ${spread} |`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push('');
|
||||||
|
out.push('## Decomposição do LCP');
|
||||||
|
|
||||||
|
for (const detail of details) {
|
||||||
|
out.push('');
|
||||||
|
out.push(`### ${detail.page} — ${detail.preset}`);
|
||||||
|
out.push('');
|
||||||
|
out.push(`Elemento de LCP: ${detail.element ? `\`${detail.element}\`` : '—'}`);
|
||||||
|
|
||||||
|
if (detail.phases.length > 0) {
|
||||||
|
out.push('');
|
||||||
|
out.push('| fase | tempo |');
|
||||||
|
out.push('|---|---|');
|
||||||
|
|
||||||
|
for (const phase of detail.phases) {
|
||||||
|
out.push(`| ${phase.phase} | ${ms(phase.timing)} |`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detail.requests.length > 0) {
|
||||||
|
out.push('');
|
||||||
|
out.push('Requests concluídas até o LCP, mais pesadas primeiro:');
|
||||||
|
out.push('');
|
||||||
|
out.push('| recurso | tipo | transferido | fim |');
|
||||||
|
out.push('|---|---|---|---|');
|
||||||
|
|
||||||
|
for (const request of detail.requests) {
|
||||||
|
out.push(`| \`${request.url}\` | ${request.type} | ${kib(request.transferSize)} | ${ms(request.endTime)} |`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detail.insights.length > 0) {
|
||||||
|
out.push('');
|
||||||
|
out.push('| insight com falha | ganho estimado de LCP | bytes |');
|
||||||
|
out.push('|---|---|---|');
|
||||||
|
|
||||||
|
for (const insight of detail.insights) {
|
||||||
|
out.push(`| ${insight.title} | ${ms(insight.lcpSavings)} | ${kib(insight.byteSavings)} |`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push('');
|
||||||
|
|
||||||
|
console.log(out.join('\n'));
|
||||||
94
scripts/test/visual-update-ci.sh
Executable file
@@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Regenerates the visual regression baselines inside a Linux container that
|
||||||
|
# matches CI's rendering environment.
|
||||||
|
#
|
||||||
|
# `composer visual:update` run on macOS writes baselines CI will reject: the
|
||||||
|
# snapshots are pixels, and the Chromium build plus the font stack differ
|
||||||
|
# between the two systems. Pest Browser serves the application from an
|
||||||
|
# in-process Amp server, so no FrankenPHP container is involved — the only
|
||||||
|
# thing that has to match is the machine running the browser.
|
||||||
|
#
|
||||||
|
# SPEC.md §1.1 and openspec/specs/visual-regression/spec.md require the diff to
|
||||||
|
# be reviewed by a human before merge. This script updates baselines; it does
|
||||||
|
# not approve them.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/test/visual-update-ci.sh [--assert] [-- <extra pest args>]
|
||||||
|
#
|
||||||
|
# --assert run the suite in assertion mode instead of updating, to
|
||||||
|
# confirm the freshly written baselines actually pass
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# DB_HOST host reachable from inside the container (default host.docker.internal)
|
||||||
|
# DB_PORT PostgreSQL port on that host (default 5433)
|
||||||
|
# DB_DATABASE database for the run (default amare_test)
|
||||||
|
# IMAGE runner image tag (default amare-ci-runner:local)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MODE="update"
|
||||||
|
if [[ "${1:-}" == "--assert" ]]; then
|
||||||
|
MODE="assert"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--" ]]; then
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
IMAGE="${IMAGE:-amare-ci-runner:local}"
|
||||||
|
DB_HOST="${DB_HOST:-host.docker.internal}"
|
||||||
|
DB_PORT="${DB_PORT:-5433}"
|
||||||
|
DB_DATABASE="${DB_DATABASE:-amare_test}"
|
||||||
|
DB_USERNAME="${DB_USERNAME:-amare}"
|
||||||
|
DB_PASSWORD="${DB_PASSWORD:-secret}"
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
if ! docker image inspect "${IMAGE}" >/dev/null 2>&1; then
|
||||||
|
echo "building ${IMAGE}"
|
||||||
|
docker build -f "${REPO_ROOT}/docker/ci-runner.Dockerfile" -t "${IMAGE}" "${REPO_ROOT}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
PEST_FLAGS="--testsuite=Browser"
|
||||||
|
if [[ "${MODE}" == "update" ]]; then
|
||||||
|
PEST_FLAGS="${PEST_FLAGS} --update-snapshots"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# node_modules and the Playwright browser cache live in named volumes rather
|
||||||
|
# than in the bind mount: the host copy is built for macOS and its native
|
||||||
|
# binaries (rollup, esbuild, playwright) would not run here. Both volumes are
|
||||||
|
# reused across runs so only the first one pays the install.
|
||||||
|
docker run --rm \
|
||||||
|
-v "${REPO_ROOT}:/app" \
|
||||||
|
-v amare-ci-node-modules:/app/node_modules \
|
||||||
|
-v amare-ci-playwright:/opt/playwright-browsers \
|
||||||
|
--add-host=host.docker.internal:host-gateway \
|
||||||
|
-e DB_CONNECTION=pgsql \
|
||||||
|
-e DB_HOST="${DB_HOST}" \
|
||||||
|
-e DB_PORT="${DB_PORT}" \
|
||||||
|
-e DB_DATABASE="${DB_DATABASE}" \
|
||||||
|
-e DB_USERNAME="${DB_USERNAME}" \
|
||||||
|
-e DB_PASSWORD="${DB_PASSWORD}" \
|
||||||
|
-e PEST_FLAGS="${PEST_FLAGS}" \
|
||||||
|
-e PEST_EXTRA="$*" \
|
||||||
|
"${IMAGE}" \
|
||||||
|
bash -euo pipefail -c '
|
||||||
|
# composer install is skipped when vendor/ came in through the bind
|
||||||
|
# mount: the dependencies are pure PHP, so the host copy is valid here.
|
||||||
|
if [ ! -f vendor/autoload.php ]; then
|
||||||
|
composer install --no-interaction --prefer-dist
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x node_modules/.bin/playwright ]; then
|
||||||
|
npm ci
|
||||||
|
fi
|
||||||
|
|
||||||
|
npx playwright install chromium
|
||||||
|
npm run build
|
||||||
|
php artisan migrate --force
|
||||||
|
php artisan db:seed --class=VisualContentSeeder --force
|
||||||
|
php artisan storage:link --force
|
||||||
|
|
||||||
|
php artisan test ${PEST_FLAGS} ${PEST_EXTRA}
|
||||||
|
'
|
||||||
83
tests/Feature/PublicSite/BrandAssetBudgetTest.php
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Feature\PublicSite;
|
||||||
|
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The brand logo is eager-loaded into the header and footer of every public
|
||||||
|
* page, so its weight lands on the critical path of the whole site. It used to
|
||||||
|
* ship at 512 px wide — 82 KiB of lockup plus 41 KiB of mark to render at 48
|
||||||
|
* and 32 px — which the MAN-109 Lighthouse audit measured as the largest
|
||||||
|
* avoidable block ahead of the LCP element.
|
||||||
|
*
|
||||||
|
* A byte budget rather than a CI performance gate: SPEC §14.1 pins the five
|
||||||
|
* blocking jobs and §22 governs when new capability is added, but nothing stops
|
||||||
|
* a future asset swap from silently undoing this. The budget is deliberately
|
||||||
|
* loose — roughly double what the current encoding costs — so it catches a
|
||||||
|
* regression in kind (a full-size original dropped back in) and not normal
|
||||||
|
* re-encoding noise.
|
||||||
|
*/
|
||||||
|
class BrandAssetBudgetTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Max transfer size per shipped webp, in bytes.
|
||||||
|
*/
|
||||||
|
private const WEBP_BUDGET = 30 * 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intrinsic size the assets are encoded at: 3x the largest rendered box
|
||||||
|
* (lockup at h-12 = 48 px, mark at h-8 = 32 px). The brand.logo component
|
||||||
|
* declares these as width/height to reserve the layout box, so a re-encode
|
||||||
|
* at a different size has to update both or the reserved box lies.
|
||||||
|
*
|
||||||
|
* @var array<string, array{width: int, height: int}>
|
||||||
|
*/
|
||||||
|
private const INTRINSIC = [
|
||||||
|
'lockup-on-light' => ['width' => 149, 'height' => 144],
|
||||||
|
'lockup-on-dark' => ['width' => 149, 'height' => 144],
|
||||||
|
'mark-on-light' => ['width' => 191, 'height' => 96],
|
||||||
|
'mark-on-dark' => ['width' => 191, 'height' => 96],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function test_brand_webp_assets_stay_within_their_byte_budget(): void
|
||||||
|
{
|
||||||
|
foreach (array_keys(self::INTRINSIC) as $name) {
|
||||||
|
$path = public_path("brand/{$name}.webp");
|
||||||
|
|
||||||
|
$this->assertFileExists($path);
|
||||||
|
$this->assertLessThanOrEqual(
|
||||||
|
self::WEBP_BUDGET,
|
||||||
|
filesize($path),
|
||||||
|
"public/brand/{$name}.webp is over the critical-path budget of "
|
||||||
|
.(self::WEBP_BUDGET / 1024).' KiB. It is eager-loaded on every public page; '
|
||||||
|
.'re-encode it at the rendered size instead of raising the budget.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_brand_webp_assets_match_the_dimensions_the_component_declares(): void
|
||||||
|
{
|
||||||
|
$component = (string) file_get_contents(
|
||||||
|
resource_path('views/components/brand/logo.blade.php')
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (self::INTRINSIC as $name => $expected) {
|
||||||
|
$size = getimagesize(public_path("brand/{$name}.webp"));
|
||||||
|
|
||||||
|
$this->assertIsArray($size, "public/brand/{$name}.webp is not a readable image.");
|
||||||
|
$this->assertSame($expected['width'], $size[0], "public/brand/{$name}.webp width changed.");
|
||||||
|
$this->assertSame($expected['height'], $size[1], "public/brand/{$name}.webp height changed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ([['width' => 191, 'height' => 96], ['width' => 149, 'height' => 144]] as $pair) {
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
"'width' => {$pair['width']}, 'height' => {$pair['height']}",
|
||||||
|
$component,
|
||||||
|
'brand/logo.blade.php must reserve the box using the real intrinsic size of the shipped asset.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
tests/Feature/PublicSite/FontDeliveryTest.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Feature\PublicSite;
|
||||||
|
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bunny serves every EB Garamond weight as both woff2 and woff, and the fonts
|
||||||
|
* plugin emitted an `@font-face` rule for each — woff2 first, woff second. Two
|
||||||
|
* rules with the same family, weight, style and unicode-range mean the later
|
||||||
|
* one wins, so browsers rendered from the woff files and discarded the
|
||||||
|
* preloaded woff2.
|
||||||
|
*
|
||||||
|
* The MAN-109 audit measured the cost on the home page: 88 KiB of woff at
|
||||||
|
* VeryHigh priority, ahead of the LCP image, plus 74 KiB of preloaded woff2
|
||||||
|
* that was fetched and never used. The `amare:fonts-woff2-only` plugin in
|
||||||
|
* vite.config.js strips the woff rules; these assertions fail if a plugin
|
||||||
|
* upgrade brings them back.
|
||||||
|
*
|
||||||
|
* Reads the build output because that is where the defect lived — the source
|
||||||
|
* config looked correct the whole time. The `feature` job runs `npm run build`
|
||||||
|
* before the suite (.github/workflows/ci.yml).
|
||||||
|
*/
|
||||||
|
class FontDeliveryTest extends TestCase
|
||||||
|
{
|
||||||
|
public function test_build_ships_woff2_only(): void
|
||||||
|
{
|
||||||
|
$manifestPath = public_path('build/fonts-manifest.json');
|
||||||
|
|
||||||
|
$this->assertFileExists(
|
||||||
|
$manifestPath,
|
||||||
|
'Run `npm run build` before this suite — the font manifest is a build artifact.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$manifest = json_decode((string) file_get_contents($manifestPath), true);
|
||||||
|
|
||||||
|
$this->assertIsArray($manifest);
|
||||||
|
$this->assertNotEmpty($manifest['preloads'] ?? [], 'The critical font weights must still be preloaded.');
|
||||||
|
|
||||||
|
foreach ($manifest['preloads'] as $preload) {
|
||||||
|
$this->assertStringEndsWith('.woff2', (string) ($preload['file'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($manifest['families'] ?? [] as $alias => $family) {
|
||||||
|
foreach ($family['variants'] ?? [] as $key => $variant) {
|
||||||
|
$this->assertNotEmpty($variant['files'] ?? [], "{$alias} {$key} lost every font file.");
|
||||||
|
|
||||||
|
foreach ($variant['files'] as $file) {
|
||||||
|
$this->assertSame(
|
||||||
|
'woff2',
|
||||||
|
$file['format'] ?? null,
|
||||||
|
"{$alias} {$key} declares a {$file['format']} file. Only woff2 is served — a second "
|
||||||
|
.'format with the same family, weight and unicode-range overrides the woff2 rule and '
|
||||||
|
.'doubles the font traffic on the critical path.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stylesheet = $manifest['style']['file'] ?? null;
|
||||||
|
$this->assertIsString($stylesheet);
|
||||||
|
|
||||||
|
$css = (string) file_get_contents(public_path('build/'.$stylesheet));
|
||||||
|
|
||||||
|
$this->assertStringContainsString('format("woff2")', $css);
|
||||||
|
$this->assertStringNotContainsString('format("woff")', $css);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_no_woff_files_are_emitted_into_the_build(): void
|
||||||
|
{
|
||||||
|
$emitted = glob(public_path('build/assets/*.woff')) ?: [];
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[],
|
||||||
|
array_map('basename', $emitted),
|
||||||
|
'The build emitted legacy woff files. They are never requested by a browser that supports woff2, '
|
||||||
|
.'which is every browser this site targets.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,5 +60,55 @@ class MediaImageComponentTest extends TestCase
|
|||||||
$this->assertStringContainsString('alt="Legado"', $html);
|
$this->assertStringContainsString('alt="Legado"', $html);
|
||||||
$this->assertStringContainsString('loading="lazy"', $html);
|
$this->assertStringContainsString('loading="lazy"', $html);
|
||||||
$this->assertStringNotContainsString('srcset=', $html);
|
$this->assertStringNotContainsString('srcset=', $html);
|
||||||
|
$this->assertStringNotContainsString('<picture', $html);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_webp_variants_are_offered_ahead_of_the_original_format(): void
|
||||||
|
{
|
||||||
|
Storage::fake('public');
|
||||||
|
|
||||||
|
$manager = new ImageManager(new Driver);
|
||||||
|
Storage::disk('public')->put('content/hero.jpg', (string) $manager->create(1600, 900)->toJpeg());
|
||||||
|
ResponsiveImage::generate('content/hero.jpg', 'public');
|
||||||
|
|
||||||
|
$html = Blade::render(
|
||||||
|
'<x-media.image path="content/hero.jpg" alt="Hero" loading="eager" sizes="100vw" class="h-full w-full" />'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertStringContainsString('<source type="image/webp"', $html);
|
||||||
|
$this->assertStringContainsString('content/hero-720.jpg.webp 720w', $html);
|
||||||
|
|
||||||
|
// `display: contents` keeps the wrapper out of the layout — callers style
|
||||||
|
// the img against the grid or flex parent, and an inline <picture> would
|
||||||
|
// break `h-full`.
|
||||||
|
$this->assertStringContainsString('<picture class="contents">', $html);
|
||||||
|
|
||||||
|
// The source is a preference, not a replacement: the img keeps the
|
||||||
|
// original format for anything that cannot decode webp.
|
||||||
|
$this->assertStringContainsString('src="/storage/content/hero.jpg"', $html);
|
||||||
|
$this->assertStringContainsString('content/hero-720.jpg 720w', $html);
|
||||||
|
$this->assertStringContainsString('sizes="100vw"', $html);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_media_without_webp_siblings_renders_a_bare_image(): void
|
||||||
|
{
|
||||||
|
Storage::fake('public');
|
||||||
|
|
||||||
|
$manager = new ImageManager(new Driver);
|
||||||
|
Storage::disk('public')->put('content/partial.jpg', (string) $manager->create(1600, 900)->toJpeg());
|
||||||
|
|
||||||
|
// Variants from before webp support: the original format only.
|
||||||
|
foreach (ResponsiveImage::WIDTHS as $width) {
|
||||||
|
Storage::disk('public')->put(
|
||||||
|
ResponsiveImage::variantPath('content/partial.jpg', $width),
|
||||||
|
(string) $manager->create(1600, 900)->toJpeg()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = Blade::render('<x-media.image path="content/partial.jpg" alt="Antigo" />');
|
||||||
|
|
||||||
|
$this->assertStringContainsString('srcset=', $html);
|
||||||
|
$this->assertStringNotContainsString('<picture', $html);
|
||||||
|
$this->assertStringNotContainsString('image/webp', $html);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,4 +58,53 @@ class ResponsiveImageTest extends TestCase
|
|||||||
Storage::disk('public')->assertExists('content/new-960.jpg');
|
Storage::disk('public')->assertExists('content/new-960.jpg');
|
||||||
Storage::disk('public')->assertExists('content/new-1440.jpg');
|
Storage::disk('public')->assertExists('content/new-1440.jpg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_generates_a_webp_sibling_for_every_variant(): void
|
||||||
|
{
|
||||||
|
Storage::fake('public');
|
||||||
|
|
||||||
|
$manager = new ImageManager(new Driver);
|
||||||
|
$path = 'content/photo.jpg';
|
||||||
|
|
||||||
|
Storage::disk('public')->put($path, (string) $manager->create(2000, 1200)->toJpeg(quality: 90));
|
||||||
|
|
||||||
|
ResponsiveImage::generate($path, 'public');
|
||||||
|
|
||||||
|
foreach (ResponsiveImage::WIDTHS as $width) {
|
||||||
|
Storage::disk('public')->assertExists("content/photo-{$width}.jpg.webp");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
ResponsiveImage::WIDTHS,
|
||||||
|
array_column(ResponsiveImage::availableWebpVariants($path, 'public'), 'width'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// The original format stays available so a browser that cannot decode
|
||||||
|
// webp still has something to fall back to through the <img>.
|
||||||
|
foreach (ResponsiveImage::WIDTHS as $width) {
|
||||||
|
Storage::disk('public')->assertExists("content/photo-{$width}.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponsiveImage::delete($path, 'public');
|
||||||
|
|
||||||
|
foreach (ResponsiveImage::WIDTHS as $width) {
|
||||||
|
Storage::disk('public')->assertMissing("content/photo-{$width}.jpg.webp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_webp_originals_do_not_get_a_duplicate_webp_sibling(): void
|
||||||
|
{
|
||||||
|
Storage::fake('public');
|
||||||
|
|
||||||
|
$manager = new ImageManager(new Driver);
|
||||||
|
$path = 'content/already.webp';
|
||||||
|
|
||||||
|
Storage::disk('public')->put($path, (string) $manager->create(1600, 900)->toWebp());
|
||||||
|
|
||||||
|
ResponsiveImage::generate($path, 'public');
|
||||||
|
|
||||||
|
Storage::disk('public')->assertExists('content/already-480.webp');
|
||||||
|
Storage::disk('public')->assertMissing('content/already-480.webp.webp');
|
||||||
|
$this->assertSame([], ResponsiveImage::availableWebpVariants($path, 'public'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,69 @@ import laravel from 'laravel-vite-plugin';
|
|||||||
import { bunny } from 'laravel-vite-plugin/fonts';
|
import { bunny } from 'laravel-vite-plugin/fonts';
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bunny serves every EB Garamond weight as both woff2 and woff, and the fonts
|
||||||
|
* plugin emits an `@font-face` rule for each — woff2 first, woff second. Two
|
||||||
|
* rules with the same family, weight, style and unicode-range mean the *later*
|
||||||
|
* one wins, so the browser rendered from the woff files and never touched the
|
||||||
|
* preloaded woff2.
|
||||||
|
*
|
||||||
|
* Measured on the home page (MAN-109): 88 KiB of woff fetched at VeryHigh
|
||||||
|
* priority, ahead of the LCP image, on top of 74 KiB of preloaded woff2 that
|
||||||
|
* was downloaded and discarded. 162 KiB of font traffic for 74 KiB of fonts.
|
||||||
|
*
|
||||||
|
* woff2 has been supported by every browser this site targets since 2016, so
|
||||||
|
* the woff rules are dead weight rather than a fallback. Strip them from the
|
||||||
|
* emitted stylesheet and manifest, and drop the files from the bundle.
|
||||||
|
* `tests/Feature/PublicSite/FontDeliveryTest.php` fails if they come back.
|
||||||
|
*/
|
||||||
|
function fontsWoff2Only() {
|
||||||
|
const withoutWoffRules = (css) => css.replace(
|
||||||
|
/@font-face\s*\{[^}]*\}\s*/g,
|
||||||
|
(block) => (/format\(\s*["']woff["']\s*\)/.test(block) ? '' : block),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'amare:fonts-woff2-only',
|
||||||
|
enforce: 'post',
|
||||||
|
generateBundle(_options, bundle) {
|
||||||
|
for (const [fileName, asset] of Object.entries(bundle)) {
|
||||||
|
if (asset.type !== 'asset') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName.endsWith('.woff')) {
|
||||||
|
delete bundle[fileName];
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/fonts-[^/]*\.css$/.test(fileName)) {
|
||||||
|
asset.source = withoutWoffRules(String(asset.source));
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName.endsWith('fonts-manifest.json')) {
|
||||||
|
const manifest = JSON.parse(String(asset.source));
|
||||||
|
|
||||||
|
for (const family of Object.values(manifest.families ?? {})) {
|
||||||
|
for (const variant of Object.values(family.variants ?? {})) {
|
||||||
|
variant.files = (variant.files ?? []).filter((file) => file.format === 'woff2');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [alias, css] of Object.entries(manifest.style?.familyStyles ?? {})) {
|
||||||
|
manifest.style.familyStyles[alias] = withoutWoffRules(css);
|
||||||
|
}
|
||||||
|
|
||||||
|
asset.source = JSON.stringify(manifest, null, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
laravel({
|
laravel({
|
||||||
@@ -19,6 +82,7 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
|
fontsWoff2Only(),
|
||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
watch: {
|
watch: {
|
||||||
|
|||||||