Handy durchgeprueft, Lichtfarbe je Kachel, haengendes Licht behoben

DAS LICHT BLIEB HAENGEN -- vier Ursachen, jede einzeln behoben:

1. EIN WETTLAUF. Zwischen dem Anmelden eines Bildaufbaus und seinem
   Ablauf konnte der Zeiger die Karte laengst verlassen haben. Dann hatte
   pointerout schon aufgeraeumt, und der Bildaufbau schaltete das Licht
   gleich wieder AN. Der haeufigste Fall und der unauffaelligste.
2. UEBER EINE LUECKE VERLASSEN. Wer eine Karte nicht ueber eine
   Nachbarkarte verliess, sondern ueber den Zwischenraum, loeste kein
   Ereignis aus, das die alte Karte kannte.
3. GESCROLLT, OHNE DIE MAUS ZU BEWEGEN. Die Karte wandert unter dem
   stehenden Zeiger weg -- es kommt gar kein Zeigerereignis. Jetzt wird
   beim Scrollen nachgesehen, ob die beleuchtete Karte noch unter dem
   Zeiger liegt.
4. FENSTER ODER TAB VERLASSEN. Auch dort kommt nichts mehr.

Es gibt jetzt genau EINE beleuchtete Karte -- mehr kann es nicht geben,
es gibt ja nur einen Zeiger. Beim Wechsel geht die alte aus, bevor die
neue angeht.

DIE FARBE GEHOERT ZUR KACHEL. Auf der Wissensseite leuchteten alle sechs
Welten violett, weil die Farbe dort --w heisst und der ganze uebrige
Workspace --ton benutzt. Das Licht griff auf --ton zu, fand nichts und
nahm den Farbton der SEITE. Dieselbe Luecke bei den Aufgaben-Spalten
(--sfarbe), den Kalenderkarten (--afarbe) und den Kalenderzeilen
(--zfarbe). Alle vier setzen jetzt --ton mit.

Nachgewiesen an echten Bildpunkten, nicht am Quelltext: Der Zeiger wird
auf die Aufgaben-Kachel gesetzt (dort muss Rot ueberwiegen: 85 zu 22)
und auf die Kalender-Kachel (dort Blau: 71 zu 13). Ein
Zeichenkettenvergleich haette das nicht gekonnt -- der Browser rechnet
color-mix() aus und schreibt je nach Fassung rgb(), color() oder oklab()
zurueck.

DAS HANDY, komplett durchgeprueft: neue pruef-handy.mjs faehrt alle
fuenfzehn Seiten auf DREI echten Geraetegroessen ab (320 px iPhone SE,
390 px iPhone, 412 px Android) und misst fuenf Dinge, die am Rechner
unsichtbar sind. Gefunden und behoben:

  * ZWEI ECHTE UEBERLAEUFE (startcheck +27 px, automation +33 px). Die
    Seite liess sich waagerecht schieben -- auf einem Telefon der
    schlimmste Fehler. Ursachen: zwoelf Raster mit festem Mindestmass
    (minmax(280px, 1fr) kann nicht schrumpfen -> min(280px, 100%)),
    zwoelf feste Mindestbreiten an Auswahlfeldern, und ein "flex: none"
    an den Stufen-Knoepfen, das jedes Schrumpfen verbot. "width: 100%"
    allein reichte dort nicht.
  * BERUEHRZIELE unter 24 px (WCAG 2.2, Kriterium 2.5.8). Auswahlfelder
    waren 18 bis 22 px hoch. Jetzt 44 px -- die Empfehlung von Apple und
    Google, und der Daumen ist nun einmal breiter als ein Mauszeiger.
    Nebenbei behebt die Schriftgroesse 16 px das Hineinzoomen von iOS.
  * SCHRIFT unter 11,7 px an 52 Stellen. Gesucht wurden sie nicht von
    Hand -- das waere ein Ratespiel gewesen und haette die Haelfte
    uebersehen -- sondern durch Durchsuchen der Stildateien nach
    font-size unter 0,73rem.

ZWEI FEHLER IN DEN EIGENEN PRUEFUNGEN gefunden und behoben: Die
Schriftregel griff zuerst gar nicht (start.css wird VOR den Seitenstilen
geladen, bei gleicher Staerke gewinnt die spaetere -- jetzt mit
body.start qualifiziert), und die Lichtpruefung scrollte 600 px und
stellte nicht zurueck, sodass die folgende Pruefung ins Leere zeigte.

GEGENPROBE gemacht: Mit absichtlich eingebauten Fehlern (8-px-Schrift,
500 px breiter Inhalt) meldet die Handypruefung sofort Rot. Eine
Pruefung, die immer bestaetigt, bestaetigt nichts.

Alle achtzehn Pruefungen laufen gruen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-01 14:48:48 +02:00
co-authored by Claude Opus 5
parent 41ffb91688
commit 1fe443e59c
48 changed files with 834 additions and 149 deletions
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 KiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 459 KiB

After

Width:  |  Height:  |  Size: 711 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 KiB

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 756 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 889 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 790 KiB

After

Width:  |  Height:  |  Size: 806 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

After

Width:  |  Height:  |  Size: 325 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 533 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

