Gemeldet 27.08.2026: "es ueberschneidet den hintergrund sieht irgendwie scheisse aus." 1. HARTE KANTEN IM HINTERGRUND Das scharfe Trio-Artwork lag als CSS-Hintergrund mit "contain" hinter der Seite und endete an einer messerscharfen Linie (bei 1366x800 exakt bei 570px und 1345px); darueber/darunter lag sichtbar die verwaschene Fassung. Das las sich wie ein aufgeklebtes Band quer ueber der Seite. Jetzt ein echtes <img>, das an seinen EIGENEN Raendern weich in die verwaschene Ebene uebergeht. Entscheidend: Die Maske haengt am Bild, nicht am Fenster -- als CSS-Hintergrund war das unmoeglich, weil die Kante je nach Fensterformat wandert. 2. "EVENT DES JAHRES" -- 700px tote Flaeche Eine 480px schmale Kachel sass zentriert in einer 1180px breiten Kiste, die selbst Rahmen und Hintergrund trug: drei Rahmen ineinander um einen einzigen Inhalt, links und rechts je 350px Leere. Jetzt volle Breite mit Bild links / Text rechts, die umgebende Kiste ist rahmenlos. Hoehe von 656px auf 332px, ohne dass Inhalt verloren geht -- das Bild ist dabei doppelt so gross wie vorher. 3. Welt-Kacheln mit einer Spur Milchglas und feinem Lichtsaum, damit sie zur Szene gehoeren statt als flache Rechtecke daraufzuliegen. BEIM BAUEN GEFUNDEN UND KORRIGIERT: Der erste Entwurf hat die versteckte zweite Event-Kachel wieder sichtbar gemacht (leere Kachel mit einsamem "Mehr erfahren"). Ursache ist die im Code zweimal dokumentierte Falle: ".jahres-event-card[hidden]" ist genau so stark wie eine Zwei-Klassen-Regel und verliert gegen die spaetere. Deshalb steht in den neuen Regeln ueberall :not([hidden]). Versionsnummern von main.css/main.js hochgesetzt -- Assets werden mit max-age=14400 ausgeliefert, sonst haette Dogi die Aenderung bis zu vier Stunden nicht gesehen. Geprueft: pruef-handy (56), pruef-barrierefrei (60), pruef-design (0 Fundstellen), pruef-blickfang (13), pruef-assets (67), pruef-musik (17) -- alle gruen. Der Musik-Knopf holt seine Farben jetzt aus dem <img> statt aus dem CSS-Hintergrund (main.js), sonst waere er ausgerechnet auf der Startseite auf die Ersatzfarbe zurueckgefallen. Co-Authored-By: Claude Opus 5 <[email protected]>
95 lines
4.1 KiB
JavaScript
95 lines
4.1 KiB
JavaScript
/* =====================================================================
|
|
Welche Bilder sind größer, als sie je angezeigt werden?
|
|
|
|
Sammelt über alle öffentlichen Seiten (Desktop UND Handy) für jedes
|
|
<img> 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 <img>. 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} <img>-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`);
|