Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Dieses Dokument beschreibt Architektur, Konventionen und wichtige Implementierun
- **Einstiegspunkt:** `index.html`
- **Styles:** `css/styles.css` (Layout & Komponenten) + `css/tokens.css` (Design-Tokens)
- **Logik:** `js/app.js` (eine einzige Datei)
- **Aktuelle Version:** `v68` (Script-Tag: `<script src="js/app.js?v=68">`)
- **Aktuelle Version:** `v69` (Script-Tag: `<script src="js/app.js?v=69">`)

---

Expand All @@ -26,7 +26,7 @@ python3 -m http.server 8080

**Cache-Busting:** Nach Änderungen an `app.js` die Versionsnummer im Script-Tag in `index.html` erhöhen und die sichtbare `v{N}` im Footer mitziehen. **Auch die CSS-Links (`css/tokens.css?v=N`, `css/styles.css?v=N`) tragen denselben `?v=N`** – bei reinen CSS-/Layout-Änderungen unbedingt mitziehen, sonst liefert GitHub Pages das alte Stylesheet:
```html
<script src="js/app.js?v=68"></script>
<script src="js/app.js?v=69"></script>
```

### Externe Aufrufe
Expand All @@ -37,6 +37,8 @@ Dass daraus **keine Inhalte** abfließen können, ist kein Zufall, sondern eine

> ⚠️ **Die Zusagen im Markup müssen dazu passen.** Hero-Badge, Feature-Karte „Local-First" und die FAQ-Antwort „Verlassen meine Daten den Browser?" sprechen deshalb von *Inhalten*, die im Browser bleiben, und benennen die Zählung ausdrücklich. Die FAQ-Antwort steht **zweimal** in `index.html` – sichtbar und im `FAQPage`-Markup – und muss wörtlich übereinstimmen (ein Test prüft das).

> ⚠️ **Das Ladegewicht-Budget zählt den Zähl-Aufruf NICHT mit.** Bis v68 tat es das – und maß damit das Netz statt das Repo: in der CI ging der Aufruf durch und brachte rund 9 KB mit, im Entwicklungscontainer sperrt die Netzwerkrichtlinie ihn. Derselbe Commit lieferte zwei verschiedene Zahlen, und dieselbe Grenze war einmal eingehalten und einmal gerissen. Gefiltert wird **beim Zählen**, ausdrücklich nicht über `page.route()`: ein Interceptor umgeht den Speicher-Cache, danach lädt `logo.svg` (106 KB) zweimal – einmal für die Topbar, einmal für den Hero – und die Messung ist um genau diesen Betrag falsch. Die Grenze selbst (700 KB) steht seit v45 unverändert.

