diff --git a/server/crew-adresse.js b/server/crew-adresse.js index 2160bdb5..393a8419 100644 --- a/server/crew-adresse.js +++ b/server/crew-adresse.js @@ -255,6 +255,56 @@ export function crewWeiche(req, res, next) { return next(); } + /* DIE BUEHNEN INNEN GEHOEREN AUCH DER MODI-APP (10.09.2026). + + Filipe: "auch die hintergrund bilder vom crew espace sollen anderes + aussehen, der community angepasst wie die zugangscode seite die ist + mega." + + Die neun Buehnen hinter den Seiten zeigen nicht bloss rotes Licht, + sondern das Spicy-Media-Logo selbst -- in `halle` und `arena` sogar + vielfach nebeneinander. Umfaerben haette daraus ein blaues fremdes + Logo gemacht, also etwas Schlimmeres. Es gibt jetzt eigene Motive + aus den Community-Bildern der Dogfather-Seite (tools/crew-buehnen.mjs). + + WARUM DIE ADRESSE UMGEBOGEN WIRD UND NICHT DAS CSS: `start.css` + nennt die neun Bilder in neun Zeilen und wird an alle + ausgeliefert. Eine zweite Zeile je Szene fuer die Modi-App waere + achtzehn Zeilen, die zusammenpassen muessen -- und der Name der + Szene stuende dann doppelt da. Hier steht eine Regel. + + UND SIE HAT KEIN FLACKERN: Das Bild wird beim ERSTEN Abruf schon + als das richtige geliefert. Eine Umschaltung per JavaScript haette + erst die Spicy-Buehne gezeigt und dann getauscht -- sichtbar, und + ausgerechnet auf der Seite, die niemand sehen soll. */ + const buehne = /^\/workspace\/assets\/img\/(buehne-[a-z]+-(?:uhd|breit|schmal)\.webp)$/ + .exec(req.path); + if (buehne) { + req.url = `/workspace/assets/img/crew-${buehne[1]}`; + return next(); + } + + /* DAS ZEICHEN IN DER KOPFLEISTE (10.09.2026). + + `start.css` setzt links neben den Namen eine Chili -- als + CSS-Hintergrund, nicht als Bild im HTML. Auf der Modi-Adresse stand + damit oben links das Zeichen von Spicy Media, auf jeder Seite, den + ganzen Tag. Im Bildschirmfoto der Startseite war es das Erste, was + auffiel. + + UEBER DIE ADRESSE UND NICHT UEBER JAVASCRIPT: Eine Umschaltung im + Browser haette erst die Chili gezeigt und sie dann getauscht -- + sichtbar, und ausgerechnet an der Stelle, die man zuerst ansieht. + So kommt beim ersten Abruf schon der Husky. + + Er liegt laengst da (marke-husky.webp) und wird auf der Zugangswand + und im App-Symbol ohnehin benutzt. Ein neues Bild waere eine Datei + mehr fuer dasselbe Zeichen. */ + if (req.path === "/workspace/assets/img/marke-spicy.webp") { + req.url = "/workspace/assets/img/marke-husky.webp"; + return next(); + } + if (istWorkspacePfad(req.path) || req.path.startsWith("/assets/")) return next(); return res.redirect(302, "/workspace/"); } diff --git a/server/pruef-crew-adresse.mjs b/server/pruef-crew-adresse.mjs index f0921b16..f05f35be 100644 --- a/server/pruef-crew-adresse.mjs +++ b/server/pruef-crew-adresse.mjs @@ -484,6 +484,72 @@ for (const b of buehnen) { ok(buehnen.length >= 8 && buehnenDa === buehnen.length, `crew.: alle ${buehnenDa} von ${buehnen.length} Buehnenbildern werden ausgeliefert`); +/* ===================================================================== + DIE BUEHNEN INNEN (10.09.2026) + + Filipe: "auch die hintergrund bilder vom crew espace sollen anderes + aussehen, der community angepasst wie die zugangscode seite". + + DIE SZENENNAMEN WERDEN AUS start.css GELESEN, nicht hier + abgeschrieben. Eine Liste an zwei Stellen ist eine Liste, die + auseinanderlaeuft -- und dann faellt eine neue Szene ohne eigenes + Motiv niemandem auf: Auf der Modi-Seite stuende ploetzlich wieder + ein fremdes Logo im Hintergrund, und zwar genau auf der Seite, an + die niemand gedacht hat. + ===================================================================== */ +console.log(""); +console.log("--- Die Buehnen innen ---"); +{ + const { readFileSync } = await import("node:fs"); + const { dirname, join } = await import("node:path"); + const { fileURLToPath } = await import("node:url"); + const css = readFileSync( + join(dirname(fileURLToPath(import.meta.url)), "..", "workspace", "assets", "css", "start.css"), + "utf8"); + const namen = [...new Set((css.match(/buehne-[a-z]+-(?:uhd|breit|schmal)\.webp/g) || []))]; + + /* Die Zahl gehoert in die Bedingung: Faende das Muster nichts -- + umbenannte Dateien, anderer Ordner --, waere alles darunter gruen, + ohne dass ein einziges Bild angesehen wurde. */ + ok(namen.length >= 5, `${namen.length} Buehnenbilder in start.css gefunden`); + + let eigene = 0, alte = 0; + const fehlend = []; + for (const name of namen) { + const aufCrew = await hole(`/workspace/assets/img/${name}`, { host: CREW }); + const aufWs = await hole(`/workspace/assets/img/${name}`, { host: WS }); + if (aufCrew.status === 200) eigene++; else fehlend.push(name); + if (aufWs.status === 200) alte++; + } + ok(eigene === namen.length, + `crew.: fuer alle ${namen.length} gibt es ein eigenes Motiv` + + (fehlend.length ? ` -- es fehlt: ${fehlend.slice(0, 3).join(", ")}` : "")); + ok(alte === namen.length, `workspace.: dort liegen unveraendert die alten (${alte})`); + + /* DAS ZEICHEN IN DER KOPFLEISTE. Es kommt aus dem CSS als + Hintergrundbild -- auf der Modi-Adresse stand dort die Chili von + Spicy Media, auf jeder Seite. */ + const zeichenCrew = await hole("/workspace/assets/img/marke-spicy.webp", { host: CREW }); + const zeichenWs = await hole("/workspace/assets/img/marke-spicy.webp", { host: WS }); + const husky = await hole("/workspace/assets/img/marke-husky.webp", { host: WS }); + ok(zeichenCrew.status === 200 && husky.status === 200 + && zeichenCrew.text === husky.text, + "crew.: oben links kommt der Husky, nicht die Chili"); + ok(zeichenWs.status === 200 && zeichenWs.text !== husky.text, + "workspace.: dort bleibt es die Chili (Gegenprobe)"); + + /* UND SIE MUESSEN VERSCHIEDEN SEIN. Beide Adressen antworten mit 200 + -- das sagt noch nicht, dass es zwei verschiedene Bilder sind. Ohne + diese Zeile waere die Pruefung auch dann gruen, wenn die Weiche gar + nicht greift. */ + const probe = namen.find((n) => n.endsWith("-breit.webp")) || namen[0]; + const a1 = await hole(`/workspace/assets/img/${probe}`, { host: CREW }); + const a2 = await hole(`/workspace/assets/img/${probe}`, { host: WS }); + ok(a1.text.length > 0 && a2.text.length > 0 && a1.text !== a2.text, + `und es sind zwei verschiedene Bilder (${probe}: ` + + `${Math.round(a1.text.length / 1024)} KB gegen ${Math.round(a2.text.length / 1024)} KB)`); +} + /* ===================================================================== DIE GEGENPROBE diff --git a/tools/crew-buehnen.mjs b/tools/crew-buehnen.mjs new file mode 100644 index 00000000..7d6c095c --- /dev/null +++ b/tools/crew-buehnen.mjs @@ -0,0 +1,446 @@ +/* =================================================================== + Die Buehnen der Modi-App (crew.dogfather-universe.com, 10.09.2026) + + Filipe: "auch die hintergrund bilder vom crew espace sollen anderes + aussehen, der community angepasst wie die zugangscode seite die ist + mega." + + --------------------------------------------------------------------- + WARUM UMFAERBEN HIER NICHT GENUEGT + + Bei der Zugangswand hat es gereicht, die Szene ins Blau zu ziehen -- + dort war Rot das Problem. INNEN ist es etwas anderes: Die neun + Buehnen zeigen nicht bloss rotes Licht, sondern das SPICY-MEDIA-LOGO + selbst, in `halle` und `arena` sogar vielfach nebeneinander. Ein + blaues Spicy-Logo waere schlimmer als ein rotes: Es saehe aus, als + haette jemand die falsche Marke eingefaerbt. + + Die Modi-App braucht deshalb eigene Motive -- und die gibt es + laengst. Auf der oeffentlichen Dogfather-Seite liegen die + Community-Bilder, die genau das zeigen, was in dieser App passiert. + + --------------------------------------------------------------------- + WELCHES BILD WOHIN -- und das folgt dem, was auf der Seite passiert + + studio Startseite "Team Dogi -- Familie. Treue. + Zusammenhalt." Filipe mit seinem + Husky. Der Eingang gehoert dem, + worum es geht. + lounge Chat, Community dasselbe Motiv: Wo geredet wird, + steht das Miteinander im Bild. + halle Angebote, Eingang, TEAM DOGI mit den Menschen des + Steckbrief Teams -- hier geht es um sie. + skyline Kalender "Streamplan": Dogi und HasiDog + auf der Buehne. Woertlich das + Thema der Seite. + portal Ideen-Board, dieselbe Buehne -- dort wird + Live-Ablauf gesendet, und dorthin gehen die + Ideen. + showbuehne Dashboard, Reports ebenfalls die Buehne. + arena Dateien, Wissen "DogFathers Galerie": Filmrollen + und Bildschirme. Ein Archiv sieht + so aus. + garage Aufgaben, Technik "Der Streamer": Filipe an seinem + Platz, Monitore, Ringlicht. Dort + wird gearbeitet. + + ZUERST STAND HIER DER HOLO-KONTROLL- + RAUM, und er sah grossartig aus -- + aber darauf steht "WERDE MODI". + Eine Einladung hinter dem + Aufgabenbrett von Leuten, die + laengst dabei sind, liest sich + falsch. Ein Bild sagt etwas, auch + wenn es nur Hintergrund ist. + wald Start-Check wieder das Familienmotiv; die + Seite gibt es fuer diese Rollen + gar nicht und sie ist nur da, + damit kein Rueckfall auf eine + Spicy-Buehne moeglich ist. + + FUENF BILDER AUF NEUN PLAETZE, mit denselben Regeln wie im Haus: + Paare, die inhaltlich benachbart sind und nie nebeneinander auf einer + Seite stehen -- man sieht immer nur eins. + + ALLE NEUN werden gebaut, auch die, die eine Modi-Rolle nie zu sehen + bekommt. Der Grund steht in `start.css`: Dort ist `studio` der + Rueckfall fuer jede Seite ohne eigene Szene. Eine Luecke hier hiesse, + dass irgendwann doch ein Spicy-Logo im Hintergrund der Modi-App + steht -- und zwar genau auf der Seite, an die niemand gedacht hat. + + --------------------------------------------------------------------- + DIE GEGENPROBE: NICHT HELLER ALS DAS, WAS SCHON LAEUFT + + Ueber diesen Bildern steht Text. Die Dogfather-Motive sind heller als + die Spicy-Szenen -- weisse Schrift im Bild, helles Fell, Scheinwerfer. + Ein Bild, das gut aussieht und die Schrift darueber unlesbar macht, + ist der schnellste Weg zu einer Oberflaeche, die niemand benutzt. + + Deshalb wird jede fertige Fassung gegen die BESTEHENDE gleicher Szene + und Groesse gehalten: Das obere Zehntel der Helligkeit im mittleren + Band -- dort, wo der Inhalt steht -- darf nicht heller sein als dort. + Das ist kein selbst gewaehlter Grenzwert, sondern der Stand, der seit + Wochen laeuft und den pruef-buehne an echten Bildpunkten gemessen hat. + + Aufrufen mit: node tools/crew-buehnen.mjs + =================================================================== */ + +import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const WURZEL = join(dirname(fileURLToPath(import.meta.url)), ".."); +const QUELLE = join(WURZEL, "assets", "img"); +const ZIEL = process.env.BUEHNEN_ZIEL || join(WURZEL, "workspace", "assets", "img"); +/* Zum Vergleichen immer die echten -- auch wenn das Ziel umgelenkt ist. */ +const VERGLEICH = join(WURZEL, "workspace", "assets", "img"); + +const SZENEN = { + studio: "bg-community.jpg", + lounge: "bg-community.jpg", + wald: "bg-community.jpg", + halle: "bg-modis.jpg", + skyline: "bg-streamplan.jpg", + portal: "bg-streamplan.jpg", + showbuehne: "bg-streamplan.jpg", + arena: "bg-dogfather.jpg", + garage: "bg-streamer.jpg", +}; + +/* WIE STARK ABGEDUNKELT -- AUSGERECHNET, NICHT GEWAEHLT. + + Erster Anlauf: ein fester Wert fuer alle, 0,34. Die Messung unten + hat ihn zerlegt -- achtzehn von siebenundzwanzig Fassungen waren + heller als die Buehne, die sie ersetzen, teils deutlich (114 gegen + 67). Ueber ihnen steht derselbe kleine Text; ich haette Bilder + ausgeliefert, die gut aussehen und die Schrift darueber schlechter + lesbar machen. + + EIN FESTER WERT KANN DAS AUCH NICHT LOESEN. Die fuenf Motive sind + verschieden hell: "Team Dogi" ist eine Nacht mit einem Ring, der + Holo-Kontrollraum ist von vorne bis hinten beleuchtet. Was fuer das + eine reicht, macht das andere zum Schemen -- und genau das ist beim + Tresorbild schon einmal passiert (0,62 uebernommen, man sah nichts + mehr). + + Also wird je Fassung gerechnet: Erst ohne Abdunklung bauen und + messen, dann ausrechnen, wie viel Schwarz noetig ist, um genau auf + den Wert der bestehenden Buehne zu kommen -- und nur so viel. + Schwarz mit Deckkraft a macht aus L ein L*(1-a); daraus folgt + a = 1 - Ziel/Ist. So dunkel wie noetig, nicht dunkler. */ +const GAMMA_MAX = 3.2; // darueber bleibt vom Motiv ein Schemen + +/** Ein ERSTER Schaetzwert fuer die Kurve. + * + * Aus v' = 255*(v/255)^g folgt g = ln(Ziel/255) / ln(Ist/255) -- das + * gilt aber nur fuer GRAUE Bildpunkte. Die Kurve wirkt auf jeden + * Farbkanal einzeln, die Helligkeit ist eine gewichtete Summe der + * drei, und L(v^g) ist nicht L(v)^g, sobald ein Bildpunkt farbig ist. + * + * Genau daran ist der erste Anlauf gescheitert: Die Rechnung sagte + * 61,5 und heraus kamen 67. Deshalb ist das hier nur der Startwert -- + * danach wird nachgemessen und nachgezogen, bis es stimmt. */ +function gammaFuer(ist, ziel) { + if (!(ist > 0) || ist >= 255 || ziel <= 0) return 1; + const g = Math.log(ziel / 255) / Math.log(ist / 255); + return Math.min(Math.max(g, 1), GAMMA_MAX); +} + +/** + * Baut so lange nach, bis der Spitzenwert das Ziel erreicht. + * + * ZWEI WERKZEUGE, IN DIESER REIHENFOLGE: + * 1. Die Kurve -- sie senkt die Lichter viel staerker als die Tiefen + * und laesst dem Bild seine Zeichnung. + * 2. Erst wenn die Kurve am Anschlag ist (GAMMA_MAX), kommt ein + * flacher schwarzer Schleier dazu. Er senkt alles gleich und legt + * Nebel ueber das Bild -- deshalb so spaet und so wenig wie + * moeglich. + * + * Hoechstens sechs Anlaeufe. Ein Naeherungsverfahren ohne Deckel ist + * eine Endlosschleife mit Umweg. + */ +async function annaehern(bauArgs, ziel) { + let gamma = 1, dunkel = 0; + let r = await bauen({ ...bauArgs, gamma, dunkel }); + gamma = gammaFuer(r.spitze, ziel); + + for (let runde = 0; runde < 6; runde++) { + r = await bauen({ ...bauArgs, gamma, dunkel }); + if (r.spitze <= ziel + 1) break; + if (gamma < GAMMA_MAX) { + /* Noch einmal dieselbe Rechnung, diesmal vom gemessenen Stand + aus -- die Kurven verketten sich. */ + gamma = Math.min(gamma * gammaFuer(r.spitze, ziel), GAMMA_MAX); + } else { + /* Kurve am Anschlag: den Rest flach wegnehmen. Schwarz mit + Deckkraft a macht aus L ein L*(1-a) -- und das gilt fuer + Farbe genauso wie fuer Grau, hier rechnet nichts falsch. */ + dunkel = Math.min(1 - (1 - dunkel) * (ziel / r.spitze), 0.8); + } + } + return { ...r, gamma, dunkel }; +} + +/* WIE STARK GEKUEHLT. + + Die Motive sind unterschiedlich warm: "Team Dogi" hat einen orangen + Ring, "TEAM DOGI mit den Menschen" ist Feuer. Nebeneinander in einer + App wirken sie wie fuenf verschiedene Programme. + + Ein Drittel Blau ueber den Mischmodus "color" zieht sie zusammen, + ohne sie zu faelschen -- das Feuer bleibt Feuer, es glueht nur + kuehler. Ganz blau waere die Zugangswand, und die ist eine Buehne + fuer EIN Motiv; hier sind es fuenf, die zusammenpassen muessen. */ +const KUEHLE = 0.32; + +for (const datei of new Set(Object.values(SZENEN))) { + const p = join(QUELLE, datei); + if (!existsSync(p)) { console.error(`Fehlt: ${p}`); process.exit(1); } +} + +const alsDaten = (p) => "data:image/jpeg;base64," + readFileSync(p).toString("base64"); + +const { chromium } = await import( + "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); +const browser = await chromium.launch(); +const seite = await (await browser.newContext({ deviceScaleFactor: 1 })).newPage(); +await seite.setViewportSize({ width: 400, height: 300 }); +if (!existsSync(ZIEL)) mkdirSync(ZIEL, { recursive: true }); + +/** Baut eine Fassung und misst sie gleich mit. */ +async function bauen({ quelle, breite, ausschnitt = null, gamma = 1, dunkel = 0, guete }) { + const roh = await seite.evaluate(async (a) => { + const i = new Image(); i.src = a.daten; await i.decode(); + + const qx = a.ausschnitt ? a.ausschnitt[0] * i.width : 0; + const qy = a.ausschnitt ? a.ausschnitt[1] * i.height : 0; + const qb = a.ausschnitt ? a.ausschnitt[2] * i.width : i.width; + const qh = a.ausschnitt ? a.ausschnitt[3] * i.height : i.height; + + const zb = a.breite; + const zh = Math.round(zb * (qh / qb)); + + const c = document.createElement("canvas"); + c.width = zb; c.height = zh; + const g = c.getContext("2d"); + g.imageSmoothingQuality = "high"; + + g.filter = "contrast(1.06) saturate(1.04)"; + g.drawImage(i, qx, qy, qb, qh, 0, 0, zb, zh); + g.filter = "none"; + + /* Kuehlen: Farbton und Saettigung von der Farbe, Helligkeit vom + Bild -- jede Kante und jeder Funke bleibt stehen. */ + g.globalCompositeOperation = "color"; + g.globalAlpha = a.kuehle; + g.fillStyle = "#2a5f8c"; + g.fillRect(0, 0, zb, zh); + g.globalAlpha = 1; + g.globalCompositeOperation = "source-over"; + + /* Vignette: aussen dunkler, damit die Raender der Seite ruhig + bleiben und die Mitte trotzdem etwas zeigt. */ + const v = g.createRadialGradient(zb / 2, zh / 2, Math.min(zb, zh) * 0.26, + zb / 2, zh / 2, Math.max(zb, zh) * 0.74); + v.addColorStop(0, "rgba(0,0,0,0)"); + v.addColorStop(0.6, "rgba(0,0,0,.18)"); + v.addColorStop(1, "rgba(0,0,0,.46)"); + g.fillStyle = v; + g.fillRect(0, 0, zb, zh); + + /* =================================================================== + ABDUNKELN MIT EINER KURVE, NICHT MIT EINEM SCHLEIER + + Ein flaches Schwarz mit Deckkraft a macht aus jedem L ein + L*(1-a): Es senkt alles im selben Verhaeltnis. Bei den hellsten + Motiven waeren dafuer ueber 80 % noetig gewesen -- und darunter + liegt dann ein grauer Nebel statt eines Bildes. Genau das ist + beim Tresorbild schon einmal passiert. + + Eine Gammakurve senkt die Lichter viel staerker als die Tiefen: + v' = 255 * (v/255)^g. Bei g = 1,8 faellt 200 auf 165 (-17 %), + aber 60 auf 19 (-69 %). Das Bild wird dunkler UND behaelt seine + Zeichnung -- die hellen Stellen bleiben die hellen, sie + schreien nur nicht mehr. + + `g` wird nicht geraten: Aus dem gemessenen Spitzenwert p und dem + Ziel folgt g = ln(Ziel/255) / ln(p/255). Die Tabelle wird einmal + gefuellt und dann 256-fach nachgeschlagen statt millionenfach + gerechnet. */ + if (a.gamma && a.gamma !== 1) { + const tabelle = new Uint8ClampedArray(256); + for (let v = 0; v < 256; v++) tabelle[v] = Math.round(255 * (v / 255) ** a.gamma); + const bd = g.getImageData(0, 0, zb, zh); + const px = bd.data; + for (let k = 0; k < px.length; k += 4) { + px[k] = tabelle[px[k]]; + px[k + 1] = tabelle[px[k + 1]]; + px[k + 2] = tabelle[px[k + 2]]; + } + g.putImageData(bd, 0, 0); + } + + /* Der flache Schleier -- nur, wenn die Kurve nicht gereicht hat. */ + if (a.dunkel > 0) { + g.fillStyle = `rgba(0,0,0,${a.dunkel})`; + g.fillRect(0, 0, zb, zh); + } + + /* MESSUNG: das obere Zehntel der Helligkeit im MITTLEREN BAND. + Dort steht der Inhalt; die Raender sind ohnehin dunkel und + wuerden den Wert schoenrechnen. */ + const by = Math.round(zh * 0.18), bh = Math.round(zh * 0.64); + const d = g.getImageData(0, by, zb, bh).data; + const hell = []; + for (let k = 0; k < d.length; k += 4) { + hell.push(0.2126 * d[k] + 0.7152 * d[k + 1] + 0.0722 * d[k + 2]); + } + hell.sort((x, y) => x - y); + + /* Die Streuung sagt, wie viel MOTIV in dem Ausschnitt steckt. + Eine leere dunkle Ecke ist zwar schoen dunkel -- und zeigt + nichts. */ + let summe = 0, quadrate = 0; + for (const h of hell) { summe += h; quadrate += h * h; } + const mittel = summe / hell.length; + + return { + bild: c.toDataURL("image/webp", a.guete), + spitze: hell[Math.floor(hell.length * 0.9)], + streuung: Math.sqrt(Math.max(quadrate / hell.length - mittel * mittel, 0)), + breite: zb, hoehe: zh, + }; + }, { daten: alsDaten(quelle), breite, ausschnitt, gamma, dunkel, guete, kuehle: KUEHLE }); + + return { ...roh, puffer: Buffer.from(roh.bild.split(",")[1], "base64") }; +} + +/** Dieselbe Messung an einer BESTEHENDEN Datei -- der Massstab. */ +async function messen(pfad) { + if (!existsSync(pfad)) return null; + return await seite.evaluate(async (b64) => { + const i = new Image(); i.src = "data:image/webp;base64," + b64; await i.decode(); + const c = document.createElement("canvas"); + c.width = i.width; c.height = i.height; + const g = c.getContext("2d"); + g.drawImage(i, 0, 0); + const by = Math.round(i.height * 0.18), bh = Math.round(i.height * 0.64); + const d = g.getImageData(0, by, i.width, bh).data; + const hell = []; + for (let k = 0; k < d.length; k += 4) { + hell.push(0.2126 * d[k] + 0.7152 * d[k + 1] + 0.0722 * d[k + 2]); + } + hell.sort((x, y) => x - y); + return hell[Math.floor(hell.length * 0.9)]; + }, readFileSync(pfad).toString("base64")); +} + +const maengel = []; +let gebaut = 0, verglichen = 0, gesamt = 0; + +console.log("\n--- Die neun Buehnen der Modi-App ---"); +for (const [szene, datei] of Object.entries(SZENEN)) { + const quelle = join(QUELLE, datei); + const zeile = []; + + for (const [name, breite, guete] of [ + ["uhd", 2400, 0.88], + ["breit", 1600, 0.88], + /* HOCHKANT: die Mitte, hochkant beschnitten -- ein auf 9:16 + gequetschtes Breitbild zeigt sonst nur Rand. Und dunkler: Auf dem + Handy liegen die kleinsten Texte der ganzen Anwendung genau in + der Bildmitte. Dieselbe Ueberlegung wie bei den Spicy-Buehnen, + dort am 07.09.2026 an echten Bildpunkten gemessen. */ + ["schmal", 900, 0.87], + ]) { + /* Der Massstab: dieselbe Szene, dieselbe Groesse, wie sie seit + Wochen laeuft. Ohne ihn ist "nicht heller" eine Behauptung -- + deshalb ist sein Fehlen ein FEHLER und kein Ueberspringen. */ + const alt = await messen(join(VERGLEICH, `buehne-${szene}-${name}.webp`)); + if (alt === null) { + maengel.push(`${szene}/${name}: keine bestehende Fassung zum Vergleichen`); + continue; + } + verglichen++; + + /* =================================================================== + DER HOCHKANT-AUSSCHNITT WIRD GESUCHT, NICHT GESETZT + + Das Haus schneidet fuer das Handy die MITTE heraus -- bei den + Spicy-Szenen richtig, dort steht das Logo mittig und der Grund + ringsum ist dunkel. Bei den Dogfather-Motiven ist die Mitte + genau das Hellste: "STREAMPLAN" in weisser Schrift, die + Scheinwerfer, der beleuchtete Kontrollraum. + + Fuenf von neun Fassungen blieben deshalb auch mit 66 % Schwarz + zu hell -- und 81 % waeren noetig gewesen, also ein Schemen. + + Jetzt werden fuenf Fenster durchprobiert und zwei Fragen + gestellt, in dieser Reihenfolge: + 1. Wird es mit hoechstens 66 % Schwarz dunkel genug? (Pflicht) + 2. In welchem steckt am meisten Motiv? (Kuer, ueber die + Streuung) + So viel Motiv wie moeglich, so dunkel wie noetig. Faellt Frage 1 + ueberall durch, wird das dunkelste genommen UND gemeldet -- die + Messung ganz unten schlaegt dann ohnehin an. */ + let ausschnitt = null; + if (name === "schmal") { + /* Das Fenster wird gesucht: erst dunkel genug, dann so viel + Motiv wie moeglich. */ + let bester = null; + for (const x of [0, 0.11, 0.22, 0.33, 0.44]) { + const fenster = [x, 0, 0.56, 1]; + const probe = await bauen({ quelle, breite, ausschnitt: fenster, guete }); + const g = gammaFuer(probe.spitze, alt); + const passt = g < GAMMA_MAX; + if (!bester + || (passt && !bester.passt) + || (passt === bester.passt + && (passt ? probe.streuung > bester.probe.streuung + : probe.spitze < bester.probe.spitze))) { + bester = { fenster, probe, passt }; + } + } + ausschnitt = bester.fenster; + } + + const r = await annaehern({ quelle, breite, ausschnitt, guete }, alt); + const { gamma, dunkel } = r; + writeFileSync(join(ZIEL, `crew-buehne-${szene}-${name}.webp`), r.puffer); + gebaut++; + gesamt += r.puffer.length; + + /* Und danach NOCH EINMAL messen. Die Rechnung sagt, was noetig + waere; ob es gereicht hat, sagt nur das fertige Bild -- WebP + rundet, und die Deckelung bei 0,66 kann greifen. */ + if (r.spitze > alt + 2) { + maengel.push(`${szene}/${name}: bleibt heller als die bestehende Buehne ` + + `(${r.spitze.toFixed(1)} gegen ${alt.toFixed(1)}, Kurve ${gamma.toFixed(2)}, ` + + `Schleier ${(dunkel * 100).toFixed(0)} %)`); + } + zeile.push(`${name} ${Math.round(r.puffer.length / 1024)} KB ` + + `· Kurve ${gamma.toFixed(2)}${dunkel ? ` +${(dunkel * 100).toFixed(0)} %` : ""}` + + ` · ${r.spitze.toFixed(0)}/${alt.toFixed(0)}` + + (ausschnitt ? ` · ab ${(ausschnitt[0] * 100).toFixed(0)} %` : "")); + } + console.log(` ${szene.padEnd(11)} ${datei.padEnd(22)} ${zeile.join(" · ")}`); +} + +await browser.close(); + +/* Die Zahlen gehoeren in die Bedingung, nicht nur in den Bericht. */ +const soll = Object.keys(SZENEN).length * 3; +if (gebaut !== soll) { + console.error(`\nFEHLER: ${gebaut} von ${soll} Fassungen erzeugt.`); process.exit(1); +} +if (verglichen !== soll) { + console.error(`\nFEHLER: nur ${verglichen} von ${soll} gegen eine bestehende Buehne gehalten.`); + process.exit(1); +} +if (maengel.length) { + console.error("\nFEHLER:\n " + maengel.join("\n ")); process.exit(1); +} +console.log(`\n${gebaut} Fassungen, ${Math.round(gesamt / 1024 / 1024 * 10) / 10} MB.` + + ` Keine ist heller als die bestehende Buehne derselben Szene --` + + ` der Text darueber bleibt also mindestens so lesbar wie bisher.`); diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index a0c456ae..a6dab255 100644 --- a/workspace/app.webmanifest +++ b/workspace/app.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/workspace-192.png?v=202609101626", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609101702", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609101626", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609101702", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609101626", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609101702", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609101626", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609101702", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/img/crew-buehne-arena-breit.webp b/workspace/assets/img/crew-buehne-arena-breit.webp new file mode 100644 index 00000000..129d2749 Binary files /dev/null and b/workspace/assets/img/crew-buehne-arena-breit.webp differ diff --git a/workspace/assets/img/crew-buehne-arena-schmal.webp b/workspace/assets/img/crew-buehne-arena-schmal.webp new file mode 100644 index 00000000..30c1a4d7 Binary files /dev/null and b/workspace/assets/img/crew-buehne-arena-schmal.webp differ diff --git a/workspace/assets/img/crew-buehne-arena-uhd.webp b/workspace/assets/img/crew-buehne-arena-uhd.webp new file mode 100644 index 00000000..c28aaecc Binary files /dev/null and b/workspace/assets/img/crew-buehne-arena-uhd.webp differ diff --git a/workspace/assets/img/crew-buehne-garage-breit.webp b/workspace/assets/img/crew-buehne-garage-breit.webp new file mode 100644 index 00000000..b8228f15 Binary files /dev/null and b/workspace/assets/img/crew-buehne-garage-breit.webp differ diff --git a/workspace/assets/img/crew-buehne-garage-schmal.webp b/workspace/assets/img/crew-buehne-garage-schmal.webp new file mode 100644 index 00000000..7186ae15 Binary files /dev/null and b/workspace/assets/img/crew-buehne-garage-schmal.webp differ diff --git a/workspace/assets/img/crew-buehne-garage-uhd.webp b/workspace/assets/img/crew-buehne-garage-uhd.webp new file mode 100644 index 00000000..ce9a8744 Binary files /dev/null and b/workspace/assets/img/crew-buehne-garage-uhd.webp differ diff --git a/workspace/assets/img/crew-buehne-halle-breit.webp b/workspace/assets/img/crew-buehne-halle-breit.webp new file mode 100644 index 00000000..751a66d4 Binary files /dev/null and b/workspace/assets/img/crew-buehne-halle-breit.webp differ diff --git a/workspace/assets/img/crew-buehne-halle-schmal.webp b/workspace/assets/img/crew-buehne-halle-schmal.webp new file mode 100644 index 00000000..dd8233ec Binary files /dev/null and b/workspace/assets/img/crew-buehne-halle-schmal.webp differ diff --git a/workspace/assets/img/crew-buehne-halle-uhd.webp b/workspace/assets/img/crew-buehne-halle-uhd.webp new file mode 100644 index 00000000..9dc5f3fb Binary files /dev/null and b/workspace/assets/img/crew-buehne-halle-uhd.webp differ diff --git a/workspace/assets/img/crew-buehne-lounge-breit.webp b/workspace/assets/img/crew-buehne-lounge-breit.webp new file mode 100644 index 00000000..00761acb Binary files /dev/null and b/workspace/assets/img/crew-buehne-lounge-breit.webp differ diff --git a/workspace/assets/img/crew-buehne-lounge-schmal.webp b/workspace/assets/img/crew-buehne-lounge-schmal.webp new file mode 100644 index 00000000..f77323f8 Binary files /dev/null and b/workspace/assets/img/crew-buehne-lounge-schmal.webp differ diff --git a/workspace/assets/img/crew-buehne-lounge-uhd.webp b/workspace/assets/img/crew-buehne-lounge-uhd.webp new file mode 100644 index 00000000..fac8d159 Binary files /dev/null and b/workspace/assets/img/crew-buehne-lounge-uhd.webp differ diff --git a/workspace/assets/img/crew-buehne-portal-breit.webp b/workspace/assets/img/crew-buehne-portal-breit.webp new file mode 100644 index 00000000..b5808a17 Binary files /dev/null and b/workspace/assets/img/crew-buehne-portal-breit.webp differ diff --git a/workspace/assets/img/crew-buehne-portal-schmal.webp b/workspace/assets/img/crew-buehne-portal-schmal.webp new file mode 100644 index 00000000..729c82b8 Binary files /dev/null and b/workspace/assets/img/crew-buehne-portal-schmal.webp differ diff --git a/workspace/assets/img/crew-buehne-portal-uhd.webp b/workspace/assets/img/crew-buehne-portal-uhd.webp new file mode 100644 index 00000000..bcf30463 Binary files /dev/null and b/workspace/assets/img/crew-buehne-portal-uhd.webp differ diff --git a/workspace/assets/img/crew-buehne-showbuehne-breit.webp b/workspace/assets/img/crew-buehne-showbuehne-breit.webp new file mode 100644 index 00000000..4069ec73 Binary files /dev/null and b/workspace/assets/img/crew-buehne-showbuehne-breit.webp differ diff --git a/workspace/assets/img/crew-buehne-showbuehne-schmal.webp b/workspace/assets/img/crew-buehne-showbuehne-schmal.webp new file mode 100644 index 00000000..3926f533 Binary files /dev/null and b/workspace/assets/img/crew-buehne-showbuehne-schmal.webp differ diff --git a/workspace/assets/img/crew-buehne-showbuehne-uhd.webp b/workspace/assets/img/crew-buehne-showbuehne-uhd.webp new file mode 100644 index 00000000..e60f42f4 Binary files /dev/null and b/workspace/assets/img/crew-buehne-showbuehne-uhd.webp differ diff --git a/workspace/assets/img/crew-buehne-skyline-breit.webp b/workspace/assets/img/crew-buehne-skyline-breit.webp new file mode 100644 index 00000000..48ef954d Binary files /dev/null and b/workspace/assets/img/crew-buehne-skyline-breit.webp differ diff --git a/workspace/assets/img/crew-buehne-skyline-schmal.webp b/workspace/assets/img/crew-buehne-skyline-schmal.webp new file mode 100644 index 00000000..49e69d41 Binary files /dev/null and b/workspace/assets/img/crew-buehne-skyline-schmal.webp differ diff --git a/workspace/assets/img/crew-buehne-skyline-uhd.webp b/workspace/assets/img/crew-buehne-skyline-uhd.webp new file mode 100644 index 00000000..7041edc4 Binary files /dev/null and b/workspace/assets/img/crew-buehne-skyline-uhd.webp differ diff --git a/workspace/assets/img/crew-buehne-studio-breit.webp b/workspace/assets/img/crew-buehne-studio-breit.webp new file mode 100644 index 00000000..3f4a6748 Binary files /dev/null and b/workspace/assets/img/crew-buehne-studio-breit.webp differ diff --git a/workspace/assets/img/crew-buehne-studio-schmal.webp b/workspace/assets/img/crew-buehne-studio-schmal.webp new file mode 100644 index 00000000..6b30c45e Binary files /dev/null and b/workspace/assets/img/crew-buehne-studio-schmal.webp differ diff --git a/workspace/assets/img/crew-buehne-studio-uhd.webp b/workspace/assets/img/crew-buehne-studio-uhd.webp new file mode 100644 index 00000000..c7529198 Binary files /dev/null and b/workspace/assets/img/crew-buehne-studio-uhd.webp differ diff --git a/workspace/assets/img/crew-buehne-wald-breit.webp b/workspace/assets/img/crew-buehne-wald-breit.webp new file mode 100644 index 00000000..04a7d148 Binary files /dev/null and b/workspace/assets/img/crew-buehne-wald-breit.webp differ diff --git a/workspace/assets/img/crew-buehne-wald-schmal.webp b/workspace/assets/img/crew-buehne-wald-schmal.webp new file mode 100644 index 00000000..209442d2 Binary files /dev/null and b/workspace/assets/img/crew-buehne-wald-schmal.webp differ diff --git a/workspace/assets/img/crew-buehne-wald-uhd.webp b/workspace/assets/img/crew-buehne-wald-uhd.webp new file mode 100644 index 00000000..95829579 Binary files /dev/null and b/workspace/assets/img/crew-buehne-wald-uhd.webp differ diff --git a/workspace/assets/js/kopf.js b/workspace/assets/js/kopf.js index 3beeb479..7f2c69c5 100644 --- a/workspace/assets/js/kopf.js +++ b/workspace/assets/js/kopf.js @@ -54,8 +54,27 @@ Leerzeichen liesse ihn auf schmalen Handys in zwei Zeilen fallen und die Leiste mitwachsen. */ if (ich.marke) { - for (const verweis of document.querySelectorAll('.kopfleiste .marke__text .zurueck')) { - verweis.textContent = String(ich.marke).replace(/ /g, ' '); + const markeText = String(ich.marke).replace(/ /g, ' '); + /* ZWEI BAUFORMEN, UND DIE ZWEITE FEHLTE. + + Auf den Unterseiten steht in der Kopfleiste ein Rueckweg + ("Creator Workspace · Aufgaben") -- dort wird der Verweistext + ersetzt. Die STARTSEITE hat keinen; dort steht der Name als + blosser Text ("Spicy & Dogi · Creator Workspace"). + + Diese Zeile fehlte, und man sah es sofort: Im Bildschirmfoto + betroffenen Startseite stand oben links "SPICY & DOGI · + CREATOR WORKSPACE". Reitertitel, Begruessung und Symbol waren + laengst richtig -- nur die Leiste, die man als Erstes ansieht, + nicht. + + `textContent` zu setzen leert dabei die Aufteilung in + Namensteil und Raute, die weiter unten gebaut wird. Das ist + gewollt: Wo nur EIN Name steht, gibt es nichts zu trennen. */ + for (const text of document.querySelectorAll('.kopfleiste .marke__text')) { + const verweis = text.querySelector('.zurueck'); + if (verweis) { verweis.textContent = markeText; continue; } + text.textContent = markeText; } /* DER REITER UND DAS ZEICHEN GEHOEREN DAZU (10.09.2026). diff --git a/workspace/assets/js/teamlage.js b/workspace/assets/js/teamlage.js index 9026f2c7..a7cbd798 100644 --- a/workspace/assets/js/teamlage.js +++ b/workspace/assets/js/teamlage.js @@ -109,10 +109,11 @@ /* =================================================================== DIE MITGLIEDER-KACHEL AUS KAPITEL 4 (10.09.2026) - Der Blueprint nennt dort: "Name & Foto · Rolle(n)/Kategorie(n) · - Status (aktiv/pausiert) · Anzahl offener Aufgaben · Datum 'Modi - seit' · Schnellaktionen". Name, Foto und die Zahlen standen - schon; Status, "dabei seit" und die Stufe kommen hier dazu. + Der Blueprint nennt dort Name und Foto, Zustaendigkeiten, den + Status (aktiv/pausiert), die Zahl offener Aufgaben, das Datum + des Eintritts und Schnellaktionen. Name, Foto und die Zahlen + standen schon; Status, "dabei seit" und die Stufe kommen hier + dazu. ================================================================ */ const merkmale = el('div', 'tperson__merkmale'); diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 57b44376..bd7f2c7f 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -12,12 +12,12 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - + + + + + +
@@ -245,10 +245,10 @@ - - - - - + + + + +