diff --git a/assets/img/app-symbole/analyse-180.png b/assets/img/app-symbole/analyse-180.png
index db1bfabf..018ac048 100644
Binary files a/assets/img/app-symbole/analyse-180.png and b/assets/img/app-symbole/analyse-180.png differ
diff --git a/assets/img/app-symbole/cloud-180.png b/assets/img/app-symbole/cloud-180.png
index e22e4643..91388f51 100644
Binary files a/assets/img/app-symbole/cloud-180.png and b/assets/img/app-symbole/cloud-180.png differ
diff --git a/assets/img/app-symbole/git-180.png b/assets/img/app-symbole/git-180.png
index adbd8090..9d1c816b 100644
Binary files a/assets/img/app-symbole/git-180.png and b/assets/img/app-symbole/git-180.png differ
diff --git a/assets/img/app-symbole/portal-180.png b/assets/img/app-symbole/portal-180.png
index 2b17a7a6..dd1193c8 100644
Binary files a/assets/img/app-symbole/portal-180.png and b/assets/img/app-symbole/portal-180.png differ
diff --git a/assets/img/app-symbole/universe-180.png b/assets/img/app-symbole/universe-180.png
index d4abeb66..97fb492c 100644
Binary files a/assets/img/app-symbole/universe-180.png and b/assets/img/app-symbole/universe-180.png differ
diff --git a/assets/img/app-symbole/verwaltung-180.png b/assets/img/app-symbole/verwaltung-180.png
index ee086c8b..6db569bf 100644
Binary files a/assets/img/app-symbole/verwaltung-180.png and b/assets/img/app-symbole/verwaltung-180.png differ
diff --git a/assets/img/app-symbole/wdverwalt-180.png b/assets/img/app-symbole/wdverwalt-180.png
index 03a7eb23..d64787c1 100644
Binary files a/assets/img/app-symbole/wdverwalt-180.png and b/assets/img/app-symbole/wdverwalt-180.png differ
diff --git a/assets/img/app-symbole/webdesign-180.png b/assets/img/app-symbole/webdesign-180.png
index 47725094..9920373c 100644
Binary files a/assets/img/app-symbole/webdesign-180.png and b/assets/img/app-symbole/webdesign-180.png differ
diff --git a/assets/img/app-symbole/workspace-180.png b/assets/img/app-symbole/workspace-180.png
index 0151c311..33c3593f 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/zocker-180.png b/assets/img/app-symbole/zocker-180.png
index 98c931ee..21f53bf6 100644
Binary files a/assets/img/app-symbole/zocker-180.png and b/assets/img/app-symbole/zocker-180.png differ
diff --git a/server/pruef-struktur.mjs b/server/pruef-struktur.mjs
index 1d7b8c40..4d416f71 100644
--- a/server/pruef-struktur.mjs
+++ b/server/pruef-struktur.mjs
@@ -341,6 +341,66 @@ console.log(`
: `alle ${hinterWand.length} stehen in der Ausnahmeliste`);
}
+/* ---------- 10. Sind die Symbole fuer JEDES Geraet richtig gebaut? -------
+
+ PC und Handy holen ihr Symbol an drei verschiedenen Stellen, und
+ jede stellt andere Anforderungen:
+
+ Browser-Reiter das normale Symbol, darf runde Ecken haben
+ Android die maskable-Fassung, davon wird aussen bis zu
+ ein Fuenftel weggeschnitten
+ iPhone -- und iOS rundet
+ SELBST ab und fuellt alles Durchsichtige mit
+ SCHWARZ
+
+ Der letzte Punkt ist der, der auffiel ("auf dem einen klappt es und
+ auf dem anderen nicht"): Ein apple-touch-icon mit eigener Rundung
+ bekommt auf dem iPhone schwarze Zipfel. Auf dem PC sieht dieselbe
+ Datei tadellos aus -- deshalb faellt es nur auf einem der beiden
+ Geraete auf, und deshalb muss es gemessen werden statt angesehen.
+
+ Geprueft werden die Dateinamen, nicht die Bilder selbst -- die
+ Bildpruefung braucht einen Browser und laeuft in pruef-symbole.mjs.
+ Hier geht es darum, dass ueberhaupt fuer jede App und jedes Geraet
+ eine Datei da ist. */
+console.log(`
+=== Symbole fuer alle Geraete ===`);
+{
+ const symOrdner = join(REPO, "assets", "img", "app-symbole");
+ if (!existsSync(symOrdner)) {
+ ok(false, "der Ordner assets/img/app-symbole fehlt");
+ } else {
+ const da = readdirSync(symOrdner);
+ const apps = [...new Set(da.map((n) => n.replace(/-(\d+)(-maskable)?\.png$/, "")))]
+ .filter((n) => n.endsWith(".png") === false);
+ ok(apps.length >= 8, `${apps.length} Apps mit eigenen Symbolen`);
+
+ const noetig = ["192", "512", "192-maskable", "512-maskable", "180", "32"];
+ const fehlend = [];
+ for (const a of apps) {
+ for (const g of noetig) {
+ if (!da.includes(`${a}-${g}.png`)) fehlend.push(`${a}-${g}.png`);
+ }
+ }
+ ok(fehlend.length === 0,
+ fehlend.length ? `fehlende Symbole: ${fehlend.slice(0, 6).join(", ")}`
+ : `alle ${apps.length * noetig.length} Symbole vorhanden `
+ + `(Reiter, Android-maskable, iPhone)`);
+
+ /* Jede Seite muss BEIDES nennen: das Reiter-Symbol und das fuers
+ iPhone. Fehlt das zweite, nimmt iOS einen Bildschirmausschnitt
+ der Seite -- unbrauchbar und unerkennbar. */
+ let ohneApple = [];
+ for (const seite of seiten) {
+ const text = readFileSync(join(WS, seite), "utf8");
+ if (!/rel="apple-touch-icon"/i.test(text)) ohneApple.push(seite);
+ }
+ ok(ohneApple.length === 0,
+ ohneApple.length ? `ohne apple-touch-icon (iPhone nimmt sonst einen Screenshot): ${ohneApple.join(", ")}`
+ : `alle ${seiten.length} Workspace-Seiten nennen ein iPhone-Symbol`);
+ }
+}
+
/* ---------- Gegenprobe --------------------------------------------------- */
console.log(`\n=== Gegenprobe (muss anschlagen) ===`);
diff --git a/tools/app-symbole.mjs b/tools/app-symbole.mjs
index 950a6470..1d02ddd4 100644
--- a/tools/app-symbole.mjs
+++ b/tools/app-symbole.mjs
@@ -317,7 +317,7 @@ export const STIL_JE_APP = {
* Jede Kachel bekommt deshalb einen eigenen Namen aus Stil und App.
* Damit lässt sich beliebig viel davon in ein Dokument legen, ohne
* dass sich zwei ins Gehege kommen. */
-export function symbolTeil(f, stilName, n, { maskable = false, logoDaten } = {}) {
+export function symbolTeil(f, stilName, n, { maskable = false, apple = false, logoDaten } = {}) {
const stil = STILE[stilName];
const logoFilter = typeof stil.logo === "function" ? stil.logo(f) : stil.logo;
@@ -345,8 +345,23 @@ export function symbolTeil(f, stilName, n, { maskable = false, logoDaten } = {})
78 % ist der Anteil, der bei jeder gaengigen Maske heil bleibt
(Kreis, Quadrat mit runden Ecken, Tropfen). */
- const radius = maskable ? 0 : 22.4; // 22,4 % ist die Apple-Rundung
- const k = `s-${stilName}-${f.schluessel}${maskable ? "-m" : ""}`;
+ /* APPLE-TOUCH-ICON: quadratisch, undurchsichtig, OHNE eigene Rundung.
+
+ BEFUND 05.09.2026 ("auf dem einen klappt es und auf dem anderen
+ nicht"). iOS holt sein Symbol NICHT aus dem Manifest, sondern aus
+ . Und es behandelt diese Datei anders
+ als jeder andere Browser: Es rundet die Ecken SELBST ab und fuellt
+ alles Durchsichtige mit SCHWARZ.
+
+ Unser Symbol brachte seine eigene Rundung mit -- also durchsichtige
+ Ecken. Auf dem iPhone wurden daraus schwarze Zipfel, die dann ein
+ zweites Mal beschnitten wurden. Auf dem PC faellt das nicht auf,
+ weil Windows und Chrome mit Transparenz umgehen koennen. Genau
+ daher der Unterschied zwischen den beiden Geraeten.
+
+ Deshalb hier: randvoll, eckig, deckend. Die Rundung macht iOS. */
+ const radius = (maskable || apple) ? 0 : 22.4; // 22,4 % ist die Apple-Rundung
+ const k = `s-${stilName}-${f.schluessel}${maskable ? "-m" : ""}${apple ? "-a" : ""}`;
/* Das eigentliche Symbol ist bei maskable kleiner als die Datei. */
const innen = maskable ? Math.round(n * 0.78) : n;
@@ -527,7 +542,7 @@ const GROESSEN = [
{ n: 512, maskable: false, endung: "512" },
{ n: 192, maskable: true, endung: "192-maskable" },
{ n: 512, maskable: true, endung: "512-maskable" },
- { n: 180, maskable: false, endung: "180" },
+ { n: 180, maskable: false, apple: true, endung: "180" },
{ n: 32, maskable: false, endung: "32" },
];
@@ -551,7 +566,7 @@ async function bauen() {
}
for (const g of GROESSEN) {
await seite.setViewportSize({ width: g.n, height: g.n });
- await seite.setContent(symbolHtml(f, stilName, g.n, { maskable: g.maskable, logoDaten }),
+ await seite.setContent(symbolHtml(f, stilName, g.n, { maskable: g.maskable, apple: g.apple, logoDaten }),
{ waitUntil: "networkidle" });
await seite.waitForTimeout(60);
const datei = join(ziel, `${f.schluessel}-${g.endung}.png`);