diff --git a/server/tiktok-videos.mjs b/server/tiktok-videos.mjs index 1d6fbac8..b83be982 100644 --- a/server/tiktok-videos.mjs +++ b/server/tiktok-videos.mjs @@ -561,7 +561,14 @@ function BUEHNE(blendeZu) { + "filter='url(%23n)'/%3E%3C/svg%3E\")"; const css = [ - '#tk-buehne{position:fixed;inset:0;z-index:2147483000;pointer-events:none;', + /* Zwei Regler: `--tk-ton` ist die rohe Bereichsfarbe (Balken, + Schein, Vorhang), `--tk-ton-text` dieselbe Farbe so weit + aufgehellt, dass man sie LESEN kann. Vorgabe sind die + Hausfarben -- solange keine Seite geladen ist, gibt es keinen + Bereich, dessen Farbe man nehmen koennte. */ + '#tk-buehne{--tk-ton:' + VIOLETT + ';--tk-ton-text:' + VIOLETT + ';', + ' --tk-hell:' + AKZENT + ';', + ' position:fixed;inset:0;z-index:2147483000;pointer-events:none;', ' font-family:"Segoe UI",system-ui,-apple-system,"Helvetica Neue",Arial,sans-serif;', ' -webkit-font-smoothing:antialiased}', @@ -594,11 +601,11 @@ function BUEHNE(blendeZu) { zwei Striche, ein Wort dazwischen, weit gesperrt. */ '#tk-karte .kicker{display:flex;align-items:center;justify-content:center;gap:9px;', ' font-size:9px;font-weight:700;letter-spacing:.38em;text-transform:uppercase;', - ' color:' + VIOLETT + ';opacity:0;transform:translateY(10px);', + ' color:var(--tk-ton-text);opacity:0;transform:translateY(10px);', ' transition:opacity .42s ease .04s,transform .42s cubic-bezier(.16,.8,.3,1) .04s}', '#tk-karte .kicker i{display:block;width:22px;height:1px;', - ' background:linear-gradient(90deg,rgba(169,139,255,0),' + VIOLETT + ')}', - '#tk-karte .kicker i + i{background:linear-gradient(90deg,' + VIOLETT + ',rgba(169,139,255,0))}', + ' background:linear-gradient(90deg,transparent,var(--tk-ton-text))}', + '#tk-karte .kicker i + i{background:linear-gradient(90deg,var(--tk-ton-text),transparent)}', '#tk-karte.an .kicker{opacity:.92;transform:none}', /* `text-wrap: balance` verteilt die Woerter selbst auf die Zeilen. @@ -614,8 +621,8 @@ function BUEHNE(blendeZu) { /* Wenig Schein, dafuer ein enger dunkler Schatten: Ein breiter Schein frisst die Kanten der Buchstaben, und im Video wird daraus Matsch. */ - '#tk-karte p b{color:' + AKZENT + ';font-weight:800;', - ' text-shadow:0 1px 2px rgba(3,2,8,.85),0 0 16px rgba(142,201,255,.30)}', + '#tk-karte p b{color:var(--tk-ton-text);font-weight:800;', + ' text-shadow:0 1px 2px rgba(3,2,8,.85)}', /* Der Abspann bekommt mehr Gewicht als die Zwischenkarten: Er ist das Einzige, was der Mensch danach noch tun soll. */ @@ -623,6 +630,11 @@ function BUEHNE(blendeZu) { ' radial-gradient(100% 52% at 50% 6%, rgba(169,139,255,.36), rgba(0,0,0,0) 66%),', ' radial-gradient(130% 62% at 50% 94%, rgba(142,201,255,.22), rgba(0,0,0,0) 64%),', ' linear-gradient(180deg,#120e27 0%,#0a0817 56%,#050409 100%)}', + /* Der Abspann ist in allen vier Videos derselbe und soll auch so + aussehen -- er faellt deshalb auf die Hausfarben zurueck, egal + auf welcher Seite er steht. */ + '#tk-karte.ende{--tk-ton-text:' + AKZENT + '}', + '#tk-karte.ende .kicker{color:' + VIOLETT + '}', '#tk-karte.ende p{font-size:32px}', /* `display:block` ist nicht Zierde: Ein ist inline, und dort gelten Hoehe, Breite und margin:auto nicht. Der Strich war @@ -645,10 +657,11 @@ function BUEHNE(blendeZu) { '#tk-satz span{display:flex;align-items:stretch;gap:11px;', ' background:rgba(10,8,22,.80);backdrop-filter:blur(14px) saturate(1.25);', ' -webkit-backdrop-filter:blur(14px) saturate(1.25);', - ' border:1px solid rgba(169,139,255,.26);border-radius:15px;padding:11px 14px;', + ' border:1px solid color-mix(in srgb,var(--tk-ton) 34%,transparent);', + ' border-radius:15px;padding:11px 14px;', ' box-shadow:0 20px 44px -20px #000,inset 0 1px 0 rgba(255,255,255,.07)}', '#tk-satz i{flex:none;width:3px;border-radius:3px;', - ' background:linear-gradient(180deg,' + VIOLETT + ',' + AKZENT + ')}', + ' background:linear-gradient(180deg,var(--tk-ton),var(--tk-hell))}', '#tk-satz em{font-style:normal;font-weight:700;font-size:19px;line-height:1.32;', ' letter-spacing:-.006em;color:#ece8fa}', @@ -658,7 +671,8 @@ function BUEHNE(blendeZu) { fertigen Video -- das sieht nach Bildschirmaufnahme aus und nicht nach Film. */ '#tk-blende{position:absolute;inset:0;transform:translateY(102%);', - ' background:linear-gradient(180deg,' + VIOLETT + ' 0%,#4b3fa8 34%,#0b0918 78%,' + TINTE + ' 100%)}', + ' background:linear-gradient(180deg,var(--tk-ton) 0%,', + ' color-mix(in srgb,var(--tk-ton) 45%,#0b0918) 34%,#0b0918 78%,' + TINTE + ' 100%)}', '#tk-blende.zu{transform:translateY(0);', ' transition:transform .34s cubic-bezier(.66,0,.34,1)}', '#tk-blende.auf{transform:translateY(-102%);', @@ -773,15 +787,67 @@ function buehneFuer(seite) { was sie zudecken sollte. Der Zustand lebt deshalb hier und nicht im Browser. */ let vorhangZu = false; + + /* Die Bereichsfarbe wird ABGELESEN, nicht gefuehrt: Jede Seite des + Treffs setzt `--ton` auf ihrem , und genau dieser Wert geht + an die Buehne. Eine Liste hier waere eine zweite Wahrheit, die + irgendwann von der ersten abweicht. */ + const TON_LESEN = () => { + const b = document.getElementById("tk-buehne"); + if (!b) return null; + const roh = getComputedStyle(document.body).getPropertyValue("--ton").trim(); + if (!/^#[0-9a-f]{6}$/i.test(roh)) return null; + + /* Aufhellen, bis man es lesen kann -- dieselbe Rechnung wie bei + den Namen im Chat. #918a01 (Dogi-Media) kommt als Schrift auf + dunklem Grund sonst auf 1,9:1. */ + const teile = (h) => [1, 3, 5].map((i) => parseInt(h.substr(i, 2), 16) / 255); + const gerade = (c) => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)); + const leucht = (v) => 0.2126 * gerade(v[0]) + 0.7152 * gerade(v[1]) + 0.0722 * gerade(v[2]); + const abstand = (x, y) => { + const a = leucht(x); const b2 = leucht(y); + const hoch = Math.max(a, b2); const tief = Math.min(a, b2); + return (hoch + 0.05) / (tief + 0.05); + }; + const grund = [0.043, 0.035, 0.094]; /* #0b0918 */ + const f = teile(roh); + let text = "#f1edff"; + for (let t = 0; t <= 0.92; t += 0.04) { + const m = f.map((c) => c + (1 - c) * t); + if (abstand(m, grund) >= 4.5) { + text = "rgb(" + m.map((c) => Math.round(c * 255)).join(",") + ")"; + break; + } + } + b.style.setProperty("--tk-ton", roh); + b.style.setProperty("--tk-ton-text", text); + b.style.setProperty("--tk-hell", text); + return roh; + }; + + /* DIE FARBE WIRD BEIM SEITENWECHSEL GELESEN, NICHT BEI JEDEM + HANDGRIFF (27.09.2026). + + Erst stand das Ablesen in `auf()` -- und `auf()` laeuft vor jeder + Karte, jedem Untertitel und jedem Rollen. Das waren ueber + hundert zusaetzliche Wege in den Browser und drei Sekunden mehr + je Video, fuer eine Antwort, die sich nur beim Seitenwechsel + aendern kann. Gemessen: Video 1 wuchs von 32,9 auf 36,1 s. */ const auf = () => seite.evaluate(BUEHNE, vorhangZu); + const tonLesen = () => seite.evaluate(TON_LESEN).catch(() => null); /* Der Wiederaufbau haengt hier und nicht beim Aufrufer: Er braucht `vorhangZu`, und ein Zustand, den zwei Stellen fuehren, laeuft auseinander. */ - seite.on("load", () => { seite.evaluate(BUEHNE, vorhangZu).catch(() => {}); }); + seite.on("load", () => { + seite.evaluate(BUEHNE, vorhangZu) + .then(() => seite.evaluate(TON_LESEN)) + .catch(() => {}); + }); return { auf, + tonLesen, /** Vorhang zu. Danach darf navigiert werden. */ async zu(ms = 380) { @@ -1100,6 +1166,7 @@ for (const nr of liste) { es kurz still -- 450 ms reichten nicht, der Vorhang ging in dieser Stille auf. Kommt der Hinweis doch noch, faengt die Uhr von vorn an. */ + await b.tonLesen(); const still = await ruhig(seite); if (!still) { console.warn(` Hinweis: ${String(a[0]).replace(/^https?:\/\/[^/]+/, "")} ` diff --git a/tools/treff-farben.mjs b/tools/treff-farben.mjs new file mode 100644 index 00000000..c30d657c --- /dev/null +++ b/tools/treff-farben.mjs @@ -0,0 +1,36 @@ +/* Welche Farbe traegt jede Kachel des Community-Bereichs? + * + * Gelesen wird aus den zwei Stellen, die es wirklich entscheiden: + * workspace.js sagt, welche TON-NUMMER eine Kachel hat, start.css + * sagt, welcher Farbwert hinter der Nummer steht. Abgeschrieben wird + * nichts -- eine Liste, die niemand pflegt, kann nicht veralten. + * + * node tools/treff-farben.mjs + */ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; + +const HIER = dirname(fileURLToPath(import.meta.url)); +const js = readFileSync(join(HIER, "..", "server", "workspace.js"), "utf8"); +const css = readFileSync(join(HIER, "..", "workspace", "assets", "css", "start.css"), "utf8"); + +const hex = new Map(); +for (const m of css.matchAll(/\[data-ton="(\d+)"\]\s*\{\s*--ton:\s*(#[0-9a-fA-F]{6})/g)) { + hex.set(m[1], m[2]); +} + +/* Die Kacheln des Treffs stehen alle mit `...TREFF_GRUPPE` davor -- + daran sind sie erkennbar, ohne sie einzeln aufzuzaehlen. */ +const treffer = [...js.matchAll( + /\.\.\.TREFF_GRUPPE,\s*name:\s*"([^"]+)"[\s\S]{0,400}?ton:\s*(\d+)/g)]; + +console.log("KACHEL TON FARBE"); +for (const [, name, ton] of treffer) { + console.log(name.padEnd(20) + String(ton).padEnd(6) + (hex.get(ton) || "?")); +} +console.log("\n" + treffer.length + " Kacheln im Community-Bereich gefunden."); +if (!treffer.length) { + console.error("KEINE gefunden -- die Erkennung passt nicht mehr zum Quelltext."); + process.exit(1); +}