Files
dogfather-universe/tools/workspace-symbol.mjs
T
DogFatherGitandClaude Opus 5 2872f714f3 Ein App-Symbol aus beiden Marken: Husky in der Mitte, Chili als Sockel
Filipe: "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.

Der Husky steht im Zentrum -- eine Silhouette traegt bei kleiner Groesse
am besten. Die Chili liegt als Bogen darunter, wie ein Sockel. Der
Hintergrund traegt beide Farben: Chili-Rot unten links, Dogi-Blau oben
rechts, und sie treffen sich in der Mitte -- dieselbe Klammer wie im
Schriftzug "Spicy & Dogi" in der Kopfleiste.

Der Husky wird als MASKE eingesetzt und mit Silber gefuellt: Das
Original ist schwarzweiss und waere auf dunklem Grund ein dunkler Fleck.

UNTER 64 px FAELLT DIE CHILI WEG. Sie 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.

EINMAL NACHGEBESSERT nach dem Blick aufs Bild: Die Chili sass zuerst
hoeher und schnitt dem Husky die Brust ab. Jetzt liegt sie tiefer, er
steht vollstaendig.

GEPRUEFT WIRD NICHT DIE DATEIGROESSE, sondern die Streuung der
Helligkeit. Ein Symbol, das aus einem Fehler heraus einfarbig ist, hat
dieselbe Bytezahl wie eines mit Motiv -- die beweist also nichts. Ein
leeres Feld hat keine Streuung; gemessen wurden 48 bis 69 bei einer
Untergrenze von 12, unter der das Werkzeug abbricht.

EINE FALLE MITENTSCHAERFT: `tools/app-symbole.mjs` fuehrte den Workspace
noch in seiner Liste und haette die sechs Dateien beim naechsten Lauf
stillschweigend ueberschrieben -- gleiche Namen, gleicher Ordner, kein
Fehler, nur wieder das alte Symbol. Er steht dort nicht mehr.

pruef-workspace-seiten EXIT=0 (32), pruef-assets EXIT=0.
Nebenbefund, NICHT von hier: pruef-verwaltung-app scheitert an einem
MODULE_NOT_FOUND -- gegen den alten Stand gegengeprueft, dort derselbe
Fehler. Vorbestehend, gehoert auf die offene Liste.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-08 14:19:08 +02:00

160 lines
7.6 KiB
JavaScript

/* ===================================================================
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 `<!doctype html><html><head><meta charset="utf-8"><style>
html,body{margin:0;width:${n}px;height:${n}px;overflow:hidden}
.feld{position:relative;width:${n}px;height:${n}px;
/* Der Grund: beide Marken als Verlauf, unten links Rot, oben
rechts Blau -- sie treffen sich in der Mitte. */
background:
radial-gradient(120% 120% at 22% 88%, #7d2018 0%, transparent 58%),
radial-gradient(120% 120% at 80% 16%, #1d4e73 0%, transparent 60%),
linear-gradient(38deg, #14060a 0%, #0b1320 52%, #0d1c2c 100%);
${maskable ? "" : `border-radius:${Math.round(n * 0.22)}px;`}
display:grid;place-items:center}
/* Ein feiner Lichtrand oben -- dieselbe Sprache wie die Kacheln:
Licht kommt von oben, die Kante faengt es. */
.feld::before{content:"";position:absolute;inset:0;
${maskable ? "" : `border-radius:${Math.round(n * 0.22)}px;`}
box-shadow:inset 0 ${Math.max(1, n * 0.004)}px 0 rgba(255,255,255,.18),
inset 0 0 ${n * 0.16}px rgba(0,0,0,.55)}
.ring{position:absolute;width:${ringGr}px;height:${ringGr}px;
display:grid;place-items:center}
/* DIE CHILI ALS BOGEN. Sie wird gedreht und sitzt unten -- so
laeuft sie um den Husky herum, statt ihn zu ueberdecken. */
.chili{position:absolute;width:${Math.round(ringGr * 0.92)}px;
height:${Math.round(ringGr * 0.92)}px;
background:url("${chili}") center/contain no-repeat;
transform:rotate(-16deg) translateY(${Math.round(n * 0.225)}px) scale(1.04);
filter:drop-shadow(0 ${n * 0.008}px ${n * 0.02}px rgba(0,0,0,.75))
drop-shadow(0 0 ${n * 0.03}px rgba(255,90,70,.45))}
/* DER HUSKY als Maske, gefuellt mit Silber -- das Original ist
schwarzweiss und waere auf dunklem Grund ein dunkler Fleck. */
.husky{position:relative;width:${huskyGr}px;height:${huskyGr}px;
background:linear-gradient(168deg,#ffffff 4%,#dae4ef 34%,#9fb0c4 66%,#5d7288 100%);
-webkit-mask:url("${husky}") center/contain no-repeat;
mask:url("${husky}") center/contain no-repeat;
filter:drop-shadow(0 ${n * 0.01}px ${n * 0.022}px rgba(0,0,0,.8))
drop-shadow(0 0 ${n * 0.028}px rgba(140,200,245,.5))}
</style></head><body><div class="feld"><div class="ring">
${mitChili ? '<div class="chili"></div>' : ""}
<div class="husky"></div>
</div></div></body></html>`;
}
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.`);