/* ===================================================================== Welche Bilder sind größer, als sie je angezeigt werden? Sammelt über alle öffentlichen Seiten (Desktop UND Handy) für jedes die größte Anzeigebreite -- mal Pixeldichte, damit Retina zählt. Vergleicht das mit der echten Dateibreite. Ein Bild, das mehr als das 2,5-fache seiner größten Anzeige misst, ist ein Kandidat zum Verkleinern: Es überträgt Pixel, die nie ein Auge erreicht. NUR . CSS-Hintergründe (bg-*.jpg) füllen den Bildschirm und werden getrennt betrachtet. ===================================================================== */ import { chromium } from "playwright"; import { statSync, readFileSync, existsSync } from "node:fs"; const BASIS = "https://dogfather-universe.com"; const SEITEN = [ "/", "/dogfather.html", "/hasidog.html", "/manager.html", "/casper.html", "/vanvan.html", "/tt-muddi.html", "/zeitreise.html", "/streamer.html", "/community.html", "/stimmen.html", "/streamplan.html", "/team-modis.html", "/team-scouts.html", "/creator.html", "/werte.html", "/bewerben.html", "/kontakt.html", "/links.html", "/supporter.html", "/abonnieren.html", "/medien-dogfather.html", "/medien-hasidog.html", "/medien-casper.html", ]; /* Dateibreite eines lokalen JPG/PNG lesen (ohne Bibliothek). */ function breiteVon(pfad) { if (!existsSync(pfad)) return 0; const s = readFileSync(pfad); if (s[0] === 0xFF && s[1] === 0xD8) { // JPEG let i = 2; while (i < s.length) { if (s[i] !== 0xFF) { i++; continue; } const m = s[i + 1]; if (m >= 0xC0 && m <= 0xC3) return s.readUInt16BE(i + 7); i += 2 + s.readUInt16BE(i + 2); } } else if (s.slice(0, 8).equals(Buffer.from([0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A]))) { // PNG return s.readUInt32BE(16); } return 0; } const browser = await chromium.launch(); const maxAnzeige = new Map(); // dateiname -> maxBreite (CSS-px * DPR) for (const vp of [{ width: 1280, height: 900 }, { width: 390, height: 844 }]) { for (const pfad of SEITEN) { const s = await browser.newPage({ viewport: vp }); try { await s.goto(BASIS + pfad, { waitUntil: "networkidle", timeout: 30000 }); await s.waitForTimeout(300); const m = await s.evaluate(() => { const raus = {}; for (const im of document.querySelectorAll("img")) { const src = (im.currentSrc || im.src || ""); if (!src.includes("/assets/img/")) continue; // nur eigene Bilder const name = src.split("/assets/img/")[1].split("?")[0]; const r = im.getBoundingClientRect(); if (r.width < 2) continue; const px = Math.round(r.width * (window.devicePixelRatio || 1)); raus[name] = Math.max(raus[name] || 0, px); } return raus; }); for (const [k, v] of Object.entries(m)) maxAnzeige.set(k, Math.max(maxAnzeige.get(k) || 0, v)); } catch { /* egal */ } await s.close(); } } await browser.close(); const kandidaten = []; for (const [name, anzeige] of maxAnzeige) { const pfad = "assets/img/" + name; const dateiBreite = breiteVon(pfad); if (!dateiBreite || !existsSync(pfad)) continue; const kb = Math.round(statSync(pfad).size / 1024); const faktor = dateiBreite / Math.max(anzeige, 1); /* 2,5-fach als Schwelle: 2x deckt Retina, der Rest ist Puffer gegen Layout-Schwankungen. Nur nennen, was auch spürbar wiegt (>=120 KB). */ if (faktor >= 2.5 && kb >= 120) { kandidaten.push({ name, dateiBreite, anzeige, faktor: faktor.toFixed(1), kb, ziel: Math.min(dateiBreite, anzeige * 2) }); } } kandidaten.sort((a, b) => b.kb - a.kb); console.log(`\n${maxAnzeige.size} -Bilder gesehen. Überdimensioniert (>= 2,5x Anzeige, >= 120 KB):\n`); if (!kandidaten.length) console.log(" keine -- alle Bilder passen zu ihrer Anzeigegröße."); for (const k of kandidaten) { console.log(` ${k.kb.toString().padStart(4)} KB ${k.name.padEnd(26)} Datei ${k.dateiBreite}px, angezeigt ${k.anzeige}px (${k.faktor}x) -> Ziel ${k.ziel}px`); } const summe = kandidaten.reduce((a, k) => a + k.kb, 0); if (kandidaten.length) console.log(`\n Summe der Kandidaten: ${summe} KB`);