Musik-Knopf wird zum Chamäleon - passt Farben live an jede Seite an

Bisher kannte der Knopf nur 3 feste Weltfarben (dogfather/hasidog/
spicymedia). Jetzt analysiert main.js (musikKnopfFarbenAnpassen) per
Canvas das tatsächliche Bild jeder einzelnen Seite - Durchschnittsfarbe
plus die kräftigste Farbe darin, als Verlauf - und setzt sie live als
--music-c1/--music-c2 auf den Knopf.

Bildquelle in Prioritätsreihenfolge: 1) scharfes Hero-Banner-Bild,
2) Startseiten-Artwork, 3) Ambiente-Hintergrundbild der Seite. Ohne
Bild fällt der Knopf sauber auf die normale Weltfarbe zurück. Läuft
beim ersten Laden UND nach jedem nahtlosen Seitenwechsel (SPA-Navigation),
damit die Farbe wirklich bei jeder Seite mitwechselt.
This commit is contained in:
2026-08-01 11:11:58 +02:00
parent 9d47a1e425
commit d8664e7476
2 changed files with 144 additions and 9 deletions
+130
View File
@@ -267,6 +267,134 @@
});
}
/* ===================================================================
Musik-Knopf: Chamäleon-Farben
Der Knopf soll nicht nur zwischen 3 festen Weltfarben wechseln,
sondern sich an das tatsächliche Bild JEDER einzelnen Seite anpassen
— Durchschnittsfarbe plus die kräftigste (sattste) Farbe darin, als
kleiner Verlauf. Bildquelle in Prioritätsreihenfolge:
1. Das scharfe Banner-Bild im Kopfbereich (.hero-banner img)
2. Das Artwork der Startseite (.hero-artwork, CSS-Hintergrund)
3. Das Ambiente-Hintergrundbild der Seite (--page-bg am <body>)
Gibt es nichts davon (reine Text-Hero-Seiten wie Links oder Videos),
fällt der Knopf sauber auf die normale Weltfarbe zurück (siehe CSS:
var(--music-c1, var(--accent))).
Bilder sind alle selbst gehostet (gleiche Domain) — Canvas-Auslesen
funktioniert dadurch ohne CORS-Probleme.
=================================================================== */
function bildquelleErmitteln() {
const banner = document.querySelector(".hero-banner img");
if (banner && banner.getAttribute("src")) return banner;
const artwork = document.querySelector(".hero-artwork");
if (artwork) {
const hintergrund = getComputedStyle(artwork, "::before").backgroundImage;
const ersteEbene = hintergrund && hintergrund.split(",")[0];
const treffer = ersteEbene && ersteEbene.match(/url\(["']?(.*?)["']?\)/);
if (treffer && treffer[1]) return treffer[1];
}
const seitenBg = getComputedStyle(document.body).getPropertyValue("--page-bg");
const treffer2 = seitenBg && seitenBg.match(/url\(["']?(.*?)["']?\)/);
if (treffer2 && treffer2[1]) return treffer2[1];
return null;
}
function rgbNachHsl(r, g, b) {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h = 0, s = 0;
const l = (max + min) / 2;
if (max !== min) {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
default: h = (r - g) / d + 4;
}
h *= 60;
}
return [h, s * 100, l * 100];
}
const begrenzen = (wert, min, max) => Math.min(max, Math.max(min, wert));
let musikFarbLauf = 0;
function musikKnopfFarbenAnpassen() {
const knopf = document.getElementById("music-toggle");
if (!knopf) return;
const lauf = ++musikFarbLauf;
const zuruecksetzen = () => {
knopf.style.removeProperty("--music-c1");
knopf.style.removeProperty("--music-c2");
};
const quelle = bildquelleErmitteln();
if (!quelle) {
zuruecksetzen();
return;
}
const verarbeiten = (bild) => {
if (lauf !== musikFarbLauf) return; // zwischenzeitlich schon weitergeklickt
try {
const groesse = 28;
const canvas = document.createElement("canvas");
canvas.width = groesse;
canvas.height = groesse;
const ctx = canvas.getContext("2d");
ctx.drawImage(bild, 0, 0, groesse, groesse);
const daten = ctx.getImageData(0, 0, groesse, groesse).data;
let rSumme = 0, gSumme = 0, bSumme = 0, n = 0;
let besteSaettigung = -1, satR = 0, satG = 0, satB = 0;
for (let i = 0; i < daten.length; i += 4) {
const r = daten[i], g = daten[i + 1], b = daten[i + 2];
rSumme += r; gSumme += g; bSumme += b; n++;
const hsl = rgbNachHsl(r, g, b);
if (hsl[2] > 15 && hsl[2] < 88 && hsl[1] > besteSaettigung) {
besteSaettigung = hsl[1]; satR = r; satG = g; satB = b;
}
}
const r = Math.round(rSumme / n), g = Math.round(gSumme / n), b = Math.round(bSumme / n);
const [h1, s1, l1] = rgbNachHsl(r, g, b);
const farbe1 = `hsl(${Math.round(h1)}deg ${Math.round(begrenzen(s1, 42, 92))}% ${Math.round(begrenzen(l1, 40, 62))}%)`;
let farbe2;
if (besteSaettigung > 18) {
const [h2, s2, l2] = rgbNachHsl(satR, satG, satB);
farbe2 = `hsl(${Math.round(h2)}deg ${Math.round(begrenzen(s2, 50, 95))}% ${Math.round(begrenzen(l2, 42, 66))}%)`;
} else {
farbe2 = `hsl(${Math.round((h1 + 45) % 360)}deg ${Math.round(begrenzen(s1, 45, 90))}% ${Math.round(begrenzen(l1 + 8, 42, 68))}%)`;
}
knopf.style.setProperty("--music-c1", farbe1);
knopf.style.setProperty("--music-c2", farbe2);
} catch {
zuruecksetzen(); // z.B. wenn das Bild aus irgendeinem Grund nicht lesbar ist
}
};
if (typeof quelle === "string") {
const bild = new Image();
bild.addEventListener("load", () => verarbeiten(bild));
bild.addEventListener("error", zuruecksetzen);
bild.src = quelle;
} else if (quelle.complete && quelle.naturalWidth > 0) {
verarbeiten(quelle);
} else {
quelle.addEventListener("load", () => verarbeiten(quelle), { once: true });
quelle.addEventListener("error", zuruecksetzen, { once: true });
}
}
/* ===================================================================
Nahtloser Seitenwechsel — damit die Musik nicht abreißt
@@ -382,6 +510,7 @@
renderHeader(); // markiert den richtigen Menüpunkt als aktiv
window.scrollTo(0, 0);
document.body.classList.remove("seite-laedt");
musikKnopfFarbenAnpassen(); // Chamäleon-Knopf an die neue Seite anpassen
await skripteUebernehmen(dok);
}
@@ -430,6 +559,7 @@
renderFooter();
renderFog();
renderMusicWidget();
musikKnopfFarbenAnpassen();
nahtloseNavigationAktivieren();
});
})();