diff --git a/public/52396285023_561ffc0ff6_o.avif b/public/52396285023_561ffc0ff6_o.avif deleted file mode 100644 index 6f355fcb..00000000 Binary files a/public/52396285023_561ffc0ff6_o.avif and /dev/null differ diff --git a/public/53569851134_02afc18fb4_o.avif b/public/53569851134_02afc18fb4_o.avif deleted file mode 100644 index 1c6690c6..00000000 Binary files a/public/53569851134_02afc18fb4_o.avif and /dev/null differ diff --git a/public/JOA00162.avif b/public/JOA00162.avif deleted file mode 100644 index 548380d2..00000000 Binary files a/public/JOA00162.avif and /dev/null differ diff --git a/public/JOA00162.webp b/public/JOA00162.webp index 025f17c7..464a0e7f 100644 Binary files a/public/JOA00162.webp and b/public/JOA00162.webp differ diff --git a/public/Jeronimo.avif b/public/Jeronimo.avif deleted file mode 100644 index f9b79d29..00000000 Binary files a/public/Jeronimo.avif and /dev/null differ diff --git a/public/Jeronimo.webp b/public/Jeronimo.webp index 0eb3d694..395ec14d 100644 Binary files a/public/Jeronimo.webp and b/public/Jeronimo.webp differ diff --git a/public/Lula.avif b/public/Lula.avif deleted file mode 100644 index 6ac065e8..00000000 Binary files a/public/Lula.avif and /dev/null differ diff --git a/public/Lula.webp b/public/Lula.webp index 512e29f3..9bb377d4 100644 Binary files a/public/Lula.webp and b/public/Lula.webp differ diff --git a/public/Marca-Lula.avif b/public/Marca-Lula.avif deleted file mode 100644 index a64c042f..00000000 Binary files a/public/Marca-Lula.avif and /dev/null differ diff --git a/public/RUI - 2-2.avif b/public/RUI - 2-2.avif deleted file mode 100644 index e0c0e424..00000000 Binary files a/public/RUI - 2-2.avif and /dev/null differ diff --git a/public/RUI - 2-2.webp b/public/RUI - 2-2.webp index 4a78f55f..96f4e3fe 100644 Binary files a/public/RUI - 2-2.webp and b/public/RUI - 2-2.webp differ diff --git a/public/WAGNER - 2-9 final.avif b/public/WAGNER - 2-9 final.avif deleted file mode 100644 index fce44019..00000000 Binary files a/public/WAGNER - 2-9 final.avif and /dev/null differ diff --git a/public/WAGNER - 2-9 final.webp b/public/WAGNER - 2-9 final.webp index 5405e4cb..bad113a9 100644 Binary files a/public/WAGNER - 2-9 final.webp and b/public/WAGNER - 2-9 final.webp differ diff --git a/public/fundo.avif b/public/fundo.avif deleted file mode 100644 index 24cd8ca8..00000000 Binary files a/public/fundo.avif and /dev/null differ diff --git a/scripts/build-radio-artes.mjs b/scripts/build-radio-artes.mjs index d6fcb0b1..f4a95f07 100644 --- a/scripts/build-radio-artes.mjs +++ b/scripts/build-radio-artes.mjs @@ -108,7 +108,7 @@ async function verticalMark(height) { /** Foto oficial do candidato (cutout com alfa). */ async function candidatePhoto(height) { - const trim = await sharp(`${REPO}public/JOA00162.avif`) + const trim = await sharp(`${REPO}public/JOA00162.webp`) .trim({ threshold: 5 }) .png() .toBuffer({ resolveWithObject: true }) diff --git a/src/app/(frontend)/(home)/page.tsx b/src/app/(frontend)/(home)/page.tsx index e510bb29..5992120e 100644 --- a/src/app/(frontend)/(home)/page.tsx +++ b/src/app/(frontend)/(home)/page.tsx @@ -52,14 +52,14 @@ const problemItems: CampaignCarouselItem[] = [ id: 'escala-6x1', title: 'Pelo fim da escala 6x1!', body: 'Jornadas longas aumentam o risco de AVC e infarto. Descanso é saúde pública. A bancada de Lula derrotou a direita na Câmara: o fim da 6×1 avançou. Mas a briga continua no Senado, onde a direita tenta barrar o avanço. No dia 4 de outubro, o 1313 é o seu sim ao descanso.', - image: '/fundo.avif', + image: '/fundo.webp', imageAlt: 'Jorge Solla defendendo direitos dos trabalhadores na Câmara dos Deputados', }, { id: 'sus', title: 'Pra valorizar o SUS', body: 'O SUS é o sistema de saúde de todo brasileiro. Com Lula, a saúde voltou a receber recursos: Nova PAC de R$ 30,5 bilhões e Mais Médicos retomado. Mas o teto de gastos da direita ainda trava o orçamento. O 1313 é seu voto em defesa do SUS no Congresso!', - image: '/53569851134_02afc18fb4_o.avif', + image: '/53569851134_02afc18fb4_o.webp', imageAlt: 'Jorge Solla em agenda pública de defesa do SUS', imageFrame: { width: 1024, @@ -71,7 +71,7 @@ const problemItems: CampaignCarouselItem[] = [ id: 'mataripe', title: 'Pra defender os baianos', body: 'A Refinaria de Mataripe foi vendida no governo Bolsonaro e segue nas mãos de estrangeiros. Com Lula e Jerônimo, a Bahia voltou a andar pra frente; mas recomprar Mataripe exige bancada forte no Congresso. Votar 1313 é devolver à Bahia o que é seu.', - image: '/52396285023_561ffc0ff6_o.avif', + image: '/52396285023_561ffc0ff6_o.webp', imageAlt: 'Vista da Bahia durante mobilização popular', }, ] diff --git a/src/components/CampaignHero.tsx b/src/components/CampaignHero.tsx index ff620a11..dd85d369 100644 --- a/src/components/CampaignHero.tsx +++ b/src/components/CampaignHero.tsx @@ -5,28 +5,28 @@ const SUPPORT_URL = 'https://apoiar.me/jorgesolla' const allyImages = [ { id: 'rui', - src: '/RUI - 2-2.avif', + src: '/RUI - 2-2.webp', width: 177, height: 265, sizes: '(min-width: 1920px) 211px, (min-width: 1366px) calc(6.14vw + 93px), 177px', }, { id: 'wagner', - src: '/WAGNER - 2-9 final.avif', + src: '/WAGNER - 2-9 final.webp', width: 177, height: 265, sizes: '(min-width: 1920px) 211px, (min-width: 1366px) calc(6.14vw + 93px), 177px', }, { id: 'jeronimo', - src: '/Jeronimo.avif', + src: '/Jeronimo.webp', width: 177, height: 265, sizes: '(min-width: 1920px) 211px, (min-width: 1366px) calc(6.14vw + 93px), 177px', }, { id: 'lula', - src: '/Lula.avif', + src: '/Lula.webp', width: 216, height: 213, sizes: @@ -68,7 +68,7 @@ export const CampaignHero = () => (
Jorge Solla, candidato a deputado federal pela Bahia { } }) + it('serve masters WebP — AVIF fica fora do otimizador (libheif)', () => { + const { container } = render() + + const sources = Array.from(container.querySelectorAll('img')).map( + (image) => image.getAttribute('src') ?? '', + ) + expect(sources.length).toBeGreaterThan(0) + + for (const source of sources) { + expect(source.endsWith('.webp'), source).toBe(true) + } + }) + it('expõe as provas e as fotos com nomes acessíveis', () => { render() diff --git a/tests/unit/imageDelivery.unit.spec.ts b/tests/unit/imageDelivery.unit.spec.ts index 021af680..51943f85 100644 --- a/tests/unit/imageDelivery.unit.spec.ts +++ b/tests/unit/imageDelivery.unit.spec.ts @@ -12,6 +12,12 @@ const MAX_HEIGHT = 2400 const LOCAL_IMAGE_REF = /src[=:]\s*['"](\/[^'"`]+\.(?:avif|webp|png|jpe?g))['"]/g +// `image:` covers the home card data (`CampaignCarouselItem.image`), which is +// also rendered through next/image — the dimension budget above only walks +// `src` literals on purpose (the card masters predate the budget), but the +// AVIF ban below must catch both spellings. +const LOCAL_IMAGE_LITERAL = /(?:src|image)[=:]\s*['"](\/[^'"`]+\.(?:avif|webp|png|jpe?g))['"]/g + const publicFile = (src: string) => fileURLToPath(new URL(`../../public${decodeURIComponent(src)}`, import.meta.url)) @@ -24,14 +30,14 @@ const localHeroAssets = [ ...new Set([...campaignHeroSource.matchAll(LOCAL_IMAGE_REF)].map(([, src]) => src)), ] -const collectSourceImageRefs = () => { +const collectSourceImageRefs = (pattern: RegExp) => { const refs = new Set() const srcDir = fileURLToPath(new URL('../../src', import.meta.url)) for (const entry of readdirSync(srcDir, { recursive: true, withFileTypes: true })) { if (!entry.isFile() || !/\.(?:ts|tsx)$/.test(entry.name)) continue const code = readFileSync(join(entry.parentPath, entry.name), 'utf8') - for (const [, src] of code.matchAll(LOCAL_IMAGE_REF)) refs.add(src) + for (const [, src] of code.matchAll(pattern)) refs.add(src) } return refs @@ -49,7 +55,9 @@ describe('entrega das imagens do hero (perf do /_next/image)', () => { }) it('mantém todo asset local entregue via next/image dentro do orçamento', async () => { - const refs = [...collectSourceImageRefs()].filter((src) => existsSync(publicFile(src))) + const refs = [...collectSourceImageRefs(LOCAL_IMAGE_REF)].filter((src) => + existsSync(publicFile(src)), + ) expect(refs.length).toBeGreaterThan(0) for (const src of refs) { @@ -59,6 +67,24 @@ describe('entrega das imagens do hero (perf do /_next/image)', () => { } }) + it('não entrega AVIF pelo otimizador (libheif: GHSA-2xp9-vwfh-vxw4 e travamento por chave)', () => { + const refs = [...collectSourceImageRefs(LOCAL_IMAGE_LITERAL)] + expect(refs.length).toBeGreaterThan(0) + + for (const src of refs) { + expect(src.endsWith('.avif'), src).toBe(false) + } + }) + + it('não mantém AVIF em public/ — o otimizador decodifica qualquer arquivo local por URL', () => { + const publicDir = fileURLToPath(new URL('../../public', import.meta.url)) + const avifFiles = readdirSync(publicDir, { recursive: true, withFileTypes: true }) + .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.avif')) + .map((entry) => entry.name) + + expect(avifFiles).toEqual([]) + }) + it('configura TTL longo para os derivados do otimizador', () => { const nextConfig = readFileSync( fileURLToPath(new URL('../../next.config.mjs', import.meta.url)),