From d8664e7476b55bd89d14e226aaac7ab9640287b9 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Sat, 1 Aug 2026 11:11:58 +0200 Subject: [PATCH] =?UTF-8?q?Musik-Knopf=20wird=20zum=20Cham=C3=A4leon=20-?= =?UTF-8?q?=20passt=20Farben=20live=20an=20jede=20Seite=20an?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- assets/css/main.css | 23 +++++--- assets/js/main.js | 130 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 9 deletions(-) diff --git a/assets/css/main.css b/assets/css/main.css index 9394c5d..5c9a9a7 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -1233,9 +1233,12 @@ body.seite-laedt main { opacity: .55; transition: opacity .15s ease; } align-items: flex-end; gap: 12px; } -/* Musik-Knopf: passt sich wieder der Farbwelt der jeweiligen Seite an - (babyblau auf Dogfather/HasiDog, rot auf Spicy-Media-Seiten) statt - fest halb/halb zu sein. */ +/* Musik-Knopf: "Chamäleon" — passt sich per JS (main.js, + musikKnopfFarbenAnpassen) an das tatsächliche Bild der jeweiligen Seite + 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 { width: 54px; height: 54px; @@ -1247,19 +1250,21 @@ body.seite-laedt main { opacity: .55; transition: opacity .15s ease; } align-items: center; justify-content: center; cursor: pointer; - color: var(--accent-contrast); - border: 1px solid color-mix(in srgb, var(--accent) 60%, transparent); - background: linear-gradient(135deg, var(--accent), var(--accent-2)); + color: #fff; + text-shadow: 0 1px 3px rgba(0, 0, 0, .55); + 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), - 0 0 24px color-mix(in srgb, var(--accent) 35%, transparent); - transition: transform .25s ease, filter .25s ease, box-shadow .25s ease; + 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, + background 1.1s ease, border-color 1.1s ease; } .music-toggle:hover, .music-toggle:focus-visible { transform: translateY(-2px) scale(1.05); filter: brightness(1.12); 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-toggle span { position: relative; z-index: 2; } diff --git a/assets/js/main.js b/assets/js/main.js index c585847..545ca80 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -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 ) + 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(); }); })();