diff --git a/assets/img/app-symbole/workspace-180.png b/assets/img/app-symbole/workspace-180.png index 2b8e652c..c967e28a 100644 Binary files a/assets/img/app-symbole/workspace-180.png and b/assets/img/app-symbole/workspace-180.png differ diff --git a/assets/img/app-symbole/workspace-192-maskable.png b/assets/img/app-symbole/workspace-192-maskable.png index 87d12f9d..adc43d4a 100644 Binary files a/assets/img/app-symbole/workspace-192-maskable.png and b/assets/img/app-symbole/workspace-192-maskable.png differ diff --git a/assets/img/app-symbole/workspace-192.png b/assets/img/app-symbole/workspace-192.png index 55e9047a..412a46c4 100644 Binary files a/assets/img/app-symbole/workspace-192.png and b/assets/img/app-symbole/workspace-192.png differ diff --git a/assets/img/app-symbole/workspace-32.png b/assets/img/app-symbole/workspace-32.png index 15ed751c..834310b9 100644 Binary files a/assets/img/app-symbole/workspace-32.png and b/assets/img/app-symbole/workspace-32.png differ diff --git a/assets/img/app-symbole/workspace-512-maskable.png b/assets/img/app-symbole/workspace-512-maskable.png index 55a6d64c..bf3e1f9b 100644 Binary files a/assets/img/app-symbole/workspace-512-maskable.png and b/assets/img/app-symbole/workspace-512-maskable.png differ diff --git a/assets/img/app-symbole/workspace-512.png b/assets/img/app-symbole/workspace-512.png index 77990e0e..bad0668b 100644 Binary files a/assets/img/app-symbole/workspace-512.png and b/assets/img/app-symbole/workspace-512.png differ diff --git a/server/pruef-zwischenspeicher.mjs b/server/pruef-zwischenspeicher.mjs index be64e085..6d85ef5d 100644 --- a/server/pruef-zwischenspeicher.mjs +++ b/server/pruef-zwischenspeicher.mjs @@ -167,6 +167,52 @@ console.log("\n=== Jede eingebundene Datei trägt einen Stempel"); : `und keine einzige ohne (${seiten.length} Seiten geprüft)`); } +/* --------------------------------------------------------------------- + DIE APP-SYMBOLE GEHÖREN DAZU (09.09.2026) + + Sie standen mit absolutem Pfad in der Seite und fielen deshalb durch + das Muster des Stempel-Werkzeugs. Bilder werden einen Tag lang + zwischengespeichert -- ein neu gezeichnetes Symbol war damit bis zu + 24 Stunden unsichtbar. Aufgefallen ist es erst, als das Symbol am + 09.09.2026 tatsächlich neu gezeichnet wurde. + --------------------------------------------------------------------- */ +console.log("\n=== Auch die App-Symbole tragen einen Stempel"); +{ + const { readdirSync, readFileSync } = await import("node:fs"); + const { dirname } = await import("node:path"); + const { fileURLToPath } = await import("node:url"); + const WURZEL = join(dirname(fileURLToPath(import.meta.url)), "..", "workspace"); + const seiten = readdirSync(WURZEL).filter((n) => n.endsWith(".html")); + let mit = 0; const ohne = []; + for (const s of seiten) { + const text = readFileSync(join(WURZEL, s), "utf8"); + for (const t of text.matchAll( + /href="(\/assets\/img\/app-symbole\/[^"?]+)(\?[^"]*)?"/g)) { + if (t[2] && /\?v=\d+/.test(t[2])) mit++; + else ohne.push(`${s} → ${t[1]}`); + } + } + /* Die Zahl gehört in die Bedingung: Findet die Prüfung gar kein Symbol, + wäre "keines ohne Stempel" sonst wahr, ohne etwas gesehen zu haben. */ + ok(mit >= seiten.length, + `${mit} Symbolverweise mit Stempel (${seiten.length} Seiten)`); + ok(ohne.length === 0, + ohne.length ? `OHNE Stempel: ${ohne.slice(0, 4).join(", ")}` + : "und keiner ohne"); + + /* DAS MANIFEST IST DIE WICHTIGERE HÄLFTE. Aus ihm holt das + Betriebssystem das Symbol der INSTALLIERTEN App -- steht dort eine + starre Adresse, bleibt das alte Bild auf dem Startbildschirm, egal + wie oft neu ausgeliefert wird. */ + const manifest = JSON.parse(readFileSync(join(WURZEL, "app.webmanifest"), "utf8")); + const symbole = manifest.icons.map((i) => i.src); + const gestempelt = symbole.filter((s) => /\?v=\d+$/.test(s)); + ok(symbole.length >= 4 && gestempelt.length === symbole.length, + gestempelt.length === symbole.length + ? `alle ${symbole.length} Symbole im Manifest tragen einen Stempel` + : `im Manifest ohne Stempel: ${symbole.filter((s) => !/\?v=\d+$/.test(s)).join(", ")}`); +} + /* --------------------------------------------------------------------- DIE EINE STELLE, AN DER DAS AUTOMATISCHE UPDATE HÄNGT @@ -206,7 +252,11 @@ console.log("\n=== Der Versionsstempel — daran hängt das automatische Update" const seiten = readdirSync(join(WURZEL, "workspace")).filter((n) => n.endsWith(".html")); const stempel = new Map(); - for (const s of seiten) { + /* Das Manifest zählt mit: Ein Stempel, der nur in den Seiten + mitgezogen wird und im Manifest stehen bleibt, wäre genau die Art + Abweichung, die niemandem auffällt -- bis das Symbol auf dem + Startbildschirm ein halbes Jahr alt ist. */ + for (const s of [...seiten, "app.webmanifest"]) { const text = readFileSync(join(WURZEL, "workspace", s), "utf8"); for (const t of text.matchAll(/\?v=(\d+)/g)) { if (!stempel.has(t[1])) stempel.set(t[1], []); diff --git a/tools/ansicht.mjs b/tools/ansicht.mjs index 36cbaebc..829bb27d 100644 --- a/tools/ansicht.mjs +++ b/tools/ansicht.mjs @@ -65,11 +65,25 @@ await seite.fill("#code", "CODE-DOGI-0001"); await seite.click("button[type=submit]"); await seite.waitForURL("**/start.html", { timeout: 25000 }); +/* AUSSCHNITT=.kopfleiste node tools/ansicht.mjs start aufgaben + nimmt nur dieses eine Element auf. Ein 1600 px breites Bild wird beim + Ansehen heruntergerechnet -- ein 22-px-Zeichen darin ist danach ein + Fleck aus vier Punkten, und man beurteilt die Verkleinerung statt der + Sache. */ +const AUSSCHNITT = process.env.AUSSCHNITT || ""; + for (const s of SEITEN) { await seite.goto(`${BASIS}/workspace/${s}.html`, { waitUntil: "networkidle" }); await seite.waitForTimeout(1600); - await seite.screenshot({ path: `/tmp/a-${s}${BREITE < 700 ? "-handy" : ""}.png`, fullPage: false }); - console.log(`/tmp/a-${s}.png`); + const pfad = `/tmp/a-${s}${BREITE < 700 ? "-handy" : ""}.png`; + if (AUSSCHNITT) { + const teil = seite.locator(AUSSCHNITT).first(); + if (await teil.count() === 0) throw new Error(`Auf ${s}.html gibt es kein ${AUSSCHNITT}`); + await teil.screenshot({ path: pfad }); + } else { + await seite.screenshot({ path: pfad, fullPage: false }); + } + console.log(pfad); } await browser.close(); diff --git a/tools/ausschnitt.mjs b/tools/ausschnitt.mjs new file mode 100644 index 00000000..807802e1 --- /dev/null +++ b/tools/ausschnitt.mjs @@ -0,0 +1,41 @@ +/* AUSSCHNITT — schneidet ein Stueck aus einem PNG heraus und + vergroessert es, damit man Details wirklich sieht. + + Warum ein eigenes Werkzeug: Ein Bildschirmfoto von 1600 px wird beim + Ansehen heruntergerechnet. Ein 22-px-Zeichen darin ist danach ein + Fleck aus vier Pixeln -- man beurteilt dann die Verkleinerung, nicht + das Zeichen. Hier wird der Ausschnitt VOR dem Ansehen vergroessert, + und zwar ohne Glaettung: So sieht man genau die Pixel, die auch der + Bildschirm zeigt. + + Aufruf: node tools/ausschnitt.mjs [faktor] [ziel] +*/ +import { readFileSync, writeFileSync } from "node:fs"; + +const [datei, x, y, b, h, faktor = "4", ziel = "/tmp/ausschnitt.png"] = process.argv.slice(2); +if (!datei || !b) { + console.error("Aufruf: node tools/ausschnitt.mjs [faktor] [ziel]"); + process.exit(2); +} +const roh = readFileSync(datei).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(); + +const bb = Math.round(Number(b) * Number(faktor)); +const hh = Math.round(Number(h) * Number(faktor)); +await seite.setViewportSize({ width: bb, height: hh }); +await seite.setContent(`
`, + { waitUntil: "networkidle" }); +writeFileSync(ziel, await seite.screenshot()); +await browser.close(); +console.log(ziel); diff --git a/tools/workspace-stempel.mjs b/tools/workspace-stempel.mjs index db1c0839..631f135f 100644 --- a/tools/workspace-stempel.mjs +++ b/tools/workspace-stempel.mjs @@ -35,6 +35,22 @@ const STEMPEL = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}${p(d. vorhandenen Stempel, damit wiederholte Laeufe nicht anhaengen. */ const MUSTER = /(href|src)="(assets\/(?:css|js)\/[A-Za-z0-9._-]+)(?:\?v=\d+)?"/g; +/* ZWEITES MUSTER: DIE APP-SYMBOLE (09.09.2026). + + Sie stehen mit absolutem Pfad in der Seite (`/assets/img/app-symbole/…`) + und fielen deshalb durch das Muster oben durch. Der Server gibt Bildern + einen Tag Haltbarkeit -- ein neu gezeichnetes Symbol war damit bis zu + 24 Stunden lang unsichtbar, obwohl es ausgeliefert wurde. Genau die + Luecke, die der Stempel eigentlich schliessen soll, nur fuer Bilder + statt fuer Stilvorlagen. + + Die Reiter-Kennung und das Apple-Symbol bekommen deshalb denselben + Stempel wie alles andere. Dass es DERSELBE ist, ist wichtig: Die + Pruefung `pruef-zwischenspeicher` verlangt genau einen Stempel im + ganzen Haus, und eine zweite Zahl waere sofort rot. */ +const MUSTER_SYMBOL = + /(href)="(\/assets\/img\/app-symbole\/[A-Za-z0-9._-]+)(?:\?v=\d+)?"/g; + let dateien = 0; let stellen = 0; @@ -43,10 +59,15 @@ for (const name of readdirSync(ORDNER)) { const pfad = join(ORDNER, name); const alt = readFileSync(pfad, "utf8"); let n = 0; - const neu = alt.replace(MUSTER, (_, was, datei) => { - n += 1; - return `${was}="${datei}?v=${STEMPEL}"`; - }); + const neu = alt + .replace(MUSTER, (_, was, datei) => { + n += 1; + return `${was}="${datei}?v=${STEMPEL}"`; + }) + .replace(MUSTER_SYMBOL, (_, was, datei) => { + n += 1; + return `${was}="${datei}?v=${STEMPEL}"`; + }); if (neu !== alt) { writeFileSync(pfad, neu); dateien += 1; @@ -55,5 +76,38 @@ for (const name of readdirSync(ORDNER)) { } } +/* DAS MANIFEST GEHOERT DAZU (09.09.2026). + + Aus ihm holt sich das Betriebssystem das Symbol der installierten App. + Ohne Stempel zeigen Android und Windows nach einer Neuzeichnung + weiter das alte Bild -- die Adresse hat sich ja nicht geaendert, und + ein einmal installiertes Symbol wird nicht von sich aus nachgeladen. + Das Manifest selbst wird ohne Haltbarkeit ausgeliefert, kommt also + frisch an; nur die Adressen darin waren starr. + + Derselbe Stempel wie in den Seiten -- `pruef-zwischenspeicher` + verlangt im ganzen Haus genau eine Zahl und wuerde eine zweite sofort + melden. */ +const MANIFEST = join(ORDNER, "app.webmanifest"); +const MUSTER_MANIFEST = + /("src":\s*")(\/assets\/img\/app-symbole\/[A-Za-z0-9._-]+)(?:\?v=\d+)?(")/g; +{ + const alt = readFileSync(MANIFEST, "utf8"); + let n = 0; + const neu = alt.replace(MUSTER_MANIFEST, (_, vor, datei, nach) => { + n += 1; + return `${vor}${datei}?v=${STEMPEL}${nach}`; + }); + /* Findet das Muster nichts, ist das ein FEHLER und kein Achselzucken: + Dann hat jemand das Manifest umgebaut, und ab da laeuft das Symbol + still auf dem alten Stand weiter. */ + if (n === 0) { + console.error("FEHLER: im Manifest keine Symboladresse gefunden -- Muster pruefen."); + process.exit(1); + } + if (neu !== alt) { writeFileSync(MANIFEST, neu); dateien += 1; stellen += n; } + console.log(` app.webmanifest ${n} Verweise`); +} + console.log(`\nStempel ${STEMPEL} auf ${stellen} Verweise in ${dateien} Dateien gesetzt.`); if (!dateien) console.log("Nichts geaendert -- stimmt das Muster noch?"); diff --git a/tools/workspace-symbol.mjs b/tools/workspace-symbol.mjs index 5b128c87..6928d779 100644 --- a/tools/workspace-symbol.mjs +++ b/tools/workspace-symbol.mjs @@ -10,21 +10,35 @@ 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. + DIE LOESUNG: Der Husky ist das Zeichen, die Chili seine Farbe. Er + steht gross und mittig, sie liegt klein darunter -- ein Hauptmotiv + und ein Beiklang, nicht zwei gleich laute Stimmen. - 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. + ÜBERARBEITET AM 09.09.2026 (Filipe: "kannst du bitte das logo wenn + man es installiert auf pc oder handy ... noch perfektionnieren. ich + finde es könnte noch besser aussehen"). Was falsch war und warum: - 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. + 1. DER HUSKY WAR EIN FLECK. Er lag als Maske auf einer Silberflaeche. + Von einer Maske zaehlt nur der Alphakanal, und die Vorlage ist + rundum freigestellt -- also war das Ergebnis eine geschlossene + Flaeche in Hundeform: kein Auge, keine Schnauze, kein Ohrinneres. + Jetzt liegt die Originalzeichnung im Mischmodus "screen" ueber + einer stahlblauen Silhouette. Das Fell bleibt dunkel, das Gesicht + kommt heraus. Aus dem Fleck wird wieder ein Hund. + 2. DIE CHILI LIEF UEBER DEN FANG. Als Bogen unten herum gedreht kreuzte + ihr Stiel die Schnauze. Jetzt ist sie gespiegelt (Stiel nach rechts, + weg vom Kopf), deutlich kleiner und liegt hinter dem Hals. + 3. DER HALS ENDETE IN EINER GERADEN KANTE. Die Vorlage ist unten + angeschnitten; als Symbol sah das nach Fehler aus. Eine zweite + Maske blendet ihn nach unten aus, statt ihn abzuschneiden. + 4. DER GRUND WAR MATSCHIG. Rot und Blau trafen sich diagonal genau in + der Mitte -- dort, wo der Kopf steht. Jetzt liegt das kalte Licht + oben (beim Kopf), das warme unten (bei der Chili), und dazwischen + ist ruhiges Nachtblau. + + MASKABLE: Android schneidet bis zu 20 % ringsum weg. Dafuer wird genau + EINE Zahl kleiner -- die Groesse der Gruppe, in der Kopf und Chili in + Prozent sitzen. Nicht sechs einzelne Werte, die auseinanderlaufen. Aufrufen mit: node tools/workspace-symbol.mjs =================================================================== */ @@ -36,7 +50,11 @@ 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"); +/* Das Ziel ist umlenkbar (SYMBOL_ZIEL=/tmp/probe). Ein Symbol beurteilt + man nicht aus dem Quelltext, sondern indem man es ansieht -- und dafuer + darf man die sechs echten Dateien nicht bei jedem Versuch ueberschreiben. + Ohne die Variable bleibt alles wie bisher. */ +const ZIEL = process.env.SYMBOL_ZIEL || join(WURZEL, "assets", "img", "app-symbole"); for (const q of [HUSKY, CHILI]) { if (!existsSync(q)) { console.error(`Fehlt: ${q}`); process.exit(1); } @@ -53,55 +71,134 @@ const GROESSEN = [ { 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; +/* `weglassen` ist NUR fuer die Gegenprobe da: "zeichnung" oder "chili" + blendet genau einen Bestandteil aus. Verglichen wird danach das + erzeugte Bild mit und ohne -- sind beide gleich, war der Bestandteil + nie zu sehen. Eine Pruefung, die nur "es steht etwas drauf" sagt, + haette den Fleck von gestern anstandslos durchgewunken. */ +function html(n, maskable, husky, chili, weglassen = "") { + /* KLEIN = alles unter 64 px. Dort faellt die GESICHTSZEICHNUNG weg, + nicht die Chili. Achtfach vergroessert nebeneinandergelegt: Mit + Zeichnung ist der Kopf bei 32 px ein dunkler Fleck, als reine + Silhouette ist er eindeutig ein Hund. Die Chili dagegen ist gerade + klein am nuetzlichsten -- im Reiterband erkennt man ein Zeichen + zuerst an der Farbe, und Rot hat sonst nichts hier. */ + const klein = n < 64; + const rand = Math.round(n * 0.22); + /* ALLES STECKT IN EINER GRUPPE. Kopf und Chili sitzen darin in + Prozent. Fuer die maskable-Fassung wird deshalb genau EINE Zahl + kleiner -- nicht sechs einzelne, die auseinanderlaufen koennen. + 0,72 ist nachgemessen (mit eingezeichnetem Kreis angesehen): Damit + liegt alles Wesentliche im Sicherheitskreis mit 80 % Durchmesser, + den Android als einziges garantiert. */ + const g = Math.round(n * (maskable ? 0.72 : 0.94)); + const ecke = maskable ? "" : `border-radius:${rand}px;`; + + /* ALLE REGELN TRAGEN DENSELBEN VORSATZ `.gruppe > i`. + + Nicht aus Ordnungsliebe: Waehrend des Bauens stand hier `.chili` + ohne Vorsatz -- und `.gruppe > i` (Klasse + Typ) ist spezifischer + als `.chili` (nur Klasse). Die Drehung der Chili wurde dadurch + stillschweigend von der allgemeinen Regel ueberschrieben. Zwei + Regeln fuer dieselbe Eigenschaft, die spaetere verliert trotzdem: + genau der Fehler, der im Haus schon mehrfach Zeit gekostet hat. + Gleicher Vorsatz ueberall heisst: die Reihenfolge entscheidet, und + die ist sichtbar. */ return `
- ${mitChili ? '
' : ""} -
+ /* KLEIN OHNE SCHLAGSCHATTEN. Bei 32 px ist der Schatten so breit + wie die Chili selbst -- nachgesehen: aus dem roten Zeichen wurde + ein dunkler Schmier. Klein wird sie stattdessen eine Spur heller + gestellt, weil davon nur noch die Farbe uebrig bleibt. */ + filter:${klein + ? "saturate(1.06) brightness(1.08)" + : `saturate(.92) + drop-shadow(0 ${Math.max(1, Math.round(n * .004))}px ${Math.round(n * .01)}px rgba(0,0,0,.7)) + drop-shadow(0 0 ${Math.round(n * .02)}px rgba(255,86,64,.34))`}} + + /* DER KOPF -- hier steckt die eigentliche Verbesserung. + + Vorher war er eine MASKE. Von einer Maske zaehlt nur der + Alphakanal, und die Vorlage ist rundum freigestellt: Uebrig blieb + eine geschlossene Flaeche -- ein heller Fleck in Hundeform, ohne + Auge, ohne Schnauze, ohne Ohrinneres. Bei 22 px in der Kopfleiste + war genau das der Grund, warum man nichts erkannt hat. + + Jetzt zwei Lagen: unten die Silhouette in Stahlblau, darueber die + ORIGINALZEICHNUNG im Mischmodus "screen". Screen laesst Schwarz + stehen (das Fell bleibt stahlblau) und hebt Weiss heraus (Gesicht, + Ohrinneres, Auge). Aus der Silhouette wird wieder ein Gesicht -- + ohne eine einzige neu gezeichnete Linie. + + Warum nicht "mask-mode: luminance"? Chromium kann das (geprueft: + CSS.supports meldet true). Aber der Weg ueber zwei Masken ist + empfindlich: Eine Kurzschreibweise "-webkit-mask" in einer + spezifischeren Regel setzt "mask-mode" stillschweigend auf "alpha" + zurueck, und man sieht wieder den Fleck -- genau das ist beim + Bauen passiert, mit richtig aussehendem Quelltext. Der Mischmodus + hat diese Falle nicht. + + ZWEITE MASKE, DER AUSLAUF: Die Vorlage ist am unteren Rand + abgeschnitten, der Hals endet gerade. Als Symbol sah das aus wie + ein Fehler beim Freistellen. Der Verlauf blendet den Hals nach + unten aus, statt ihn abzuschneiden. */ + .gruppe > i.kopf{width:${klein ? 76 : 88}%;height:${klein ? 76 : 88}%; + top:${klein ? 44 : 43}%; + z-index:2;isolation:isolate; + background:linear-gradient(162deg,#5486b2 0%,#25507a 44%,#0e2b45 100%); + ${klein + /* KLEIN OHNE AUSLAUF. Bei 32 px frisst der Verlauf gerade die + Schnauze weg -- nachgesehen und 8-fach vergroessert + verglichen: mit Auslauf ist es ein Berg, ohne Auslauf ein + Hundekopf. Die gerade Kante am Hals ist dort drei Bildpunkte + hoch und faellt nicht auf. */ + ? `-webkit-mask:url("${husky}") center/contain no-repeat; + mask:url("${husky}") center/contain no-repeat;` + : `-webkit-mask:url("${husky}") center/contain no-repeat, + linear-gradient(180deg,#000 58%, transparent 86%); + -webkit-mask-composite:source-in; + mask:url("${husky}") center/contain no-repeat, + linear-gradient(180deg,#000 58%, transparent 86%); + mask-composite:intersect;`} + filter:drop-shadow(0 ${Math.round(n * .006)}px ${Math.round(n * .016)}px rgba(0,0,0,.7)) + drop-shadow(0 0 ${Math.round(n * .022)}px rgba(140,200,245,.34))} + .gruppe > i.kopf .zeichnung{position:absolute;inset:0;display:block; + background:url("${husky}") center/contain no-repeat; + mix-blend-mode:screen; + /* Ohne den Daempfer wird das Weiss der Vorlage zu reinem Weiss -- + auf dunklem Grund blendet das. Augenschonend heisst hier: hell + genug zum Erkennen, nicht heller. */ + filter:brightness(.88)} + .gruppe > i.kopf.flach{background:linear-gradient(168deg,#ffffff 4%,#e4eef8 38%,#adc0d4 100%)} + .gruppe > i.kopf.flach .zeichnung{display:none} + ${weglassen === "zeichnung" ? ".gruppe > i.kopf .zeichnung{display:none}" : ""} + ${weglassen === "chili" ? ".gruppe > i.chili{display:none}" : ""} +
+ +
`; } @@ -113,38 +210,96 @@ if (!existsSync(ZIEL)) mkdirSync(ZIEL, { recursive: true }); const husky = alsDaten(HUSKY), chili = alsDaten(CHILI); let gebaut = 0; -const schwach = []; +const maengel = []; -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); +/* Eine Fassung aufnehmen. `weglassen` steuert die Gegenprobe. */ +async function aufnehmen(n, maskable, weglassen = "") { + await seite.setViewportSize({ width: n, height: n }); + await seite.setContent(html(n, maskable, husky, chili, weglassen), + { waitUntil: "networkidle" }); + return await seite.screenshot({ omitBackground: false }); +} - /* 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(); +/* Streuung der Helligkeit: Ein leeres Feld hat keine. Das beweist, dass + ueberhaupt etwas draufsteht -- mehr nicht. Die Bytezahl beweist auch + das nicht, ein einfarbiges PNG ist genauso gross wie ein volles. */ +async function streuungVon(bild) { + return await seite.evaluate(async (b64) => { + const b = new Image(); b.src = "data:image/png;base64," + b64; await b.decode(); const c = document.createElement("canvas"); - c.width = bild.width; c.height = bild.height; - const g2 = c.getContext("2d"); g2.drawImage(bild, 0, 0); + c.width = b.width; c.height = b.height; + const g2 = c.getContext("2d"); g2.drawImage(b, 0, 0); const d = g2.getImageData(0, 0, c.width, c.height).data; - let s = 0, s2 = 0, n = 0; + let s = 0, s2 = 0, m = 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++; + s += h; s2 += h * h; m++; } - return Math.sqrt(s2 / n - (s / n) ** 2); + return Math.sqrt(s2 / m - (s / m) ** 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)})`); +/* Anteil der Bildpunkte, die sich zwischen zwei Aufnahmen sichtbar + unterscheiden. Acht Stufen Abstand, damit Kantenglaettung allein den + Wert nicht hochtreibt. */ +async function unterschied(a, b) { + return await seite.evaluate(async ([x, y]) => { + const laden = async (b64) => { + const b = new Image(); b.src = "data:image/png;base64," + b64; await b.decode(); + const c = document.createElement("canvas"); + c.width = b.width; c.height = b.height; + c.getContext("2d").drawImage(b, 0, 0); + return c.getContext("2d").getImageData(0, 0, c.width, c.height).data; + }; + const [p, q] = [await laden(x), await laden(y)]; + let anders = 0, m = 0; + for (let i = 0; i < p.length; i += 4) { + const w = Math.max(Math.abs(p[i] - q[i]), Math.abs(p[i + 1] - q[i + 1]), + Math.abs(p[i + 2] - q[i + 2]), Math.abs(p[i + 3] - q[i + 3])); + if (w > 8) anders++; + m++; + } + return anders / m; + }, [a.toString("base64"), b.toString("base64")]); +} + +for (const g of GROESSEN) { + const bild = await aufnehmen(g.n, !!g.maskable); + writeFileSync(join(ZIEL, `workspace-${g.endung}.png`), bild); gebaut++; + + const streuung = await streuungVon(bild); + if (streuung < 12) maengel.push(`${g.endung}: kaum Zeichnung (Streuung ${streuung.toFixed(1)})`); + + /* DIE ZWEI GEGENPROBEN. Beide messen nicht "sieht gut aus", sondern + "ist ueberhaupt da": Wenn das Bild ohne die Zeichnung genauso + aussieht wie mit ihr, dann war die Zeichnung nie zu sehen -- und + genau das war der Fehler der alten Fassung, die trotzdem gruen war. */ + const klein = g.n < 64; + const ohneZeichnung = await unterschied(bild, await aufnehmen(g.n, !!g.maskable, "zeichnung")); + const ohneChili = await unterschied(bild, await aufnehmen(g.n, !!g.maskable, "chili")); + + if (klein) { + /* Unter 64 px ist die schlichte Silhouette Absicht. Geprueft wird, + dass sie es auch bleibt -- sonst waere die Regel bloss ein + Kommentar, der beim naechsten Umbau stillschweigend kippt. */ + if (ohneZeichnung > 0.001) maengel.push( + `${g.endung}: klein soll ohne Gesichtszeichnung sein`); + } else { + /* Das Gesicht bedeckt gemessen rund ein Siebtel der Flaeche; 4 % + ist der Boden, unter den es nur durch einen Fehler faellt. */ + if (ohneZeichnung < 0.04) maengel.push( + `${g.endung}: das Gesicht kommt nicht durch (${(ohneZeichnung * 100).toFixed(1)} %)`); + } + /* Die Chili gehoert in JEDE Fassung, gerade in die kleine. Sie nimmt + gemessen 4 bis 7 % ein; 1 % ist der Boden. */ + if (ohneChili < 0.01) maengel.push( + `${g.endung}: die Chili fehlt (${(ohneChili * 100).toFixed(1)} %)`); + + console.log(` workspace-${g.endung}.png`.padEnd(32) + + `${(bild.length / 1024).toFixed(1)} KB · Streuung ${streuung.toFixed(1)}` + + ` · Gesicht ${(ohneZeichnung * 100).toFixed(1)} %` + + ` · Chili ${(ohneChili * 100).toFixed(1)} %`); } await browser.close(); @@ -152,8 +307,8 @@ await browser.close(); 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); +if (maengel.length) { + console.error("\nFEHLER:\n " + maengel.join("\n ")); process.exit(1); } -console.log(`\n${gebaut} Symbole erzeugt, alle mit erkennbarer Zeichnung.`); +console.log(`\n${gebaut} Symbole erzeugt. Husky-Gesicht und Chili sind in jeder` + + ` grossen Fassung nachweisbar sichtbar, die kleine bleibt bewusst schlicht.`); diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index cb52481a..a8cd70b4 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", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609091823", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609091823", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609091823", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609091823", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/css/start.css b/workspace/assets/css/start.css index a4a5edb3..b639988a 100644 --- a/workspace/assets/css/start.css +++ b/workspace/assets/css/start.css @@ -1842,7 +1842,12 @@ body.start > main { position: relative; z-index: 1; } Klasse tragen ALLE 18 Seiten, sie kennzeichnet nicht die Startseite, sondern den Grundstil. Gesehen im Bildschirmfoto der Unterseite -- der Selektor las sich völlig plausibel. */ -.kopfleiste:hover .marke::before { filter: brightness(1.2); } +/* Auch hier gilt: `filter` ergänzt nichts, es ersetzt. Ohne den Schein + in dieser Zeile verliert die Chili ihn beim Überfahren – und wird + dabei ausgerechnet flacher, obwohl sie heller werden soll. */ +.kopfleiste:hover .marke::before { + filter: saturate(1.12) brightness(1.18) drop-shadow(0 0 8px rgba(255, 92, 68, .62)); +} /* KEIN Farbverlauf IM Text. @@ -1966,9 +1971,17 @@ body.start > main { position: relative; z-index: 1; } /* Das Zeichen davor bekommt einen eigenen Träger: runder Rahmen, ein Hauch Fläche, weicher Schein. Aus einer aufgeklebten Form wird damit ein Wappen. */ +/* DER SCHEIN IST MIT DEM ZEICHEN MITGEWANDERT (09.09.2026). + + Hier stand ein BLAUER Schein – aus der Zeit, als vor dem Schriftzug + noch der Husky stand. Seit dem 08.09. steht dort die Chili, der Schein + ist blau geblieben. Ein blauer Hof um ein rotes Zeichen nimmt ihm + genau die Farbe, wegen der es dort steht; neunfach vergrößert sah die + Chili darum stumpf und schmutzig aus. Jetzt ist er warm – dieselbe + Farbe, die die Chili selbst hat. */ .kopfleiste .marke::before { padding: 0; - filter: drop-shadow(0 0 7px rgba(63, 189, 245, .45)); + filter: saturate(1.06) brightness(1.06) drop-shadow(0 0 6px rgba(255, 92, 68, .5)); } .kopfleiste .marke { /* Nur so breit wie ihr Inhalt -- sonst waere die Pille eine Leiste. @@ -6000,21 +6013,76 @@ body[data-fremde-sicht="ja"]::after { Grundstil, nicht die Startseite. Der Selektor las sich richtig und war es nicht; gesehen erst im Bildschirmfoto der Unterseite. - Als MASKE und nicht als Bild: Der Husky ist schwarzweiss gezeichnet - und waere auf dunklem Grund ein dunkler Fleck. Von einer Maske zaehlt - nur die Silhouette, und die wird hier mit Silber und Babyblau - gefuellt -- seine beiden Farben aus dem Rollenschema. Die Augenfarbe - klingt im Verlauf an, ohne dass man bei 22 px Augen zeichnen muesste. + =================================================================== */ + +/* =================================================================== + AUS DEM FLECK WIRD WIEDER EIN HUND (09.09.2026) + + Filipe: "kannst du bitte das logo wenn man es installiert auf pc oder + handy und oben rechts in der leiste noch perfektionnieren. ich finde + es könnte noch besser aussehen bitte." + + ER HATTE RECHT, UND DER GRUND WAR EINE MASKE. Hier lag der Husky als + Maske auf einer Silberfläche. Von einer Maske zählt nur der + Alphakanal, und die Vorlage ist rundum freigestellt: Übrig blieb eine + GESCHLOSSENE FLÄCHE in Hundeform – kein Auge, keine Schnauze, kein + Ohrinneres. Dazu lief der Verlauf unten in volles Babyblau, und weil + die Vorlage am Hals gerade abgeschnitten ist, entstand daraus ein + blauer BALKEN. Zusammen: ein Fleck. + + JETZT ZWEI LAGEN IN EINEM ELEMENT. Unten ein stahlblauer Verlauf, + darüber dieselbe Zeichnung im Mischmodus "screen". Screen lässt + Schwarz stehen (das Fell bleibt dunkelblau) und hebt Weiß heraus + (Gesicht, Ohrinneres, Auge). Beides sind HINTERGRUND-Lagen desselben + Elements – ein Pseudo-Element kann kein Kind haben, `background-blend-mode` + dagegen schon. Die Maske klebt weiterhin die Silhouette aus. + + DAS IST DASSELBE REZEPT WIE IM APP-SYMBOL (tools/workspace-symbol.mjs, + gleiche Farben, gleicher Dämpfer .88). Genau darum ging es Filipe: das + installierte Symbol UND das Zeichen in der Leiste, ein Zeichen. + + WIE ICH MICH DABEI FAST SELBST BETROGEN HÄTTE, zum Merken: + Ich hatte die Entwürfe zuerst mit `transform: scale(12)` vergrößert + angesehen und daraus geschlossen, eine Zweiton-Zeichnung sei bei 22 px + Matsch. Falsch – eine CSS-Vergrößerung lässt den Browser das Zeichen + NEU rechnen; ich habe 264 px beurteilt und geglaubt, es seien 22. + Richtig ist: bei echten 22 px aufnehmen, danach die PNG-Datei + vergrößern (tools/ausschnitt.mjs). So gemessen trägt das Gesicht + sehr wohl – man sieht Schnauze, Ohren und Auge. Eine Lupe, die + nachrechnet, ist keine Lupe. + + KEIN AUSLAUF NACH UNTEN, anders als im App-Symbol: Dort blendet eine + zweite Maske den geraden Halsschnitt aus. Bei 22 px frisst derselbe + Verlauf das Kinn weg – nachgemessen und nebeneinandergelegt. Hier + braucht es ihn auch nicht: Das Fell ist jetzt dunkelblau statt + silbern und läuft von selbst in die dunkle Leiste aus. =================================================================== */ .kopfleiste .marke::after { content: ""; flex: none; width: 22px; height: 22px; - background: linear-gradient(160deg, #eaf1f8 8%, #b9c6d6 46%, var(--dogi-haupt) 100%); + background-image: + url("../img/marke-husky.webp"), + linear-gradient(162deg, #5486b2 0%, #25507a 44%, #0e2b45 100%); + background-size: contain, auto; + background-position: center; + background-repeat: no-repeat; + background-blend-mode: screen; -webkit-mask: url("../img/marke-husky.webp") center / contain no-repeat; mask: url("../img/marke-husky.webp") center / contain no-repeat; + /* Ohne den Dämpfer wird das Weiß der Vorlage zu reinem Weiß – auf + dunklem Grund blendet das. Augenschonend heißt hier: hell genug zum + Erkennen, nicht heller. Der Schein ringsum trägt das Babyblau der + Rolle, das vorher in der Füllung saß. */ + filter: brightness(.88) drop-shadow(0 0 6px rgba(95, 189, 255, .5)); transition: filter var(--tempo); } -.kopfleiste:hover .marke::after { filter: brightness(1.2); } +/* DER SCHEIN MUSS BEIM ÜBERFAHREN MITGENANNT WERDEN. `filter` ist keine + Liste, die sich ergänzt – wer hier nur `brightness` schreibt, löscht + den Schein. Vorher stand genau das da, und das Zeichen wurde beim + Überfahren flacher statt heller. */ +.kopfleiste:hover .marke::after { + filter: brightness(1.02) drop-shadow(0 0 8px rgba(95, 189, 255, .62)); +} /* =================================================================== DIE BEGRUESSUNG SOLL MOTIVIEREN (08.09.2026) diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 2b9dbded..53a0c4ae 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. --> - - - - - - + + + + + + @@ -231,10 +231,10 @@ - - - - - + + + + + diff --git a/workspace/automation.html b/workspace/automation.html index d909f21e..6e05ffb8 100644 --- a/workspace/automation.html +++ b/workspace/automation.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -140,10 +140,10 @@ - - - - - + + + + + diff --git a/workspace/bereich.html b/workspace/bereich.html index 1cc87cba..5255ce4f 100644 --- a/workspace/bereich.html +++ b/workspace/bereich.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -149,14 +149,14 @@ - - - - - - - - - + + + + + + + + + diff --git a/workspace/calls.html b/workspace/calls.html index ca731974..d312b14b 100644 --- a/workspace/calls.html +++ b/workspace/calls.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -82,11 +82,11 @@ - - - - - - + + + + + + diff --git a/workspace/chat.html b/workspace/chat.html index 278678b3..38205997 100644 --- a/workspace/chat.html +++ b/workspace/chat.html @@ -12,19 +12,19 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - - + + + @@ -247,10 +247,10 @@ - - - - - + + + + + diff --git a/workspace/content.html b/workspace/content.html index 2cce3efa..0cabe346 100644 --- a/workspace/content.html +++ b/workspace/content.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -172,12 +172,12 @@ - - - - - - - + + + + + + + diff --git a/workspace/dateien.html b/workspace/dateien.html index ea049dfe..a0a0ede6 100644 --- a/workspace/dateien.html +++ b/workspace/dateien.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -101,10 +101,10 @@ - - - - - + + + + + diff --git a/workspace/index.html b/workspace/index.html index f2312f82..95f0efdb 100644 --- a/workspace/index.html +++ b/workspace/index.html @@ -30,9 +30,9 @@ - - - + + + @@ -401,6 +401,6 @@
- + diff --git a/workspace/kalender.html b/workspace/kalender.html index 65d0ae74..89f760cd 100644 --- a/workspace/kalender.html +++ b/workspace/kalender.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -370,11 +370,11 @@ - - - - - - + + + + + + diff --git a/workspace/leistung.html b/workspace/leistung.html index 4980a3f0..e77c0b71 100644 --- a/workspace/leistung.html +++ b/workspace/leistung.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -217,10 +217,10 @@ - - - - - + + + + + diff --git a/workspace/personen.html b/workspace/personen.html index 3fb7d11f..bbd509fa 100644 --- a/workspace/personen.html +++ b/workspace/personen.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -335,10 +335,10 @@ - - - - - + + + + + diff --git a/workspace/profil.html b/workspace/profil.html index 839496fb..5d440bce 100644 --- a/workspace/profil.html +++ b/workspace/profil.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -207,11 +207,11 @@ - - - - - - + + + + + + diff --git a/workspace/report.html b/workspace/report.html index a9b69e2a..92add7a0 100644 --- a/workspace/report.html +++ b/workspace/report.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -99,11 +99,11 @@ - - - - - - + + + + + + diff --git a/workspace/scouting.html b/workspace/scouting.html index 9b1303ff..954e35be 100644 --- a/workspace/scouting.html +++ b/workspace/scouting.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -132,10 +132,10 @@
- - - - - + + + + + diff --git a/workspace/start.html b/workspace/start.html index 7ef5588e..26fce70f 100644 --- a/workspace/start.html +++ b/workspace/start.html @@ -12,14 +12,14 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - + + @@ -667,10 +667,10 @@

- - - - - + + + + + diff --git a/workspace/startcheck.html b/workspace/startcheck.html index be226337..784869a4 100644 --- a/workspace/startcheck.html +++ b/workspace/startcheck.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -78,11 +78,11 @@ - - - - - - + + + + + + diff --git a/workspace/steckbrief.html b/workspace/steckbrief.html index b3623d75..a17def82 100644 --- a/workspace/steckbrief.html +++ b/workspace/steckbrief.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -119,10 +119,10 @@ - - - - - + + + + + diff --git a/workspace/team.html b/workspace/team.html index 8256c344..516f87f7 100644 --- a/workspace/team.html +++ b/workspace/team.html @@ -7,13 +7,13 @@ - - - - - - - + + + + + + + @@ -103,10 +103,10 @@ - - - - - + + + + + diff --git a/workspace/uebersicht.html b/workspace/uebersicht.html index 5c5c00e2..9d7891df 100644 --- a/workspace/uebersicht.html +++ b/workspace/uebersicht.html @@ -12,15 +12,15 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - - + + + @@ -142,11 +142,11 @@ - + - - - - + + + + diff --git a/workspace/wissen.html b/workspace/wissen.html index 6835ba0f..d932efad 100644 --- a/workspace/wissen.html +++ b/workspace/wissen.html @@ -12,13 +12,13 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + @@ -183,10 +183,10 @@ - - - - - + + + + +