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
+14 -9
View File
@@ -1233,9 +1233,12 @@ body.seite-laedt main { opacity: .55; transition: opacity .15s ease; }
align-items: flex-end; align-items: flex-end;
gap: 12px; gap: 12px;
} }
/* Musik-Knopf: passt sich wieder der Farbwelt der jeweiligen Seite an /* Musik-Knopf: "Chamäleon" — passt sich per JS (main.js,
(babyblau auf Dogfather/HasiDog, rot auf Spicy-Media-Seiten) statt musikKnopfFarbenAnpassen) an das tatsächliche Bild der jeweiligen Seite
fest halb/halb zu sein. */ an (Durchschnitts- + kräftigste Farbe daraus), statt nur 3 feste
Weltfarben zu kennen. --music-c1/--music-c2 werden dafür live per
Canvas-Analyse gesetzt; ohne Bild (oder solange JS noch lädt) fällt der
Knopf sauber auf die normale Weltfarbe --accent/--accent-2 zurück. */
.music-toggle { .music-toggle {
width: 54px; width: 54px;
height: 54px; height: 54px;
@@ -1247,19 +1250,21 @@ body.seite-laedt main { opacity: .55; transition: opacity .15s ease; }
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
color: var(--accent-contrast); color: #fff;
border: 1px solid color-mix(in srgb, var(--accent) 60%, transparent); text-shadow: 0 1px 3px rgba(0, 0, 0, .55);
background: linear-gradient(135deg, var(--accent), var(--accent-2)); border: 1px solid color-mix(in srgb, var(--music-c1, var(--accent)) 60%, transparent);
background: linear-gradient(135deg, var(--music-c1, var(--accent)), var(--music-c2, var(--accent-2)));
box-shadow: 0 12px 30px rgba(0, 0, 0, .45), box-shadow: 0 12px 30px rgba(0, 0, 0, .45),
0 0 24px color-mix(in srgb, var(--accent) 35%, transparent); 0 0 24px color-mix(in srgb, var(--music-c1, var(--accent)) 35%, transparent);
transition: transform .25s ease, filter .25s ease, box-shadow .25s ease; transition: transform .25s ease, filter .25s ease, box-shadow .25s ease,
background 1.1s ease, border-color 1.1s ease;
} }
.music-toggle:hover, .music-toggle:hover,
.music-toggle:focus-visible { .music-toggle:focus-visible {
transform: translateY(-2px) scale(1.05); transform: translateY(-2px) scale(1.05);
filter: brightness(1.12); filter: brightness(1.12);
box-shadow: 0 16px 38px rgba(0, 0, 0, .5), box-shadow: 0 16px 38px rgba(0, 0, 0, .5),
0 0 34px color-mix(in srgb, var(--accent) 55%, transparent); 0 0 34px color-mix(in srgb, var(--music-c1, var(--accent)) 55%, transparent);
} }
.music-widget.open .music-toggle { transform: rotate(12deg); } .music-widget.open .music-toggle { transform: rotate(12deg); }
.music-toggle span { position: relative; z-index: 2; } .music-toggle span { position: relative; z-index: 2; }
+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 Nahtloser Seitenwechsel — damit die Musik nicht abreißt
@@ -382,6 +510,7 @@
renderHeader(); // markiert den richtigen Menüpunkt als aktiv renderHeader(); // markiert den richtigen Menüpunkt als aktiv
window.scrollTo(0, 0); window.scrollTo(0, 0);
document.body.classList.remove("seite-laedt"); document.body.classList.remove("seite-laedt");
musikKnopfFarbenAnpassen(); // Chamäleon-Knopf an die neue Seite anpassen
await skripteUebernehmen(dok); await skripteUebernehmen(dok);
} }
@@ -430,6 +559,7 @@
renderFooter(); renderFooter();
renderFog(); renderFog();
renderMusicWidget(); renderMusicWidget();
musikKnopfFarbenAnpassen();
nahtloseNavigationAktivieren(); nahtloseNavigationAktivieren();
}); });
})(); })();