**Im Test abgeklemmt:** `openApp()` beantwortet die Zähl-Hosts mit einem leeren Skript (`route.fulfill`, nicht `abort` – ein abgebrochener Aufruf schriebe „Failed to load resource" in die Konsole). Sonst schriebe jeder Lauf hunderte Aufrufe in die echte Statistik.

**Die statischen Wissensseiten bleiben ohne JavaScript** und damit ohne Zählung – ihre Reichweite steht in der Search Console. `tests/seo.spec.js` prüft weiterhin *null* externe Ressourcen für diese Seiten.
Expand Down Expand Up @@ -66,7 +68,7 @@ Die App hat keinen Build-Schritt – getestet wird **die ausgelieferte App**, al
```bash
npm install # nur Dev: @playwright/test (exakt gepinnt)
npx playwright install chromium # einmalig
npm test # 379 Tests, ~150 s
npm test # 380 Tests, ~150 s
npm run test:ui # interaktiver Modus
```

Expand All @@ -88,7 +90,7 @@ npm run test:ui # interaktiver Modus
| `tests/risiko.spec.js` | k-Anonymität/l-Diversität gegen Handrechnung, Trenner-Kollision, Normalisierung, Ampelschwellen, Modellfehler (sensibles Merkmal = QI), Tab/ARIA, Übergabe aus der Bereinigung |
| `tests/tour.spec.js` | Rundgang: alle Schritte, Zielelemente existieren wirklich, Escape und Pfeiltasten, Schritte ohne Daten bieten den Import an |
| `tests/seo.spec.js` | statische Seiten aktuell (`--check`), Metadaten je Seite, `.static-hero`, Sitemap vollständig, Google-Bestätigungsdatei, Datenschutzerklärung ohne JavaScript |
| `tests/assets.spec.js` | Icon-Zuschnitt (CSS-Abgleich, echtes Rendern auf Canvas, `font-display: swap`, keine Brands-Schrift), Ladegewicht-Budget der Startseite |
| `tests/assets.spec.js` | Icon-Zuschnitt (CSS-Abgleich, echtes Rendern auf Canvas, `font-display: swap`, keine Brands-Schrift), Ladegewicht-Budget der Startseite (**ohne** den fremden Zähl-Aufruf, siehe unten) |

**Konvention:** Jeder Review-Befund bekommt einen Test, der die alte Fassung rot macht – die Kommentare im Test nennen die Version des Befunds (`Regression v28: …`). Bei App-Änderungen zuerst prüfen, ob ein bestehender Test die Regel absichert, statt sie doppelt zu implementieren.

Expand Down Expand Up @@ -516,6 +518,8 @@ Eigener Startseiten-Abschnitt **unterhalb der Modul-Karten** (`.graf-bridge`/`.g

Der Abschnitt erklärt jetzt auch, **was** die Brücke leistet (`GRAF_COLUMNS` 1:1, Vorbelegung von `accessRights` und `accrualPeriodicity` aus der Kartierung, nachzuerfassen bleiben Lizenz, Schlagwörter und Adresse) und bietet zwei Wege: `#graf-bridge-import` → **direkt** `pickAndImport()` (nicht über `openInventoryModal()` – wer kartiert hat, braucht das Erklär-Modal nicht mehr) und einen Verweis auf DatenGraf für alle, die noch nicht kartiert haben.

**Das Zeichen ist seit v69 das echte DatenGraf-Logo** (`datengraf-logo.svg`, lokal im Repo – die beiden Werkzeuge teilen ihre Bildsprache, und das Logo sagt sofort, wovon die Rede ist). Runde Fassung wie `.hero-logo`, damit die beiden als Geschwister lesbar bleiben. Die Datei stammt aus dem DatenGraf-Repo und wird **lokal ausgeliefert** – ein Hotlink auf `datengraf.nozilla.net` wäre ein zweiter externer Aufruf und würde den Test „null externe Requests" rot machen. Ein Test prüft Quelle, Alt-Text und dass das Bild wirklich lädt (`naturalWidth > 0`); ein `<img>`, das 404 liefert, sähe im Markup korrekt aus.

### Hero je Ansicht (`.view-hero`, `.hero-panel`)

Jede Unterseite trägt ihren Kopf in einem eigenen weißen Container; die Startseite ebenso (`.hero-panel`). Vorher folgten Badge, Titel, Untertitel, Aktionsbutton und Einleitungstext ohne Abstand auf die Topbar – fünf Bausteine in fünf Formaten direkt untereinander.
Expand Down Expand Up @@ -697,6 +701,7 @@ Nach Änderungen an `app.js` `?v=N` im Script-Tag **und** die `v{N}` im Footer e
| v16 | Daten-Kompass (Herzstück) – eigene View + Hero-Haupt-CTA (Topbar-„Loslegen" zeigt ebenfalls darauf): ausführliche Open-Data-Reifegrad-Checkliste nach ODRA / EU Open Data Maturity / 5-Sterne / DCAT-AP.de / DSGVO·FAIR (7 Dimensionen, Quellenangaben), Status je Item mit Score & Ampel, Vorbelegung aus dem App-Stand, adaptive Sprünge in die passenden Bausteine, Persistenz (`datenlotse_kompass`) und PDF-Export; leeres „Loslegen"-Platzhalter-Modal entfernt |
| v17 | Weiterer Ausbau (1/4) – Inventar Suche, Filter & Sortierung: `renderInventory()` in `renderInventory()` + `renderInventoryBody()` aufgeteilt; `.inv-controls` (Volltextsuche + Schutzbedarf-/Clearing-Ampel-Filter + Sortierung Titel/Vollständigkeit) über `invFilter`-State und `filteredInventory()`; der echte `idx` wird durch den Filter mitgeführt, sodass Editieren über gefilterten Teilmengen weiterhin den richtigen Datensatz trifft; Live-Meta „X von Y" + Empty-State |
| v18 | Weiterer Ausbau (2/4) – Pseudonymisierung erweitert: drei neue Muster (Sozialversicherungsnummer, Steuer-ID *kontextgetriggert*, Kfz-Kennzeichen), Aktenzeichen um Geschäftszeichen/„Gz." und buchstabenhaltige Kerne erweitert, zusätzliche Geburtsdatum-Trigger („Geburtsdatum"/„Geburtstag"); Mapping-Export als CSV (`buildPseudoMappingCSV` + Button im Mapping-Kopf); Demo-Text und Grenzen-Liste aktualisiert; verifiziert auf Determinismus, Platzhalter-Konsistenz und Null-Falschtreffer auf neutralem Verwaltungstext |
| v69 | **Das echte DatenGraf-Logo.** v68 musste sich mit `fa-circle-nodes` behelfen, weil die Netzwerkrichtlinie `datengraf.nozilla.net` sperrt und der Repo-Zugriff dieser Sitzung nicht über `daimpad/datenlotse` hinausreicht. Der Nutzer hat die Rohdatei-Adresse nachgereicht; das Logo liegt jetzt als `datengraf-logo.svg` **im Repo** und wird lokal ausgeliefert – ein Hotlink wäre ein zweiter externer Aufruf und machte den Test „null externe Requests" rot. Vor dem Übernehmen geprüft: kein `<script>`, kein `onload`, kein `foreignObject`, keine externe Referenz außer dem SVG-Namensraum. Die runde Fassung ist dieselbe wie beim `.hero-logo` – die beiden Werkzeuge teilen ihre Bildsprache, und nebeneinander sollen sie als Geschwister lesbar sein. Der Test prüft **`naturalWidth > 0`** statt nur das Markup: ein `<img>`, dessen Datei fehlt, sieht im DOM völlig korrekt aus. **Die CI wurde davon rot** – das Ladegewicht-Budget riss um 4 KB, lokal nicht. Die Ursache war nicht das Logo, sondern dass der Test den **fremden Zähl-Aufruf mitzählte**: in der CI erreichbar (rund 9 KB), im Entwicklungscontainer von der Netzwerkrichtlinie gesperrt. Derselbe Commit maß zwei Zahlen. Zwei Irrwege dabei: eine Rundung der Logo-Koordinaten (wie bei `logo.svg` in v45) brachte nur 2,6 KB und veränderte die Grafik im Pixelvergleich sichtbar – für den Betrag nicht vertretbar; und `page.route()` als Abklemmung machte es schlimmer, weil ein Interceptor den Speicher-Cache umgeht und `logo.svg` (106 KB) danach zweimal lädt. Gefiltert wird jetzt beim Zählen, die Grenze bleibt bei 700 KB. Nachgewiesen mit einem eingeschleusten 20-KB-Zählskript: exakt diese 20.000 Bytes verschwinden aus der Summe. 1 neuer Test. |
| v68 | **Zwei Einladungen für dieselbe Sache, zwei Knöpfe mit demselben Ziel.** Nachdem v67 den Rundgang dauerhaft in den Hero gestellt hatte, stand der wegklickbare Hinweis `#tour-hint` als zweite Einladung darunter – und die Topbar bot „Loslegen" an, das dasselbe tat wie „Sofort loslegen" zwei Zeilen tiefer. Beides bereinigt: der Hinweis ist weg, der Topbar-Knopf heißt jetzt „Daten-Kompass" und benennt sein Ziel. **Mit dem Hinweis entfiel sein einziger Leser:** `datenlotse_tour` wurde danach nur noch geschrieben und von niemandem mehr gelesen. Der Schlüssel und die drei Funktionen darum sind deshalb mit entfernt worden, statt als toter Zustand liegen zu bleiben – dieselbe Lehre wie bei `REQUIRED_FIELDS` in v65, diesmal aber gleich mit aufgeräumt. In der DatenGraf-Brücke ersetzt das DatenGraf-Zeichen (`fa-circle-nodes`, dasselbe wie in Topbar und Seitenleiste) das CSV-Zeichen, und der lila Rand links ist weg. ⚠️ **Nicht das echte DatenGraf-Logo:** die Netzwerkrichtlinie sperrt `datengraf.nozilla.net`, und der Repo-Zugriff dieser Sitzung reicht nicht über `daimpad/datenlotse` hinaus – die Datei war nicht zu beschaffen. Wer sie ins Repo legt, tauscht in `.graf-bridge-icon` das `<i>` gegen ein `<img>`. **Ein eigener Fehler, den erst der Test fand:** beim Herausschneiden der `.tour-hint`-Regeln nahm mein Schnitt die direkt danebenstehende Abdunkel-Regel `body.tour-on::before` mit – der Rundgang wäre ohne Abdunkeln erschienen. Der Test aus v66 machte es rot, weil er die Farbe **misst** statt die Regel zu suchen. 1 neuer Test. |
| v67 | **Der Einstieg sagte nicht, wer hier spricht – und der Hauptknopf nur die Hälfte.** Drei gemeldete Punkte, alle am ersten Bildschirm. **(1)** Titel und Aussage standen als *ein* Satz mit Doppelpunkt in einer Farbe und einer Größe; die Marke „DatenLotse" ging in ihrer eigenen Überschrift unter. Jetzt trägt sie 46 px in der Akzentfarbe, der Claim steht kleiner und in der Textfarbe darunter, durch eine dünne Linie abgesetzt. Der Test **misst** Größe und Farbe – eine Prüfung auf Klassennamen bliebe grün, wenn beide wieder gleich aussähen, und genau das ist die Regression, um die es geht. **(2)** Der Hero bot nur „Daten-Kompass starten". Wer sich erst orientieren wollte, fand dort keinen Weg zum Rundgang – der lag im wegklickbaren Hinweis und in der Seitenleiste, also genau an den zwei Stellen, an denen ein Erstnutzer nicht sucht. Jetzt stehen beide Einstiege nebeneinander: „Sofort loslegen" und „Rundgang starten". **(3)** Die DatenGraf-Zeile stand als graues Kleingedrucktes unter dem Hauptknopf – an der prominentesten Stelle der Seite, für eine Minderheit, und ohne zu erklären, was die Brücke eigentlich leistet. Sie ist jetzt ein eigener Abschnitt **unterhalb der Modul-Karten** mit Erklärung (Schema 1:1, Vorbelegung aus der Kartierung, was nachzuerfassen bleibt) und zwei Wegen – Import direkt in den Dateidialog, ohne das Erklär-Modal, das hier niemand mehr braucht, und ein Verweis auf DatenGraf für alle, die noch nicht kartiert haben. 3 neue Tests, drei Mutationen einzeln geprüft. |
| v66 | **Ein Darstellungs-Durchgang – und dreimal dieselbe Lehre: eine Regel, die nicht greift, fällt niemandem auf.** Der auffälligste Fund kam aus der Benutzung: im Rundgang zeichnete das hervorgehobene Zielelement **in die Karte hinein** – bei Schritt 3 stand der Fragebogentext über Titel und Knöpfen, und Klicks dort trafen den Fragebogen statt „Weiter". Ursache ist eine Eigenheit von `position: fixed`: der Layer bildet einen **eigenen Stapelkontext**, die Karte darin kann dessen `z-index` also nie überbieten, egal welchen Wert man ihr gibt. Ein höherer Wert an der Karte wäre wirkungslos geblieben – das Abdunkeln musste **aus dem Layer heraus** (jetzt `body.tour-on::before`), damit die Reihenfolge Abdunkeln < Hervorhebung < Karte überhaupt darstellbar ist. Zweiter Fund derselben Art: der Wissens-Generator schrieb `<footer class="footer">`, und **eine solche Regel gibt es im Stylesheet gar nicht** – die App heißt `site-footer`. Die neun statischen Seiten trugen ihre Linkzeile also seit jeher ungestylt und ohne Grund am unteren Rand. Dritter: `showView()` führte eine **Liste von IDs** plus einen Sonderfall für `.consult-cta`; der neue Startseiten-Abschnitt wäre auf allen sechs Unterseiten stehen geblieben, ohne dass etwas rot geworden wäre – jetzt entscheidet die Klasse `.home-only`, und ein Test geht alle Unterseiten durch. Dazu der inhaltliche Teil: der Hero trug sieben Bausteine (Logo, Titel, Einleitung, Chips, Zusage, Aktion, Import-Zeile) und sagte in Titel wie Einleitung „Datenmanagement" – **wofür das Werkzeug gebaut ist, nämlich Daten zu öffnen, kam nirgends vor**. Die vier Schritte stehen jetzt in einem eigenen Abschnitt, Titel, Einleitung und die drei Meta-Beschreibungen benennen Open Data. 5 neue Tests, sechs Mutationen einzeln geprüft. |
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Die App selbst bleibt abhängigkeitsfrei; für die Tests wird einmalig Playwrigh
```bash
npm install # nur Dev: @playwright/test
npx playwright install chromium
npm test # 379 Tests, ~150 s
npm test # 380 Tests, ~150 s
npm run test:ui # interaktiver Modus
```

Expand Down Expand Up @@ -309,7 +309,7 @@ datenlotse/
│ ├── sample-landesbehoerde.csv # Beispiel: fiktive Landesebene (12 Datensätze)
│ ├── sample-verein.csv # Beispiel: fiktiver gemeinnütziger Träger (12 Datensätze)
│ └── template.csv # Leere Vorlage zum eigenen Befüllen
├── tests/ # Playwright-End-to-End-Tests (379 Tests in 14 Dateien)
├── tests/ # Playwright-End-to-End-Tests (380 Tests in 14 Dateien)
│ ├── helpers.js # openApp/loadSample/Download-Helfer
│ ├── smoke.spec.js # Views, Routing, Dashboard, HTML-Validität, Anrede
│ ├── import.spec.js # CSV-Parser, Ableitung, Formel-Injection
Expand Down Expand Up @@ -343,6 +343,7 @@ datenlotse/
│ │ └── tests.yml # Playwright-Tests (Push auf main + Pull Requests)
│ └── CONTRIBUTING.md # Beitragsrichtlinien
├── logo.svg # Marken-Logo (Topbar + Hero)
├── datengraf-logo.svg # Logo des Schwester-Tools (DatenGraf-Brücke)
├── favicon.svg / .ico / *.png # Favicon-Set
├── site.webmanifest # PWA-Manifest
├── social-preview.svg/.png # Open-Graph-/Twitter-Vorschaubild (PNG wird ausgeliefert)
Expand Down
Binary file modified assets/fonts/webfonts/fa-regular-400.subset.woff2
Binary file not shown.
Binary file modified assets/fonts/webfonts/fa-solid-900.subset.woff2
Binary file not shown.
16 changes: 10 additions & 6 deletions css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -283,14 +283,18 @@ a { color: var(--c-accent); }
gap: 20px;
align-items: flex-start;
}
/* Das DatenGraf-Logo statt eines Zeichens – die beiden Werkzeuge teilen ihre
Bildsprache, und das Logo sagt sofort, wovon die Rede ist. Runde Fassung wie
`.hero-logo`, damit es neben dem DatenLotsen als Geschwister lesbar bleibt. */
.graf-bridge-icon {
width: 46px; height: 46px;
width: 56px; height: 56px;
flex-shrink: 0;
border-radius: 12px;
display: flex; align-items: center; justify-content: center;
background: linear-gradient(135deg, #420093 0%, #6c2fbf 100%);
color: #fff;
font-size: 22px;
border-radius: 50%;
background: #fff;
border: 2px solid rgba(66, 0, 147, 0.15);
box-shadow: var(--shadow-sm);
object-fit: contain;
padding: 3px;
}
.graf-bridge-body { min-width: 0; }
.graf-bridge-title {
Expand Down
1 change: 1 addition & 0 deletions datengraf-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion datenschutz/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
}
</script>
<link rel="stylesheet" href="/assets/fonts/inter/inter.css">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=68">
<link rel="stylesheet" href="/assets/fonts/fa/icons.min.css?v=69">
<link rel="stylesheet" href="/css/tokens.css">
<link rel="stylesheet" href="/css/styles.css">
</head>
Expand Down
12 changes: 6 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@
<link rel="stylesheet" href="assets/fonts/inter/inter.css">
<!-- Zugeschnitten auf die verwendeten Icons (tools/build-icons.py) – das
vollstaendige all.min.css bleibt als Quelle im Repo, wird aber nicht geladen. -->
<link rel="stylesheet" href="assets/fonts/fa/icons.min.css?v=68">
<link rel="stylesheet" href="assets/fonts/fa/icons.min.css?v=69">

<link rel="stylesheet" href="css/tokens.css?v=68">
<link rel="stylesheet" href="css/styles.css?v=68">
<link rel="stylesheet" href="css/tokens.css?v=69">
<link rel="stylesheet" href="css/styles.css?v=69">
</head>
<body>

Expand Down Expand Up @@ -325,7 +325,7 @@ <h2 class="module-title">Risiko &amp; Pseudonymisierung</h2>
der Stelle, an der sie die Module ohnehin überflogen haben. -->
<section class="graf-bridge home-only" id="graf-bridge" aria-labelledby="graf-bridge-title">
<div class="graf-bridge-panel">
<div class="graf-bridge-icon"><i class="fas fa-circle-nodes"></i></div>
<img src="datengraf-logo.svg" alt="DatenGraf" class="graf-bridge-icon" width="56" height="56" loading="lazy" decoding="async">
<div class="graf-bridge-body">
<h2 class="graf-bridge-title" id="graf-bridge-title">Schon mit DatenGraf kartiert?</h2>
<p class="graf-bridge-text">Dann brauchen Sie hier nicht von vorn anzufangen. DatenLotse liest <strong>genau das CSV-Schema</strong>, das DatenGraf exportiert: Aus jeder Kombination aus Quelle und Datentyp wird ein Datensatz-Kandidat nach DCAT-AP.de &ndash; samt Vorbelegung von Zugriffsrechten und Aktualisierungszyklus aus Ihrer Kartierung. Sie erg&auml;nzen nur noch, was DatenGraf nicht kennt: Lizenz, Schlagw&ouml;rter und die Adresse, unter der die Daten liegen.</p>
Expand Down Expand Up @@ -915,7 +915,7 @@ <h2 class="consult-card-headline">Umsetzung besprechen</h2>
Brands-Familie und h&auml;tte daf&uuml;r 116 KB Schrift nachgeladen. -->
<a href="https://github.com/daimpad/datenlotse" target="_blank" rel="noopener" title="GitHub Repository" aria-label="DatenLotse auf GitHub"><svg class="footer-github" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></a>
</nav>
<span class="footer-version">v68</span>
<span class="footer-version">v69</span>
</footer>

<!-- ── FAQ-Modal ─────────────────────────────────────────────── -->
Expand Down Expand Up @@ -1131,7 +1131,7 @@ <h2 id="license-title"><i class="fas fa-id-card"></i> Lizenz-Wegweiser</h2>
</div>
</div>

<script src="js/app.js?v=68"></script>
<script src="js/app.js?v=69"></script>

<!-- Anonyme Seitenzählung (GoatCounter). Der EINZIGE externe Aufruf der
Seite: keine Cookies, kein LocalStorage, keine Kennung, kein
Expand Down
Loading
Loading