+303
View File
@@ -0,0 +1,303 @@
/* ===================================================================
Die Handy-Pruefung.
Auf einem Telefon geht anderes kaputt als am Rechner, und man sieht
es beim Entwickeln nie: Ein Knopf steht 3 px aus dem Bild, eine
Ueberschrift ist auf halbem Wort abgeschnitten, ein Feld ist so
schmal, dass man es nicht trifft. Alles davon faellt erst auf, wenn
jemand das Geraet in der Hand hat.
Geprueft wird auf DREI echten Geraetegroessen, jede Seite einzeln:
1. Nichts steht ueber waagerechtes Scrollen ist auf einem Handy
der schlimmste Fehler -- die Seite ruckelt
und man verliert den Rand
2. Kein Text abgeschnitten gemessen wird die tatsaechliche
Textbreite gegen die verfuegbare, nicht
"sieht ganz aus"
3. Beruehrziele gross genug 24x24 CSS-Pixel ist das Mindestmass
(WCAG 2.2, Kriterium 2.5.8). Darunter
trifft man mit dem Daumen daneben
4. Nichts ueberlappt zwei Bedienelemente duerfen sich nicht
ueberdecken -- man traefe das falsche
5. Keine Fehler in der Konsole
=================================================================== */
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-handy-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = "4198";
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4198";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 400_000).unref?.();
let fehler = 0;
const ok = (b, t) => { console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {});
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
for (let i = 0; i < 60; i++) {
try {
const p = new DatabaseSync(process.env.WORKSPACE_DB);
try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); }
} catch { await new Promise((r) => setTimeout(r, 250)); }
}
/* Echte Daten statt leerer Seiten: Eine leere Liste kann nicht
ueberlaufen. Geprueft wird mit LANGEN Namen und vollen Listen --
genau dort bricht es. */
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const jetzt = new Date().toISOString();
const heute = jetzt.slice(0, 10);
const gestern = new Date(Date.now() - 86400_000).toISOString().slice(0, 10);
function anlegen(name, rolle, code) {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(code, salt, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
.run(name, rolle, hash, salt, 32768, jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
}
const idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
const idScout = anlegen("Patrick-Maximilian", "scout", "CODE-SCOU-0001");
const idTili = anlegen("Tili", "creator", "CODE-ANNA-0001");
d.prepare("INSERT INTO betreuung (creator_id, betreuer_id, seit) VALUES (?,?,?)").run(idTili, idScout, jetzt);
const LANG = "Ein sehr langer Eintrag ohne Leerzeichenumbruch fuer die Handypruefung";
for (const [status, frist] of [["offen", gestern], ["offen", heute], ["arbeit", null],
["review", null], ["erledigt", null]]) {
d.prepare(`INSERT INTO aufgaben (titel, status, creator_id, verantwortlich_id, frist,
prioritaet, erstellt, erstellt_von) VALUES (?,?,?,?,?,'hoch',?,?)`)
.run(LANG, status, idTili, idTili, frist, jetzt, idDogi);
}
for (const [art, stunde] of [["call", "10:00"], ["termin", "14:30"], ["review", "18:00"]]) {
d.prepare(`INSERT INTO termine (titel, art, beginn, dauer_min, creator_id, teilnehmer_id,
erledigt, erstellt, erstellt_von) VALUES (?,?,?,60,?,?,0,?,?)`)
.run(LANG, art, heute + "T" + stunde, idTili, idTili, jetzt, idDogi);
}
d.prepare(`INSERT INTO eintraege (bereich, art, titel, datum, dringlichkeit, status,
creator_id, erstellt, erstellt_von)
VALUES ('technik','problem',?,?,'hoch','offen',?,?,?)`)
.run(LANG, heute, idTili, jetzt, idDogi);
d.close();
const { chromium } = await import(
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await chromium.launch();
const SEITEN = [
"start.html", "uebersicht.html", "aufgaben.html", "kalender.html", "personen.html",
"profil.html", "wissen.html", "dateien.html", "calls.html", "startcheck.html",
"report.html", "scouting.html", "automation.html", "content.html", "bereich.html?b=live",
];
/* Drei echte Geraete, nicht "irgendwas Schmales":
320 iPhone SE (1. Gen) -- das schmalste noch benutzte Geraet
390 iPhone 14/15, das haeufigste Mass
412 Pixel 8 / Galaxy S24, typisches Android */
const GERAETE = [[320, 568, "sehr schmal"], [390, 844, "iPhone"], [412, 915, "Android"]];
const messen = () => ({
/* 1. Waagerechtes Scrollen. Der schlimmste Fehler auf einem Handy. */
ueber: document.documentElement.scrollWidth - document.documentElement.clientWidth,
schuld: [...document.querySelectorAll("body *")]
.filter((e) => {
const r = e.getBoundingClientRect();
/* Nur was wirklich sichtbar ist. Ein Wasserzeichen hinter
overflow:hidden meldet ein Rechteck, das nie zu sehen war --
das hat frueher schon einmal falschen Alarm ausgeloest. */
if (r.width < 1 || r.height < 1) return false;
const st = getComputedStyle(e);
if (st.visibility === "hidden" || st.display === "none") return false;
/* Steckt es in einem Vorfahren mit overflow:hidden? Dann ist es
abgeschnitten und steht nicht wirklich ueber. */
for (let v = e.parentElement; v; v = v.parentElement) {
const vs = getComputedStyle(v);
if (vs.overflow !== "visible" || vs.overflowX !== "visible") return false;
}
return r.right > window.innerWidth + 1;
})
.map((e) => `${e.tagName}.${String(e.className).slice(0, 26)}(+${Math.round(e.getBoundingClientRect().right - window.innerWidth)}px)`)
.slice(0, 4),
/* 2. Abgeschnittener Text. Gemessen wird die tatsaechliche Breite
gegen die verfuegbare -- nicht "sieht ganz aus". Elemente, die
ausdruecklich kuerzen duerfen (ellipsis), sind ausgenommen: Dort
ist das Kuerzen die Absicht und der volle Text steht woanders. */
abgeschnitten: [...document.querySelectorAll("h1, h2, h3, p, span, a, label, button, li, td, th")]
.filter((e) => {
if (e.children.length) return false;
const t = e.textContent.trim();
if (t.length < 3) return false;
const st = getComputedStyle(e);
if (st.display === "none" || st.visibility === "hidden") return false;
if (st.textOverflow === "ellipsis") return false;
if (st.overflow === "hidden" && st.whiteSpace === "nowrap") return false;
return e.scrollWidth > e.clientWidth + 2;
})
.map((e) => `"${e.textContent.trim().slice(0, 22)}" (${e.scrollWidth}>${e.clientWidth})`)
.slice(0, 5),
/* 3. Beruehrziele. 24x24 CSS-Pixel ist das Mindestmass nach WCAG 2.2
(2.5.8). Gemessen wird das Ziel selbst ODER sein umschliessender
Bereich -- ein 16 px grosses Symbol in einem 44 px grossen Knopf
ist in Ordnung. */
klein: [...document.querySelectorAll("button, a[href], input, select, textarea, [role=button]")]
.filter((e) => {
const r = e.getBoundingClientRect();
if (r.width < 1 || r.height < 1) return false;
const st = getComputedStyle(e);
if (st.display === "none" || st.visibility === "hidden") return false;
return r.width < 24 || r.height < 24;
})
.map((e) => {
const r = e.getBoundingClientRect();
return `${e.tagName}"${(e.textContent || e.getAttribute("aria-label") || "").trim().slice(0, 14)}" `
+ `${Math.round(r.width)}x${Math.round(r.height)}`;
})
.slice(0, 5),
/* 4. Ueberlappende Bedienelemente. Zwei Knoepfe, die sich decken,
heissen: Man trifft den falschen. Geprueft wird der Mittelpunkt --
liegt dort ein ANDERES Bedienelement, ist das Ziel verdeckt. */
verdeckt: [...document.querySelectorAll("button, a[href], input, select")]
.filter((e) => {
const r = e.getBoundingClientRect();
if (r.width < 8 || r.height < 8) return false;
if (r.top < 0 || r.bottom > window.innerHeight) return false;
const st = getComputedStyle(e);
if (st.display === "none" || st.visibility === "hidden" || st.pointerEvents === "none") return false;
const oben = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2);
return oben && oben !== e && !e.contains(oben) && !oben.contains(e);
})
.map((e) => `${e.tagName}"${(e.textContent || "").trim().slice(0, 16)}"`)
.slice(0, 3),
/* 5. Die Kopfleiste muss vollstaendig im Bild sein -- sie ist die
einzige Stelle, ueber die man wieder wegkommt. */
kopfDrin: (() => {
const a = document.querySelector(".abmelden")?.getBoundingClientRect();
return a ? (a.right <= window.innerWidth + 1 && a.left >= -1) : null;
})(),
/* 6. Schriftgroesse: unter 12 px liest niemand auf einem Handy. */
winzig: [...document.querySelectorAll("p, span, li, a, label, small")]
.filter((e) => e.children.length === 0 && e.textContent.trim().length > 3
&& parseFloat(getComputedStyle(e).fontSize) < 11.5)
.map((e) => `"${e.textContent.trim().slice(0, 18)}" ${getComputedStyle(e).fontSize}`)
.slice(0, 3),
});
for (const [breite, hoehe, wie] of GERAETE) {
console.log(`\n=== ${wie} (${breite}x${hoehe})`);
const kontext = await browser.newContext({
viewport: { width: breite, height: hoehe },
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
});
const seite = await kontext.newPage();
await seite.goto(BASIS + "/workspace/", { waitUntil: "domcontentloaded" });
/* Auch die Anmeldeseite selbst wird geprueft -- sie ist die erste,
die jemand auf dem Handy sieht. */
{
await seite.waitForTimeout(500);
const r = await seite.evaluate(messen);
const gut = r.ueber <= 0 && !r.abgeschnitten.length && !r.klein.length && !r.verdeckt.length;
ok(gut, `Anmeldung über=${r.ueber}px abgeschnitten=${r.abgeschnitten.length} `
+ `klein=${r.klein.length} verdeckt=${r.verdeckt.length}`
+ (r.schuld.length ? `\n steht über: ${r.schuld.join(", ")}` : "")
+ (r.abgeschnitten.length ? `\n abgeschnitten: ${r.abgeschnitten.join(", ")}` : "")
+ (r.klein.length ? `\n zu klein: ${r.klein.join(", ")}` : "")
+ (r.verdeckt.length ? `\n verdeckt: ${r.verdeckt.join(", ")}` : ""));
}
await seite.click('.rolle[data-rolle="admin"]');
await seite.fill("#code", "CODE-DOGI-0001");
await seite.click("button[type=submit]");
await seite.waitForURL("**/start.html", { timeout: 15000 });
for (const s of SEITEN) {
const konsole = [];
const auf = (m) => { if (m.type() === "error") konsole.push(m.text()); };
const err = (e) => konsole.push("pageerror: " + e.message);
seite.on("console", auf);
seite.on("pageerror", err);
await seite.goto(BASIS + "/workspace/" + s, { waitUntil: "networkidle" });
await seite.waitForTimeout(700);
const r = await seite.evaluate(messen);
const gut = r.ueber <= 0 && !r.abgeschnitten.length && !r.klein.length
&& !r.verdeckt.length && !r.winzig.length
&& r.kopfDrin !== false && !konsole.length;
console.log(` ${gut ? "ok " : "FEHL"} ${String(s).padEnd(20)}`
+ `über=${String(r.ueber).padStart(3)}px`
+ (r.schuld.length ? `\n steht über: ${r.schuld.join(", ")}` : "")
+ (r.abgeschnitten.length ? `\n abgeschnitten: ${r.abgeschnitten.join(", ")}` : "")
+ (r.klein.length ? `\n Berührziel zu klein: ${r.klein.join(", ")}` : "")
+ (r.verdeckt.length ? `\n verdeckt: ${r.verdeckt.join(", ")}` : "")
+ (r.winzig.length ? `\n zu kleine Schrift: ${r.winzig.join(", ")}` : "")
+ (r.kopfDrin === false ? "\n der Abmelden-Knopf steht aus dem Bild" : "")
+ (konsole.length ? `\n KONSOLE: ${konsole.join(" | ")}` : ""));
if (!gut) fehler++;
seite.off("console", auf);
seite.off("pageerror", err);
}
if (breite === 390) {
await seite.goto(BASIS + "/workspace/start.html", { waitUntil: "networkidle" });
await seite.waitForTimeout(600);
await seite.screenshot({ path: "pruef-handy.png", fullPage: true });
}
await kontext.close();
}
/* ---------------------------------------------------------------------
Bedienbarkeit mit dem Finger: Auf einem Geraet ohne feinen Zeiger darf
das Licht gar nicht erst eingebaut werden -- es kostete Arbeit fuer
einen Effekt, den dort niemand sehen kann.
--------------------------------------------------------------------- */
console.log("\n=== Kein Licht ohne Zeiger");
{
const kontext = await browser.newContext({
viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true });
const seite = await kontext.newPage();
await seite.goto(BASIS + "/workspace/", { waitUntil: "domcontentloaded" });
await seite.click('.rolle[data-rolle="admin"]');
await seite.fill("#code", "CODE-DOGI-0001");
await seite.click("button[type=submit]");
await seite.waitForURL("**/start.html", { timeout: 15000 });
await seite.waitForTimeout(800);
/* Eine Kachel antippen und wieder loslassen -- danach darf nichts
leuchten bleiben. */
await seite.tap(".kachel >> nth=1").catch(() => {});
await seite.waitForTimeout(400);
const r = await seite.evaluate(() => ({
lichter: document.querySelectorAll(".licht").length,
an: document.querySelectorAll('[data-licht="an"]').length,
}));
ok(r.lichter === 0, `auf dem Handy wird gar kein Licht eingebaut (${r.lichter})`);
ok(r.an === 0, `und keines bleibt an (${r.an})`);
await kontext.close();
}
await browser.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
console.log(fehler ? `\n${fehler} Prüfung(en) fehlgeschlagen.` : "\nAlles in Ordnung.");
process.exit(fehler ? 1 : 0);
+123
View File
@@ -449,6 +449,129 @@ console.log("\n=== Das Licht folgt dem Zeiger");
await seite.mouse.move(5, 5);
await seite.waitForTimeout(400);
}
/* --- Bleibt das Licht haengen? -----------------------------------------
Beanstandet am 01.09.2026: "teilweise bleibt das Licht haengen".
Es gab dafuer vier Wege, und jeder einzelne wird hier durchgespielt.
Alle vier haben dasselbe Merkmal: Der Zeiger ist weg, aber die Karte
leuchtet weiter. */
{
const anzahlAn = () => seite.evaluate(() =>
document.querySelectorAll('[data-licht="an"]').length);
/* 1. Ueber die Luecke zwischen zwei Kacheln hinaus. */
const k1 = await seite.locator(".kachel").nth(1).boundingBox();
await seite.mouse.move(k1.x + k1.width / 2, k1.y + k1.height / 2);
await seite.waitForTimeout(300);
ok(await anzahlAn() === 1, `beim Ueberfahren leuchtet genau eine Kachel (${await anzahlAn()})`);
await seite.mouse.move(k1.x + k1.width / 2, k1.y - 30); // in die Luecke darueber
await seite.waitForTimeout(350);
ok(await anzahlAn() === 0, `in der Luecke geht es aus (${await anzahlAn()})`);
/* 2. Direkt von einer Kachel auf die naechste -- es darf nie mehr
als eine gleichzeitig leuchten. */
const k2 = await seite.locator(".kachel").nth(2).boundingBox();
await seite.mouse.move(k1.x + k1.width / 2, k1.y + k1.height / 2);
await seite.waitForTimeout(250);
await seite.mouse.move(k2.x + k2.width / 2, k2.y + k2.height / 2);
await seite.waitForTimeout(300);
ok(await anzahlAn() === 1, `beim Wechsel leuchtet weiterhin nur eine (${await anzahlAn()})`);
/* 3. Gescrollt, ohne die Maus zu bewegen. Die Karte wandert unter
dem stehenden Zeiger weg -- es kommt KEIN Zeigerereignis. Das war
der haeufigste Fall. */
await seite.mouse.wheel(0, 600);
await seite.waitForTimeout(450);
ok(await anzahlAn() === 0, `nach dem Scrollen ist es aus (${await anzahlAn()})`);
/* WIEDER HOCH. Ohne das lagen die Kacheln fuer alle folgenden
Pruefungen 600 px weiter oben -- die naechste Pruefung bewegte den
Zeiger dann ins Leere und meldete einen Fehler, den es nicht gab. */
await seite.mouse.wheel(0, -600);
await seite.waitForTimeout(400);
/* 4. Der Wettlauf: schnell hinein und sofort wieder hinaus. Der
angemeldete Bildaufbau lief frueher NACH dem Verlassen und
schaltete das Licht wieder an -- so blieb es haengen. */
await seite.mouse.move(0, 0);
await seite.waitForTimeout(200);
const k3 = await seite.locator(".kachel").nth(0).boundingBox();
if (k3) {
await seite.mouse.move(k3.x + 20, k3.y + 20);
await seite.mouse.move(k3.x - 60, k3.y - 60); // im selben Wimpernschlag wieder raus
await seite.waitForTimeout(500);
ok(await anzahlAn() === 0, `hinein und sofort wieder hinaus: aus (${await anzahlAn()})`);
}
}
/* --- Die Farbe des Lichts gehoert zur Kachel ---------------------------
Beanstandet: Es leuchtete in der Farbe der SEITE statt in der der
Kachel. Geprueft wird, dass zwei verschieden gefaerbte Kacheln auch
verschieden gefaerbtes Licht bekommen. */
{
const farben = [];
for (const nr of [1, 2, 3]) {
const b = await seite.locator(".kachel").nth(nr).boundingBox();
if (!b) continue;
await seite.mouse.move(b.x + b.width / 2, b.y + b.height / 2);
await seite.waitForTimeout(300);
farben.push(await seite.evaluate((n) => {
const k = document.querySelectorAll(".kachel")[n];
const l = k.querySelector(".licht");
return {
ton: getComputedStyle(k).getPropertyValue("--ton").trim(),
licht: l ? getComputedStyle(l, "::before").backgroundImage.slice(0, 90) : null,
};
}, nr));
}
const toene = new Set(farben.map((f) => f.ton));
ok(toene.size === farben.length,
`jede Kachel hat ihren eigenen Ton (${[...toene].join(", ")})`);
/* Die Farbe im Verlauf NICHT als Text vergleichen: Der Browser
rechnet color-mix() aus und schreibt das Ergebnis je nach Fassung
als rgb(), color(srgb ...) oder oklab() zurueck. Ein
Zeichenkettenvergleich schlaegt dann fehl, obwohl die Farbe
stimmt -- genau das ist beim ersten Lauf passiert.
Verglichen werden stattdessen die Lichter UNTEREINANDER: Drei
verschieden gefaerbte Kacheln muessen drei verschiedene Verlaeufe
ergeben. Kaeme die Farbe weiterhin von der SEITE (der Fehler, den
Filipe gesehen hat), waeren alle drei gleich. */
const lichter = new Set(farben.map((f) => f.licht));
ok(lichter.size === farben.length,
`und das Licht ist je Kachel ein anderes (${lichter.size} von ${farben.length})`);
/* Zusaetzlich an echten Bildpunkten: Das Licht der Aufgaben-Kachel
(orange) muss mehr Rot als Blau haben, das der Kalender-Kachel
(tuerkis) mehr Blau als Rot. Das kann keine Zeichenkette
vortaeuschen. */
for (const [name, pruefung] of [["Aufgaben", (p) => p[0] > p[2]],
["Kalender", (p) => p[2] > p[0]]]) {
const b = await seite.evaluate((n) => {
const k = [...document.querySelectorAll(".kachel")]
.find((x) => x.querySelector(".kachel__name")?.textContent === n);
if (!k) return null;
const r = k.getBoundingClientRect();
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
}, name);
if (!b) continue;
await seite.mouse.move(b.x, b.y);
await seite.waitForTimeout(350);
const bild = await seite.screenshot({ type: "png" });
const p = await seite.evaluate(async ({ b64, x, y }) => {
const i = new Image(); i.src = "data:image/png;base64," + b64; await i.decode();
const c = document.createElement("canvas");
c.width = i.width; c.height = i.height;
const g = c.getContext("2d"); g.drawImage(i, 0, 0);
const s = i.width / window.innerWidth;
return [...g.getImageData(Math.round(x * s), Math.round(y * s), 1, 1).data];
}, { b64: bild.toString("base64"), x: b.x, y: b.y });
ok(pruefung(p), `das Licht auf "${name}" hat die richtige Farbe `
+ `(rot ${p[0]}, grün ${p[1]}, blau ${p[2]})`);
}
await seite.mouse.move(2, 2);
await seite.waitForTimeout(300);
}
ok(ruhe === "(Ruhelage)", `ruhend keine eigene Position gesetzt (${ruhe})`);
await seite.mouse.move(kasten.x + kasten.width * 0.2, kasten.y + kasten.height * 0.3);
+7 -4
View File
@@ -119,10 +119,13 @@
font-size: .74rem; color: var(--text-still); letter-spacing: normal;
}
.spalte[data-status="offen"] { --sfarbe: #6f89a8; }
.spalte[data-status="arbeit"] { --sfarbe: #3fbdf5; }
.spalte[data-status="review"] { --sfarbe: #e0a33c; }
.spalte[data-status="erledigt"] { --sfarbe: #4dab77; }
/* --ton mitgesetzt: Das Licht unter dem Zeiger nimmt seine Farbe von
dort. Ohne diese Zeile leuchtete die gruene Erledigt-Spalte orange
(der Farbton der SEITE), was aussah wie ein Fehler und einer war. */
.spalte[data-status="offen"] { --sfarbe: #6f89a8; --ton: #6f89a8; }
.spalte[data-status="arbeit"] { --sfarbe: #3fbdf5; --ton: #3fbdf5; }
.spalte[data-status="review"] { --sfarbe: #e0a33c; --ton: #e0a33c; }
.spalte[data-status="erledigt"] { --sfarbe: #4dab77; --ton: #4dab77; }
.spalte__punkt {
background: var(--sfarbe);
box-shadow: 0 0 9px color-mix(in srgb, var(--sfarbe) 70%, transparent);
+3 -3
View File
@@ -44,7 +44,7 @@
.ki-kasten__daten {
display: grid; gap: 7px; margin: 18px 0 0; padding: 16px 0 0; list-style: none;
border-top: 1px solid var(--rand);
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
}
.ki-kasten__daten li { display: flex; gap: 10px; font-size: .83rem; color: var(--text-leise); }
.ki-kasten__schild {
@@ -83,7 +83,7 @@
.zustand__daten {
display: grid; gap: 7px; margin: 18px 0 0; padding: 16px 0 0; list-style: none;
border-top: 1px solid var(--rand);
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
}
.zustand__daten li { display: flex; gap: 10px; font-size: .83rem; color: var(--text-leise); }
.zustand__daten .ki-kasten__schild { min-width: 128px; }
@@ -125,7 +125,7 @@
.regeln {
display: grid; gap: 10px;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
}
.regel {
padding: 15px 17px;
+1 -1
View File
@@ -70,7 +70,7 @@
}
.k-anlassreihe {
display: grid; gap: 9px;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
margin-top: 10px;
}
+1 -1
View File
@@ -111,7 +111,7 @@
.rollenwahl-block { grid-column: 1 / -1; }
.rollenwahl {
display: grid; gap: 8px; margin-top: 2px;
grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(232px, 100%), 1fr));
}
.rollenwahl__knopf {
+1 -1
View File
@@ -38,7 +38,7 @@
.gruppe__raster {
display: grid; gap: 14px; margin-top: 16px;
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(170px, 100%), 1fr));
}
/* Die interne Notiz hebt sich deutlich ab, damit im Gespräch mit dem
+1 -1
View File
@@ -19,7 +19,7 @@
.kacheln {
display: grid; gap: 10px;
grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(158px, 100%), 1fr));
}
.kachel {
+1 -1
View File
@@ -132,7 +132,7 @@
}
.kk__raster {
display: grid; gap: 12px; margin-bottom: 14px;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
}
.kk__raster input, .kk__raster select {
width: 100%; padding: 9px 11px;
+180 -1
View File
@@ -511,7 +511,7 @@ body.start > main { position: relative; z-index: 1; }
.zahlen {
list-style: none; margin: 0; padding: 0;
display: grid; gap: 10px;
grid-template-columns: repeat(auto-fit, minmax(122px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(122px, 100%), 1fr));
}
.zahl {
@@ -1556,3 +1556,182 @@ body.start #protokoll {
@media (forced-colors: active) {
.licht { display: none; }
}
/* =====================================================================
HANDY — was die Geraetepruefung gefunden hat (01.09.2026)
Geprueft wurde auf 320, 390 und 412 px, auf allen fuenfzehn Seiten.
Drei Arten von Fehlern kamen heraus, und alle drei sind am Rechner
unsichtbar:
1. Raster, die nicht schrumpfen konnten. minmax(280px, 1fr) ist ein
MINDESTmass -- passt es nicht, laeuft die Seite ueber statt
umzubrechen. Behoben an zwoelf Stellen mit min(280px, 100%).
2. Beruehrziele unter 24 px. Auswahlfelder waren 18 bis 22 px hoch;
mit dem Daumen trifft man daneben. WCAG 2.2 nennt 24x24 als
Mindestmass (Kriterium 2.5.8), und das ist knapp bemessen.
3. Schrift unter 11,5 px. Auf einem Telefon ist das keine
Beschriftung mehr, sondern ein Muster.
===================================================================== */
@media (max-width: 760px) {
/* --- Beruehrziele ---------------------------------------------------
Auswahlfelder, Eingaben und kleine Knoepfe bekommen ein Mindestmass.
44 px ist die Empfehlung von Apple und Google und deutlich ueber der
Norm -- der Daumen ist nun einmal breiter als ein Mauszeiger. */
select, input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
textarea, button, .schritt, .knopf-still, .abmelden {
min-height: 44px;
}
select, input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
/* 16 px verhindert nebenbei, dass iOS beim Antippen hineinzoomt --
jeder kleinere Wert loest das aus, und danach steht die Seite
schief. */
font-size: 16px;
padding: 10px 12px;
}
/* Kleine Umschalter dürfen niedriger sein, brauchen aber Breite. */
.filterreihe__knopf, .k-chip, .k-ansicht, .marke-tag, .tag-knopf {
min-height: 38px;
}
/* --- Schriftgroessen ------------------------------------------------
Alles unter 11,5 px hochgezogen. Die Rollenmarke in der Kopfleiste
lag bei 10,56 px, die Bereichszeile bei 10,88 px, die Uhrzeiten im
Kalender bei 9,6 px. */
.wer__rolle, .kopf-zeile .marke, .willkommen .marke,
.feldschild, .marke, .gruppe__unter, .kachel__unter {
font-size: .75rem;
}
.k-pille, .k-anlass { font-size: .7rem; }
.k-tag__heute, .k-anlasskarte__rest small,
.k-anlasskarte__rest, .zahl__schild, .karte__zeile {
font-size: .72rem;
}
small, .leise { font-size: .78rem; }
/* --- Kalenderpillen -------------------------------------------------
Sie waren 15 px hoch -- als Ziel zu klein und als Text zu eng. Auf
dem Handy bekommen sie Luft; dass dann weniger in eine Zelle passt,
ist richtig so: Der volle Tag steht im Tagesdialog. */
.k-pille, .k-anlass {
min-height: 26px;
display: flex; align-items: center;
padding: 3px 6px;
}
.k-tag { min-height: 92px; }
}
/* Auf sehr schmalen Geraeten (320 px, iPhone SE) wird zusaetzlich alles
Mehrspaltige einspaltig. Zwei Spalten auf 288 px Inhaltsbreite sind
keine zwei Spalten mehr, sondern zwei Streifen. */
@media (max-width: 380px) {
.neu__raster, .gruppe__raster, .zahlen, .kacheln,
.feld-block__punkte, .zustand__daten, .ki-kasten__daten, .regeln {
grid-template-columns: 1fr !important;
}
.inhalt { padding-left: 13px; padding-right: 13px; }
.kopfleiste { padding-left: 13px; padding-right: 13px; }
}
/* --- Feste Mindestbreiten aufheben -----------------------------------
Zwoelf Stellen im Stil setzen eine feste Mindestbreite (Auswahlfelder
170 bis 240 px, Beschriftungsspalten 74 bis 128 px). Am Rechner ist
das richtig -- ein Auswahlfeld, das mit seinem Inhalt schrumpft,
springt bei jeder Aenderung in der Breite.
Auf einem 320-px-Geraet bleiben nach Polsterung 294 px uebrig. Ein
Feld mit 240 px Mindestbreite plus Beschriftung passt da nicht mehr,
und die Seite laesst sich waagerecht schieben. Genau das waren die
letzten 24 und 30 px auf startcheck.html und automation.html.
Auf dem Handy nehmen die Felder deshalb die volle Breite -- was
ohnehin die bessere Bedienung ist. */
@media (max-width: 760px) {
.steuerung select, .steuerung input,
.wahl2, .wahl2 select,
.betreuung__wahl, .filterreihe select, .kopf-knoepfe select,
select, input[type="search"], input[type="text"] {
min-width: 0;
max-width: 100%;
}
.steuerung, .kopf-zeile, .kopf-knoepfe { flex-wrap: wrap; }
.steuerung > *, .kopf-knoepfe > * { min-width: 0; }
/* Beschriftungsspalten in Datenlisten: nebeneinander wird zu
untereinander. 128 px Beschriftung plus Wert gehen auf 294 px nicht
mehr nebeneinander, ohne dass eines von beiden abbricht. */
.ki-kasten__schild, .zustand__daten .ki-kasten__schild {
min-width: 0;
}
.ki-kasten__daten li, .zustand__daten li {
flex-direction: column; gap: 2px;
}
/* Alles im Inhalt bleibt in seinem Kasten. Die Zeile ist die letzte
Sicherung: Sie faengt auch das ab, was kuenftig dazukommt, ohne dass
jemand daran denkt. */
.inhalt img, .inhalt table, .inhalt pre, .inhalt input,
.inhalt select, .inhalt textarea { max-width: 100%; }
}
/* --- Mindestschriftgroesse auf dem Handy -----------------------------
Achtundvierzig Regeln im Stil setzen eine Schrift unter 11,7 px --
Marken, Zaehler, Uhrzeiten, Spaltenkoepfe. Am Rechner ist das
angenehm: kleine Beschriftungen treten zurueck und lassen dem Inhalt
den Vortritt.
Auf einem Telefon ist dieselbe Groesse kein Text mehr, sondern ein
Muster. Die Geraetepruefung hat sie einzeln aufgelistet -- 9,6 px bei
den Uhrzeiten im Kalender, 10,56 px bei der Rollenmarke.
Gesucht wurden sie nicht von Hand (das waere ein Ratespiel gewesen
und haette die Haelfte uebersehen), sondern durch Durchsuchen der
Stildateien nach font-size unter 0,73rem. Alle Fundstellen stehen
hier und werden auf 0,75rem = 12 px angehoben.
Wer kuenftig eine neue kleine Marke baut, faellt der Handypruefung
auf und landet ebenfalls hier.
JEDER Selektor traegt body.start davor. Ohne das griff die Regel
nicht: start.css wird VOR den Seitenstilen geladen, und bei gleicher
Staerke gewinnt die spaetere Regel -- kalender.css setzte die Pille
also weiterhin auf 0,6rem. Der erste Anlauf sah im Quelltext richtig
aus und aenderte nichts. */
@media (max-width: 760px) {
body.start .balance__zahl, body.start .betreuung__schild, body.start .datei__schild,
body.start .filtergruppe__schild, body.start .fortschritt__schild, body.start .fortschritt__warn,
body.start .gruppe__zahl, body.start .k-anlass, body.start .k-anlasskarte__rest,
body.start .k-anlasskarte__rest small, body.start .k-kw, body.start .k-listentag__kw,
body.start .k-listentag__marke, body.start .k-pille, body.start .k-spaltenkopf,
body.start .k-strahl__kw, body.start .k-tag__heute, body.start .k-tag__monat,
body.start .k-wochentag__name, body.start .k-zeile__art, body.start .kachel__name,
body.start .karte__zeile, body.start .katwahl__weltname, body.start .ki-kasten__schild,
body.start .kk__schild, body.start .kopfleiste .marke, body.start .marke,
body.start .marke-art, body.start .marke-fehlt, body.start .marke-fertig,
body.start .marke-geraet, body.start .marke-neu, body.start .marke-punkt,
body.start .marke-rolle, body.start .marke-saeule, body.start .marke-status,
body.start .marke-still, body.start .marke-stufe, body.start .marke-tag,
body.start .marke-wichtig, body.start .naechstes__schild, body.start .rueck__wann,
body.start .spalte__zahl, body.start .suche-gruppe__titel, body.start .summe__name,
body.start .tu-knopf, body.start .wahl2__gruppe, body.start .wahl__rolle,
body.start .wer__rolle, body.start .wert__name, body.start .willkommen__marker,
body.start .zahl__schild
{
font-size: .75rem;
}
}
/* Der Verweis auf den naechsten Termin im Dashboard war 24 px hoch --
genau an der Grenze und damit darunter, sobald der Browser rundet.
Ein Ziel, das man mit dem Daumen nur knapp trifft, ist kein Ziel. */
@media (max-width: 760px) {
body.start .naechstes__wert,
body.start a.naechstes__wert {
display: inline-flex; align-items: center;
min-height: 32px;
padding: 4px 2px;
}
}
+10 -2
View File
@@ -136,9 +136,17 @@
@media (max-width: 620px) {
.punkt__kopf { flex-direction: column; }
.stufen { width: 100%; }
.stufe-knopf { flex: 1; }
/* flex: none MUSS hier aufgehoben werden. Die Regel oben verbietet den
Stufen jedes Schrumpfen -- gedacht, damit sie am Rechner nicht
zusammengequetscht werden. Auf 294 px Inhaltsbreite passten die drei
Knoepfe dann aber nicht mehr nebeneinander und die Seite liess sich
waagerecht schieben (gemessen: 331 px Inhalt in 294 px Kasten).
"width: 100%" allein reichte nicht: Ein Element mit flex: none haelt
seine Wunschbreite auch dann. */
.stufen { width: 100%; flex: 1 1 auto; min-width: 0; flex-wrap: wrap; }
.stufe-knopf { flex: 1 1 auto; min-width: 0; }
.paket__form { grid-template-columns: 1fr; }
.punkt { padding-left: 16px; padding-right: 13px; }
}
@media (prefers-reduced-motion: reduce) {
+2 -2
View File
@@ -11,7 +11,7 @@
.summen {
display: grid; gap: 8px; margin-bottom: 26px;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(140px, 100%), 1fr));
}
.summe {
display: grid; gap: 2px; padding: 14px 16px;
@@ -36,7 +36,7 @@
.creator-liste {
display: grid; gap: 12px;
grid-template-columns: repeat(auto-fit, minmax(430px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(430px, 100%), 1fr));
}
.creator {
position: relative; overflow: hidden;
+16 -7
View File
@@ -67,11 +67,12 @@
.kacheln {
display: grid; gap: 11px;
grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(min(310px, 100%), 1fr));
}
.kachel {
--w: var(--akzent);
--ton: var(--akzent);
position: relative; display: flex; align-items: flex-start; gap: 14px;
padding: 15px 16px;
text-decoration: none; color: var(--text);
@@ -139,12 +140,20 @@
}
/* Eine Farbe je Welt -- die Reihenfolge folgt der Gliederung. */
.block[data-welt="tiktok"] .kachel { --w: #3fbdf5; }
.block[data-welt="aufbau"] .kachel { --w: #8a76ff; }
.block[data-welt="bildton"] .kachel { --w: #5fc99a; }
.block[data-welt="technik"] .kachel { --w: #e8b877; }
.block[data-welt="inhalt"] .kachel { --w: #e88fb4; }
.block[data-welt="hilfe"] .kachel { --w: #8e9cb0; }
/* Beide Namen gleichzeitig: --w heisst die Farbe hier seit jeher und
steht in zwei Dutzend Regeln dieser Datei; --ton ist der Name, den
der ganze uebrige Workspace benutzt (Kacheln der Startseite,
Seitenkoepfe, Buehnenzuordnung).
Ohne --ton leuchtete das Licht unter dem Zeiger in der Farbe der
SEITE statt in der Farbe der Kachel -- auf der Wissensseite waren
also alle sechs Welten violett. Genau das war zu sehen. */
.block[data-welt="tiktok"] .kachel { --w: #3fbdf5; --ton: #3fbdf5; }
.block[data-welt="aufbau"] .kachel { --w: #8a76ff; --ton: #8a76ff; }
.block[data-welt="bildton"] .kachel { --w: #5fc99a; --ton: #5fc99a; }
.block[data-welt="technik"] .kachel { --w: #e8b877; --ton: #e8b877; }
.block[data-welt="inhalt"] .kachel { --w: #e88fb4; --ton: #e88fb4; }
.block[data-welt="hilfe"] .kachel { --w: #8e9cb0; --ton: #8e9cb0; }
/* ---------- Karte einer Anleitung -------------------------------------------- */
+7 -1
View File
@@ -358,7 +358,12 @@
const tage = Math.round((ausText(a.tag) - ausText(daten.heute)) / 86400000);
const k = el('div', 'k-anlasskarte'
+ (a.art === 'feiertag' ? ' feiertag' : '') + (tage < 0 ? ' vorbei' : ''));
k.style.setProperty('--afarbe', a.art === 'feiertag' ? FEIERTAG_FARBE : ANLASS_FARBE);
const af = a.art === 'feiertag' ? FEIERTAG_FARBE : ANLASS_FARBE;
k.style.setProperty('--afarbe', af);
/* --ton mitsetzen: Davon nimmt das Licht unter dem Zeiger seine
Farbe (siehe kopf.js). Sonst leuchtete jede Karte im Farbton der
SEITE statt in ihrem eigenen. */
k.style.setProperty('--ton', af);
k.append(el('span', 'k-anlasskarte__sym', a.sym));
const txt = el('div', 'k-anlasskarte__txt');
@@ -562,6 +567,7 @@
k.dataset.art = e.art;
k.dataset.erledigt = e.erledigt ? 'ja' : 'nein';
k.style.setProperty('--zfarbe', e.farbe);
k.style.setProperty('--ton', e.farbe);
k.append(el('span', 'k-zeile__zeit', e.uhr ? e.uhr + ' Uhr' : '—'));
k.append(el('span', 'k-zeile__titel', e.titel));
+58 -4
View File
@@ -182,15 +182,42 @@
let warten = false;
let letzte = null;
/* Die Karte, auf der das Licht gerade AN ist. Genau eine -- mehr
kann es nicht geben, es gibt ja nur einen Zeiger. */
let aktuelle = null;
const aus = (karte) => {
if (!karte) return;
karte.removeAttribute('data-licht');
karte.style.removeProperty('--mx');
karte.style.removeProperty('--my');
if (aktuelle === karte) aktuelle = null;
};
wurzel.addEventListener('pointermove', (e) => {
const karte = e.target.closest?.(LICHT_KARTEN);
if (!karte) return;
if (!karte) {
/* Der Zeiger ist zwischen den Karten -- das Licht der letzten
geht aus. Ohne das blieb es haengen, sobald man eine Karte
ueber eine Luecke verliess statt ueber eine Nachbarkarte. */
aus(aktuelle);
letzte = null;
return;
}
/* Direkt von einer Karte auf die naechste: Die alte ausschalten,
bevor die neue angeht. */
if (aktuelle && aktuelle !== karte) aus(aktuelle);
letzte = { karte, x: e.clientX, y: e.clientY };
if (warten) return;
warten = true;
requestAnimationFrame(() => {
warten = false;
/* WETTLAUF. Zwischen dem Anmelden dieses Bildaufbaus und seinem
Ablauf kann der Zeiger die Karte laengst verlassen haben --
dann hat pointerout schon aufgeraeumt, und diese Zeilen
schalteten das Licht gleich wieder AN. Genau so blieb es
haengen. Deshalb wird hier noch einmal geprueft, ob ueberhaupt
noch etwas zu beleuchten ist. */
if (!letzte) return;
const { karte: k, x, y } = letzte;
vorbereiten(k);
@@ -198,6 +225,7 @@
k.style.setProperty('--mx', ((x - r.left) / r.width * 100).toFixed(1) + '%');
k.style.setProperty('--my', ((y - r.top) / r.height * 100).toFixed(1) + '%');
k.dataset.licht = 'an';
aktuelle = k;
});
}, { passive: true });
@@ -213,10 +241,36 @@
verlaesst, wird geloescht -- sonst flackerte das Licht bei jeder
Bewegung ueber ein Wort hinweg. */
if (karte.contains(e.relatedTarget)) return;
karte.removeAttribute('data-licht');
karte.style.removeProperty('--mx');
karte.style.removeProperty('--my');
letzte = null; /* den angemeldeten Bildaufbau entwerten */
aus(karte);
}, { capture: true, passive: true });
/* Drei weitere Wege, auf denen der Zeiger verschwindet, ohne dass
pointerout die Karte meldet -- jeder davon hat das Licht haengen
lassen:
Fenster verlassen der Zeiger geht ueber den Rand hinaus
Tab gewechselt es kommt gar kein Ereignis mehr
gescrollt die Karte wandert unter dem stehenden
Zeiger weg; mausbewegt wurde nichts
Beim Scrollen wird nicht blind geloescht: Es wird nachgesehen, ob
die zuletzt beleuchtete Karte noch unter dem Zeiger liegt. */
let zeigerX = 0, zeigerY = 0;
wurzel.addEventListener('pointermove', (e) => { zeigerX = e.clientX; zeigerY = e.clientY; },
{ passive: true });
const pruefen = () => {
if (!aktuelle) return;
const unten = document.elementFromPoint(zeigerX, zeigerY);
if (!unten || !aktuelle.contains(unten)) { letzte = null; aus(aktuelle); }
};
window.addEventListener('scroll', pruefen, { passive: true, capture: true });
window.addEventListener('blur', () => { letzte = null; aus(aktuelle); });
document.addEventListener('visibilitychange', () => {
if (document.hidden) { letzte = null; aus(aktuelle); }
});
document.addEventListener('pointerleave', () => { letzte = null; aus(aktuelle); });
};
/* ---------- Jede Seite trägt Zeichen und Farbe ihrer Kachel -------------
+7 -7
View File
@@ -7,9 +7,9 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
</head>
<body class="start">
@@ -139,9 +139,9 @@
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/aufgaben.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/aufgaben.js?v=202609011448" defer></script>
</body>
</html>
+8 -8
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609011448" />
</head>
<body class="start">
@@ -132,9 +132,9 @@
</main>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/automation.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/automation.js?v=202609011448" defer></script>
</body>
</html>
+8 -8
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609011448" />
</head>
<body class="start">
@@ -107,9 +107,9 @@
</main>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/bereich.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/bereich.js?v=202609011448" defer></script>
</body>
</html>
+9 -9
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609011448" />
</head>
<body class="start">
@@ -54,10 +54,10 @@
</main>
<script src="assets/js/ki.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/calls.js?v=202609011148" defer></script>
<script src="assets/js/ki.js?v=202609011448" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/calls.js?v=202609011448" defer></script>
</body>
</html>
+8 -8
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/content.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/content.css?v=202609011448" />
</head>
<body class="start">
@@ -158,9 +158,9 @@
</main>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/content.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/content.js?v=202609011448" defer></script>
</body>
</html>
+8 -8
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609011448" />
</head>
<body class="start">
@@ -93,9 +93,9 @@
</main>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/dateien.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/dateien.js?v=202609011448" defer></script>
</body>
</html>
+2 -2
View File
@@ -25,7 +25,7 @@
type="image/webp" media="(max-width: 899px)" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
</head>
<body class="gate">
@@ -143,6 +143,6 @@
</p>
</main>
<script src="assets/js/gate.js?v=202609011148" defer></script>
<script src="assets/js/gate.js?v=202609011448" defer></script>
</body>
</html>
+8 -8
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609011448" />
</head>
<body class="start">
@@ -147,9 +147,9 @@
</main>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/kalender.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/kalender.js?v=202609011448" defer></script>
</body>
</html>
+8 -8
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609011448" />
</head>
<body class="start">
@@ -121,9 +121,9 @@
</main>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/personen.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/personen.js?v=202609011448" defer></script>
</body>
</html>
+8 -8
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609011448" />
</head>
<body class="start">
@@ -170,9 +170,9 @@
</main>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/profil.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/profil.js?v=202609011448" defer></script>
</body>
</html>
+9 -9
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/report.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/report.css?v=202609011448" />
</head>
<body class="start">
@@ -91,10 +91,10 @@
</main>
<script src="assets/js/ki.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/report.js?v=202609011148" defer></script>
<script src="assets/js/ki.js?v=202609011448" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/report.js?v=202609011448" defer></script>
</body>
</html>
+8 -8
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609011448" />
</head>
<body class="start">
@@ -124,9 +124,9 @@
</div>
</div>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/scouting.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/scouting.js?v=202609011448" defer></script>
</body>
</html>
+6 -6
View File
@@ -7,8 +7,8 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
</head>
<body class="start">
@@ -99,9 +99,9 @@
</p>
</main>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/start.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/start.js?v=202609011448" defer></script>
</body>
</html>
+9 -9
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609011448" />
</head>
<body class="start">
@@ -70,10 +70,10 @@
</main>
<script src="assets/js/ki.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/startcheck.js?v=202609011148" defer></script>
<script src="assets/js/ki.js?v=202609011448" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/startcheck.js?v=202609011448" defer></script>
</body>
</html>
+6 -6
View File
@@ -7,9 +7,9 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609011448" />
</head>
<body class="start">
@@ -57,9 +57,9 @@
</main>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/uebersicht.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/uebersicht.js?v=202609011448" defer></script>
</body>
</html>
+8 -8
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609011148" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/start.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609011448" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609011448" />
</head>
<body class="start">
@@ -175,9 +175,9 @@
</main>
<script src="assets/js/wahl.js?v=202609011148" defer></script>
<script src="assets/js/bereiche.js?v=202609011148" defer></script>
<script src="assets/js/kopf.js?v=202609011148" defer></script>
<script src="assets/js/wissen.js?v=202609011148" defer></script>
<script src="assets/js/wahl.js?v=202609011448" defer></script>
<script src="assets/js/bereiche.js?v=202609011448" defer></script>
<script src="assets/js/kopf.js?v=202609011448" defer></script>
<script src="assets/js/wissen.js?v=202609011448" defer></script>
</body>
</html>