diff --git a/assets/css/main.css b/assets/css/main.css index 9e71c9bb..4b2eca4f 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -4723,3 +4723,11 @@ h1, h2, h3 { text-shadow: 0 1px 2px rgba(0, 0, 0, .45); } .btn::after, .btn-primary::after, .btn-outline::after { display: none; } h1, h2, h3 { text-shadow: none; } } + +/* Eine Kachel, die noch nichts dahinter hat (03.09.2026). + Sie sieht aus wie die anderen, verspricht aber nichts: kein Zeiger, + kein Anheben beim Ueberfahren. Der Unterschied ist klein und genau + deshalb richtig -- man merkt ihn erst, wenn man darauf zeigt, und + dann weiss man Bescheid, ohne dass es jemand hinschreiben muss. */ +.karte--bald { cursor: default; } +.karte--bald:hover { transform: none; border-color: var(--border); box-shadow: var(--shadow); } diff --git a/links.html b/links.html index 0996b0d1..929eef94 100644 --- a/links.html +++ b/links.html @@ -107,11 +107,24 @@

Discord

Nur auf Anfrage

- + +
🛒

Merchandise / Shop

Folgt in Kürze

-
+
📝

Bewerben

diff --git a/server/abnahme-installiert.png b/server/abnahme-installiert.png new file mode 100644 index 00000000..87e64839 Binary files /dev/null and b/server/abnahme-installiert.png differ diff --git a/server/pruef-ampel.png b/server/pruef-ampel.png index 2ec06450..2be1d134 100644 Binary files a/server/pruef-ampel.png and b/server/pruef-ampel.png differ diff --git a/server/pruef-aufgabenbrett.png b/server/pruef-aufgabenbrett.png index 9544dcab..179dde79 100644 Binary files a/server/pruef-aufgabenbrett.png and b/server/pruef-aufgabenbrett.png differ diff --git a/server/pruef-checkliste.png b/server/pruef-checkliste.png new file mode 100644 index 00000000..90c413f8 Binary files /dev/null and b/server/pruef-checkliste.png differ diff --git a/server/pruef-code.png b/server/pruef-code.png index 62480d07..83d1a588 100644 Binary files a/server/pruef-code.png and b/server/pruef-code.png differ diff --git a/server/pruef-content-computer.png b/server/pruef-content-computer.png new file mode 100644 index 00000000..644cb4ad Binary files /dev/null and b/server/pruef-content-computer.png differ diff --git a/server/pruef-content-creator.png b/server/pruef-content-creator.png new file mode 100644 index 00000000..0a58fbaa Binary files /dev/null and b/server/pruef-content-creator.png differ diff --git a/server/pruef-content-handy.png b/server/pruef-content-handy.png new file mode 100644 index 00000000..1a7f1755 Binary files /dev/null and b/server/pruef-content-handy.png differ diff --git a/server/pruef-freie-namen.png b/server/pruef-freie-namen.png index e43dadaa..78e37324 100644 Binary files a/server/pruef-freie-namen.png and b/server/pruef-freie-namen.png differ diff --git a/server/pruef-live-abnahme.mjs b/server/pruef-live-abnahme.mjs new file mode 100644 index 00000000..f9a570f2 --- /dev/null +++ b/server/pruef-live-abnahme.mjs @@ -0,0 +1,221 @@ +/* ===================================================================== + ABNAHME VOR DER VORSTELLUNG (03.09.2026) + + Alle anderen Prüfungen laufen gegen einen Server auf diesem Rechner. + Diese hier geht gegen die ECHTE Domain -- so, wie sie morgen jemand + im Publikum aufruft. + + Der Unterschied ist nicht theoretisch: Zwischen "läuft bei mir" und + "läuft im Netz" liegen Caddy, Cloudflare, der Cache-Stempel, das + Zertifikat und der Dienst-Neustart. Genau dort ist am 18.08. und am + 22.08.2026 schon zweimal etwas hängengeblieben, das lokal tadellos + war (siehe CLAUDE.md, "Grundregel für ALLE Websites"). + + Geprüft wird in DREI Gestalten, weil das Publikum sie alle benutzt: + + RECHNER 1440 px + HANDY 390 px, mit Fingerbedienung + INSTALLIERT als App vom Startbildschirm -- ohne Adresszeile, ohne + Zurück-Knopf des Browsers. Dort fällt auf, was man mit + Browserleiste nie merkt. + + Gemessen wird je Seite: + * Fehler in der Konsole + * fehlgeschlagene Anfragen (4xx/5xx) + * ob etwas seitlich aus dem Bild ragt + * tote Verweise (href="#" oder leer) + * fehlende Bilder + * ob die Seite überhaupt Inhalt hat + ===================================================================== */ + +const BASIS = process.env.ABNAHME_BASIS || "https://dogfather-universe.com"; + +const SEITEN = [ + "/", "/dogfather.html", "/casper.html", "/hasidog.html", "/vanvan.html", + "/community.html", "/streamer.html", "/supporter.html", "/abonnieren.html", + "/bewerben.html", "/bewerben-creator.html", "/bewerben-scout.html", + "/bewerben-modi.html", "/bewerben-kooperation.html", + "/kontakt.html", "/werte.html", "/team-scouts.html", "/team-modis.html", + "/stimmen.html", "/links.html", "/rabattcodes.html", "/streamplan.html", + "/zeitreise.html", "/agb.html", "/webdesign", "/404.html", +]; + +let fehler = 0; +let geprueft = 0; +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; +const befunde = []; + +const EGAL = [ + /favicon/i, + /net::ERR_ABORTED/i, + /Download the React DevTools/i, +]; +const istEgal = (t) => EGAL.some((r) => r.test(t)); + +const { chromium } = await import( + "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); +const browser = await chromium.launch(); + +async function rundgang(name, kontextOptionen, seiten) { + const kontext = await browser.newContext(kontextOptionen); + const seite = await kontext.newPage(); + const konsole = []; + const schlecht = []; + seite.on("console", (m) => { if (m.type() === "error" && !istEgal(m.text())) konsole.push(m.text()); }); + seite.on("pageerror", (e) => konsole.push("Absturz: " + e.message)); + seite.on("response", (a) => { + if (a.status() >= 400 && !istEgal(a.url())) { + schlecht.push(`${a.status()} ${a.url().replace(BASIS, "")}`); + } + }); + + console.log(`\n===== ${name} =====`); + for (const pfad of seiten) { + konsole.length = 0; schlecht.length = 0; + let antwort = null; + try { + antwort = await seite.goto(BASIS + pfad, { waitUntil: "networkidle", timeout: 40000 }); + } catch (e) { + ok(false, `${pfad.padEnd(28)} nicht erreichbar (${e.message.slice(0, 40)})`); + befunde.push({ name, pfad, was: "nicht erreichbar" }); + continue; + } + await seite.waitForTimeout(500); + + const z = await seite.evaluate(() => { + const sichtbar = (e) => { + const k = e.getBoundingClientRect(); + return k.width > 0 && k.height > 0 && getComputedStyle(e).visibility !== "hidden"; + }; + const tote = [...document.querySelectorAll("a[href]")].filter(sichtbar) + .filter((a) => { const h = a.getAttribute("href"); return !h || h === "#"; }) + .map((a) => a.textContent.trim().slice(0, 30)); + /* Nur SICHTBARE Bilder mit einer Quelle. Die Seite hat + Platzhalter ( ohne src), die JavaScript erst + füllt, wenn es etwas zu zeigen gibt -- die sind kein Fehler, + sondern der Normalzustand. Beim ersten Lauf hat meine Prüfung + genau die gemeldet und damit zwei Seiten falsch beschuldigt. */ + const bilderKaputt = [...document.querySelectorAll("img")] + .filter((b) => sichtbar(b) && (b.getAttribute("src") || "").trim()) + .filter((b) => b.complete && b.naturalWidth === 0) + .map((b) => (b.getAttribute("src") || "").slice(-40)); + return { + tote, bilderKaputt, + laenge: (document.body.innerText || "").trim().length, + ueber: document.documentElement.scrollWidth - document.documentElement.clientWidth, + titel: document.title, + }; + }); + + const probleme = []; + if (antwort && antwort.status() >= 400) probleme.push(`Seite antwortet ${antwort.status()}`); + if (konsole.length) probleme.push("Konsole: " + konsole.slice(0, 2).join(" | ").slice(0, 130)); + if (schlecht.length) probleme.push("Server: " + [...new Set(schlecht)].slice(0, 3).join(", ")); + if (z.tote.length) probleme.push("tote Verweise: " + z.tote.slice(0, 3).join(", ")); + if (z.bilderKaputt.length) probleme.push("fehlende Bilder: " + z.bilderKaputt.slice(0, 2).join(", ")); + if (z.ueber > 0) probleme.push(`ragt ${z.ueber}px heraus`); + if (z.laenge < 200 && pfad !== "/404.html") probleme.push(`fast kein Inhalt (${z.laenge})`); + if (!z.titel) probleme.push("kein Seitentitel"); + + ok(!probleme.length, `${pfad.padEnd(28)} ${probleme.join(" · ")}`); + if (probleme.length) befunde.push({ name, pfad, was: probleme.join(" · ") }); + } + await kontext.close(); +} + +/* ---------- 1. Rechner ---------------------------------------------- */ +await rundgang("RECHNER (1440 px)", { viewport: { width: 1440, height: 950 } }, SEITEN); + +/* ---------- 2. Handy ------------------------------------------------- */ +await rundgang("HANDY (390 px, Finger)", { + viewport: { width: 390, height: 844 }, + isMobile: true, hasTouch: true, deviceScaleFactor: 3, + userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 " + + "(KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1", +}, SEITEN); + +/* ---------- 3. Installiert ------------------------------------------- */ +/* Als App vom Startbildschirm: kein Adressfeld, kein Zurück-Knopf des + Browsers. Wer sich dort verläuft, kommt nicht mehr heraus -- deshalb + ist diese Gestalt die unbarmherzigste. */ +console.log("\n===== INSTALLIERT (App-Ansicht) ====="); + +const app = await browser.newContext({ + viewport: { width: 390, height: 844 }, + isMobile: true, hasTouch: true, deviceScaleFactor: 3, +}); +const appSeite = await app.newPage(); + +/* ERST auf die Domain gehen, DANN das Manifest holen. + + Beim ersten Lauf stand die Probe noch auf about:blank und holte von + dort -- das ist für den Browser eine fremde Herkunft, und er lehnt + ab. Gemeldet wurde "manifest.json nicht ladbar", während curl + gleichzeitig 200 lieferte. Die Prüfung hat also nicht die Seite + geprüft, sondern sich selbst. */ +await appSeite.goto(BASIS + "/", { waitUntil: "domcontentloaded", timeout: 40000 }); + +const manifest = await appSeite.evaluate(async (basis) => { + const a = await fetch(basis + "/manifest.json"); + if (!a.ok) return { fehler: a.status }; + return a.json(); +}, BASIS).catch(() => ({ fehler: "nicht ladbar" })); + +ok(!manifest.fehler, `manifest.json wird ausgeliefert${manifest.fehler ? " (" + manifest.fehler + ")" : ""}`); +ok(!!manifest.name || !!manifest.short_name, `die App hat einen Namen ("${manifest.short_name || manifest.name}")`); +ok(manifest.display === "standalone" || manifest.display === "fullscreen", + `sie startet ohne Browserleiste (display: ${manifest.display})`); +ok(!!manifest.start_url, `sie weiss, wo sie startet (${manifest.start_url})`); +ok(Array.isArray(manifest.icons) && manifest.icons.length > 0, + `sie hat ein Symbol für den Startbildschirm (${manifest.icons?.length || 0})`); + +/* Jedes Symbol muss wirklich da sein -- ein fehlendes fällt erst auf, + wenn es als graues Viereck auf dem Startbildschirm klebt. */ +for (const symbol of (manifest.icons || []).slice(0, 4)) { + const pfad = symbol.src.startsWith("http") ? symbol.src : BASIS + "/" + symbol.src.replace(/^\//, ""); + const status = await appSeite.evaluate(async (u) => { + try { const a = await fetch(u); return a.status; } catch { return 0; } + }, pfad); + ok(status === 200, ` Symbol ${symbol.sizes || ""} vorhanden (${status})`); +} + +await appSeite.goto(BASIS + (manifest.start_url || "/"), { waitUntil: "networkidle", timeout: 40000 }); +await appSeite.waitForTimeout(1500); + +const dienst = await appSeite.evaluate(async () => { + if (!("serviceWorker" in navigator)) return { da: false, grund: "nicht unterstützt" }; + const r = await navigator.serviceWorker.getRegistration(); + return { da: !!r, umfang: r?.scope || null, aktiv: !!r?.active }; +}); +ok(dienst.da, `der Hintergrunddienst ist angemeldet${dienst.umfang ? " (" + dienst.umfang + ")" : ""}`); +ok(dienst.aktiv, "und aktiv – die App startet auch ohne Netz"); + +const appStand = await appSeite.evaluate(() => ({ + ueber: document.documentElement.scrollWidth - document.documentElement.clientWidth, + laenge: (document.body.innerText || "").trim().length, + /* In der App gibt es keinen Zurück-Knopf des Browsers. Es MUSS also + auf der Seite selbst ein Weg zurück oder ein Menü geben. */ + wege: document.querySelectorAll("a[href]").length, +})); +ok(appStand.ueber <= 0, `nichts ragt seitlich heraus (${appStand.ueber}px)`); +ok(appStand.laenge > 200, `die Startseite hat Inhalt (${appStand.laenge} Zeichen)`); +ok(appStand.wege > 3, + `es gibt Wege weiter (${appStand.wege} Verweise) – in der App gibt es keinen Browser-Zurück`); + +await appSeite.screenshot({ path: "abnahme-installiert.png", fullPage: false }); +await app.close(); + +/* ---------- Abschluss ------------------------------------------------ */ +console.log(""); +const ERWARTET = SEITEN.length * 2 + 12; +ok(geprueft >= ERWARTET, + `es sind ${geprueft} Prüfungen gelaufen (erwartet mindestens ${ERWARTET})`); + +if (befunde.length) { + console.log("\n--- WAS MORGEN AUFFALLEN WÜRDE ---"); + for (const b of befunde) console.log(` ${b.name} · ${b.pfad}\n ${b.was}`); +} + +await browser.close(); +console.log(`\n${fehler ? "FEHLER: " + fehler : "Alles in Ordnung"} — ${geprueft} Prüfungen`); +process.exit(fehler ? 1 : 0); diff --git a/server/pruef-merkzeichen.png b/server/pruef-merkzeichen.png new file mode 100644 index 00000000..ac53fea5 Binary files /dev/null and b/server/pruef-merkzeichen.png differ diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index e02fffd7..04e2b0f1 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -7,9 +7,9 @@ - - - + + + @@ -152,9 +152,9 @@ - - - - + + + + diff --git a/workspace/automation.html b/workspace/automation.html index 7773e66d..8fb93638 100644 --- a/workspace/automation.html +++ b/workspace/automation.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -132,9 +132,9 @@ - - - - + + + + diff --git a/workspace/bereich.html b/workspace/bereich.html index b3914d11..f00d2a9b 100644 --- a/workspace/bereich.html +++ b/workspace/bereich.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -115,13 +115,13 @@ - - - - - - - - + + + + + + + + diff --git a/workspace/calls.html b/workspace/calls.html index 2942f448..7276bd5a 100644 --- a/workspace/calls.html +++ b/workspace/calls.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -74,10 +74,10 @@ - - - - - + + + + + diff --git a/workspace/content.html b/workspace/content.html index 09ff7deb..4f80e306 100644 --- a/workspace/content.html +++ b/workspace/content.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -164,11 +164,11 @@ - - - - - - + + + + + + diff --git a/workspace/dateien.html b/workspace/dateien.html index bb2b8a16..c670f1d6 100644 --- a/workspace/dateien.html +++ b/workspace/dateien.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -93,9 +93,9 @@ - - - - + + + + diff --git a/workspace/index.html b/workspace/index.html index 26b8c8fa..d011dbfe 100644 --- a/workspace/index.html +++ b/workspace/index.html @@ -25,7 +25,7 @@ type="image/webp" media="(max-width: 899px)" /> - + @@ -143,6 +143,6 @@

- + diff --git a/workspace/kalender.html b/workspace/kalender.html index 77e1affc..8bc523a0 100644 --- a/workspace/kalender.html +++ b/workspace/kalender.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -178,9 +178,9 @@ - - - - + + + + diff --git a/workspace/personen.html b/workspace/personen.html index 2a27107e..51878ee0 100644 --- a/workspace/personen.html +++ b/workspace/personen.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -121,9 +121,9 @@ - - - - + + + + diff --git a/workspace/profil.html b/workspace/profil.html index a76455fb..25eebd3d 100644 --- a/workspace/profil.html +++ b/workspace/profil.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -193,10 +193,10 @@ - - - - - + + + + + diff --git a/workspace/report.html b/workspace/report.html index 9d8dec80..042bd08d 100644 --- a/workspace/report.html +++ b/workspace/report.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -91,10 +91,10 @@ - - - - - + + + + + diff --git a/workspace/scouting.html b/workspace/scouting.html index d88dbf6d..139799f7 100644 --- a/workspace/scouting.html +++ b/workspace/scouting.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -124,9 +124,9 @@ - - - - + + + + diff --git a/workspace/start.html b/workspace/start.html index 04c0bcfe..5e06ffa4 100644 --- a/workspace/start.html +++ b/workspace/start.html @@ -7,8 +7,8 @@ - - + + @@ -99,9 +99,9 @@

- - - - + + + + diff --git a/workspace/startcheck.html b/workspace/startcheck.html index 82893c4e..6e6cf351 100644 --- a/workspace/startcheck.html +++ b/workspace/startcheck.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -70,10 +70,10 @@ - - - - - + + + + + diff --git a/workspace/steckbrief.html b/workspace/steckbrief.html index 4d4d08e6..d5b6ca4a 100644 --- a/workspace/steckbrief.html +++ b/workspace/steckbrief.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -111,9 +111,9 @@ - - - - + + + + diff --git a/workspace/uebersicht.html b/workspace/uebersicht.html index 56ded9f2..d6a37465 100644 --- a/workspace/uebersicht.html +++ b/workspace/uebersicht.html @@ -7,9 +7,9 @@ - - - + + + @@ -57,9 +57,9 @@ - + - - + + diff --git a/workspace/wissen.html b/workspace/wissen.html index 657f5c01..e3bb0728 100644 --- a/workspace/wissen.html +++ b/workspace/wissen.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -175,9 +175,9 @@ - - - - + + + +