/* =================================================================== Das App-Symbol des Workspace: Husky UND Chili in einem Zeichen. Filipe (08.09.2026): "ich will dass du da eine geile mischung machst von diesen zwei logo ... mach was ultra krass geiles draus bitte." DIE AUFGABE IST NICHT "zwei Bilder nebeneinander". Ein App-Symbol steht bei 32 px im Browserreiter und bei 192 px auf dem Startbildschirm. Zwei vollstaendige Logos nebeneinander ergeben dort zwei unlesbare Haelften. Gebraucht wird EINE Form, in der beide vorkommen. DIE LOESUNG: Der Husky steht im Zentrum, die Chili bildet den BOGEN um ihn -- wie der Ring eines Siegels, nur nicht geschlossen. Das ist dieselbe Anordnung wie beim Spicy-Media-Original (Zeichen im Kreis) und laesst dem Husky die Mitte, wo eine Silhouette bei kleiner Groesse noch traegt. DER HINTERGRUND traegt beide Farben: ein diagonaler Verlauf von Chili-Rot (unten links) nach Dogi-Blau (oben rechts), abgedunkelt, damit der silberne Husky darauf steht statt darin zu verschwinden. Wo sich die beiden treffen, liegt die Mitte -- genauso wie im Schriftzug "Spicy & Dogi" in der Kopfleiste. MASKABLE: Android schneidet bis zu 20 % ringsum weg. Fuer diese Fassungen sitzt alles enger zusammen und der Bogen wird kuerzer -- sonst waere von der Chili nach dem Beschnitt nur ein Reststueck da. Aufrufen mit: node tools/workspace-symbol.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 HUSKY = join(WURZEL, "workspace", "assets", "img", "marke-husky.webp"); const CHILI = join(WURZEL, "workspace", "assets", "img", "marke-spicy.webp"); const ZIEL = join(WURZEL, "assets", "img", "app-symbole"); for (const q of [HUSKY, CHILI]) { if (!existsSync(q)) { console.error(`Fehlt: ${q}`); process.exit(1); } } const alsDaten = (p) => "data:image/webp;base64," + readFileSync(p).toString("base64"); /* Die Groessen, die Manifest und HTML tatsaechlich anfordern. */ const GROESSEN = [ { n: 512, endung: "512" }, { n: 192, endung: "192" }, { n: 512, endung: "512-maskable", maskable: true }, { n: 192, endung: "192-maskable", maskable: true }, { n: 180, endung: "180" }, { n: 32, endung: "32" }, ]; function html(n, maskable, husky, chili) { /* Bei einem maskable-Symbol darf nur der innere Kreis mit 80 % Durchmesser als sicher gelten. Alles Wichtige sitzt deshalb enger. */ const platz = maskable ? 0.62 : 0.80; const huskyGr = Math.round(n * platz * 0.72); const ringGr = Math.round(n * platz); /* Unter 64 px faellt der Chili-Bogen weg: Er waere dort ein verwaschener Fleck und wuerde die Husky-Silhouette anfressen. Ein Symbol, das klein noch erkennbar ist, ist mehr wert als eins, das alle Bestandteile zeigt und dabei zu Matsch wird. */ const mitChili = n >= 64; return `
${mitChili ? '
' : ""}
`; } 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(); if (!existsSync(ZIEL)) mkdirSync(ZIEL, { recursive: true }); const husky = alsDaten(HUSKY), chili = alsDaten(CHILI); let gebaut = 0; const schwach = []; for (const g of GROESSEN) { await seite.setViewportSize({ width: g.n, height: g.n }); await seite.setContent(html(g.n, !!g.maskable, husky, chili), { waitUntil: "networkidle" }); const bild = await seite.screenshot({ omitBackground: false }); const datei = join(ZIEL, `workspace-${g.endung}.png`); writeFileSync(datei, bild); /* NACHSEHEN, OB WIRKLICH ETWAS DRAUFSTEHT. Ein Symbol, das aus einem Fehler heraus einfarbig ist, hat dieselbe Dateigroesse wie eines mit Motiv -- die Bytezahl beweist gar nichts. Gemessen wird die Streuung der Helligkeit: Ein leeres Feld hat keine. */ const streuung = await seite.evaluate(async (b64) => { const bild = new Image(); bild.src = "data:image/png;base64," + b64; await bild.decode(); const c = document.createElement("canvas"); c.width = bild.width; c.height = bild.height; const g2 = c.getContext("2d"); g2.drawImage(bild, 0, 0); const d = g2.getImageData(0, 0, c.width, c.height).data; let s = 0, s2 = 0, n = 0; for (let i = 0; i < d.length; i += 4) { const h = 0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2]; s += h; s2 += h * h; n++; } return Math.sqrt(s2 / n - (s / n) ** 2); }, bild.toString("base64")); console.log(` workspace-${g.endung}.png`.padEnd(34) + `${(bild.length / 1024).toFixed(1)} KB ยท Streuung ${streuung.toFixed(1)}`); if (streuung < 12) schwach.push(`${g.endung} (${streuung.toFixed(1)})`); gebaut++; } await browser.close(); /* Die Zahlen gehoeren in die Bedingung, nicht nur in den Bericht. */ if (gebaut !== GROESSEN.length) { console.error(`\nFEHLER: ${gebaut} von ${GROESSEN.length} erzeugt.`); process.exit(1); } if (schwach.length) { console.error(`\nFEHLER: kaum Zeichnung in ${schwach.join(", ")} -- da steht vermutlich` + ` nichts drauf.`); process.exit(1); } console.log(`\n${gebaut} Symbole erzeugt, alle mit erkennbarer Zeichnung.`);