From 067e4283407e20f44561a350953ef1e804a78977 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 17:54:44 +0000 Subject: [PATCH] docs: Spec Community-Logos, Englisch und GitHub-Link Co-Authored-By: Claude Opus 5.5 --- ...2026-10-04-aircard-logos-sprache-design.md | 275 ++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 docs/superpowers/specs/2026-10-04-aircard-logos-sprache-design.md diff --git a/docs/superpowers/specs/2026-10-04-aircard-logos-sprache-design.md b/docs/superpowers/specs/2026-10-04-aircard-logos-sprache-design.md new file mode 100644 index 0000000..4785f7d --- /dev/null +++ b/docs/superpowers/specs/2026-10-04-aircard-logos-sprache-design.md @@ -0,0 +1,275 @@ +# AirCard-IOS – Community-Logos, Englisch und GitHub-Link – Design + +Stand: 2026-10-04 · Status: Entwurf im Chat von Kenso freigegeben („ja“), Spec + Plan + Umsetzung ohne weitere Rückfrage · baut auf `2026-10-04-aircard-website-design.md` und `2026-10-04-aircard-community-design.md` auf (live, main 8fb28eb) + +## 1. Ziel und Rahmen + +AirCard-IOS soll in die internationale Community der App [AirCard-iOS](https://github.com/Mak5er/AirCard-iOS), ohne dass Kenso fremde Marken-Logos selbst anbietet. Dafür: + +1. **Keine eingebauten Marken-Logos mehr.** Stattdessen laden Nutzer Logos hoch (Community-Logos), alle können sie benutzen, Herzen zeigen die beliebtesten. +2. **Die Seite gibt es auf Deutsch und Englisch.** +3. **GitHub-Link zum Original-Repo** der App, daneben der Sprachknopf. + +| Punkt | Entscheidung (Kenso) | +|---|---| +| Eingebaute Logos | Alle 18 Netzwerk-Logos (Visa ×4, Mastercard ×5 inkl. „Klassisch“, UnionPay ×4, American Express ×3, JCB, Discover) und das Apple-Logo entfallen, auch ihre SVG-Dateien. | +| „Klassik kann so bleiben“ | Gemeint ist die **Vorlage „Klassisch“** (Chip, Kontaktlos-Zeichen, Nummer, PLATINUM). Sie bleibt unverändert. | +| Logo oben links | Bleibt: AirCard-IOS Symbol, AirCard-IOS Text, Kein Logo (ohne Apple). | +| Platz der Community-Logos | **Unten rechts**, wo bisher das Kartennetzwerk saß. | +| Hochladen | Nur angemeldet; sofort öffentlich; Melden, Auto-Ausblenden ab 3 Meldern, Admin. | +| Herzen | **Nur angemeldet**, eins pro Konto und Logo, wieder entfernbar; Sortierung „Beliebt“ zuerst. | +| Sprache | **Automatisch** nach Browsersprache, Umschalter merkt sich die Wahl. | +| GitHub | Link `https://github.com/Mak5er/AirCard-iOS` in der Kopfzeile, Sprachknopf direkt daneben. | + +Live-Stand beim Schreiben: 0 veröffentlichte Community-Karten, 1 gespeicherte Testkarte. Es gibt keinen Altbestand mit Marken-Logos, der aufgeräumt werden müsste. + +## 2. Karte und Konfiguration + +- `CardConfig`: Das Feld `network` entfällt. Neu ist `logo: string | null`, die ID eines Community-Logos (Muster `ID_PATTERN`). `brand` ist nur noch `'aircards-symbol' | 'aircards-text' | 'none'`. `v` bleibt `1`. +- `parseConfig`: Ein altes `network` wird ignoriert. Ein fehlendes oder ungültiges `logo` ergibt `null`. `brand: 'apple'` und andere Unbekannte ergeben den Standard (`aircards-symbol`). `serializeConfig` schreibt `{v, template, logo, details, brand, crop}`. +- `DEFAULT_CONFIG.logo = null`, eine neue Karte hat also kein Logo unten rechts. +- In den Vorlagen-Layouts heißt `network` jetzt `logo`: „Nur Bild“ ohne, „Minimalistisch“, „Klassisch“ und „Signature“ mit Logo. +- Das Logo sitzt wie bisher das Netzwerk-Logo: rechte Kante 1480, Unterkante 913, höchstens 360 px breit und 125 px hoch (Seitenverhältnis des Bildes bleibt). +- **Einfarbige Logos** (`mono = true`) werden beim Zeichnen in der Details-Farbe gefüllt (Hell `#FFFFFF`, Dunkel `#111111`). Die Form kommt aus dem Alphakanal des PNG. Mehrfarbige Logos erscheinen unverändert. +- **Gelöscht** werden 19 SVGs in `web/src/assets/marks/` (alle außer `aircards-symbol.svg` und `aircards-text.svg`) sowie alles zu Netzwerk-Marken in `marks.ts` und `config.ts`. +- **Fehlt ein Logo** (gelöscht oder ausgeblendet), zeichnet der Editor die Karte ohne Logo und zeigt den Hinweis „Das gewählte Logo ist nicht mehr verfügbar.“. Die Auswahl bleibt, bis der Nutzer ein anderes Logo wählt. Beim Veröffentlichen aus „Meine Karten“ führt ein fehlendes Logo zum Abbruch mit „Das Logo dieser Karte ist nicht mehr verfügbar. Öffne die Karte im Editor und wähle ein anderes.“. + +## 3. Editor: Bereich „Logo unten rechts“ + +Die rechte Spalte „Individuell gestalten“ zeigt von oben nach unten: **Logo unten rechts**, Details (Hell/Dunkel), **Logo oben links** (bisher „Logo“). + +Der Bereich „Logo unten rechts“ besteht aus: + +- **Kopf:** Überschrift „Logo unten rechts“, Plakette „Community“ und der Knopf „+ Hochladen“. Darunter ein kleiner Satz: „Logos werden von der Community hochgeladen.“ +- **Sortierung:** Umschalter „Beliebt“ (Standard: meiste Herzen, bei Gleichstand neueste zuerst) und „Neu“ (neueste zuerst). Admins sehen zusätzlich „Gemeldet“ und „Ausgeblendet“. +- **Raster** (Desktop 2 Spalten): + - Erste Kachel „Kein Logo“. + - Jede Logo-Kachel zeigt das Logo so, wie es auf der Karte erscheint. Einfarbige Logos stehen wie die bisherigen Kacheln in Weiß auf dunklem Grund. + - Darunter folgen der Name (eine Zeile, gekürzt) und „von “ bzw. „von Anonym“ (klein, gedämpft). + - Der Herz-Knopf „♥ “ ist ein eigener Knopf, nicht in den Auswahlknopf verschachtelt. `aria-pressed` zeigt, ob ich das Logo mit einem Herz markiert habe. + - Ein Klick auf die Kachel setzt `config.logo`. Karte und Vorlagen-Miniaturen zeigen das Logo sofort. + - Ist das gewählte Logo nicht in der geladenen Liste (z. B. nach dem Öffnen einer gespeicherten Karte), steht es als zweite Kachel direkt nach „Kein Logo“. + - 24 Logos je Seite, darunter „Mehr laden“ (Cursor). +- **Infozeile zum gewählten Logo** unter dem Raster: „ · von · ♥ “ und der Knopf „Melden“. + - Wer das Logo hochgeladen hat, sieht zusätzlich „Löschen“ (mit Rückfrage). + - Admins sehen „ Meldungen“ mit den Gründen sowie „Ausblenden“/„Einblenden“ und „Löschen“. +- **Leer:** „Noch keine Community-Logos – lade das erste hoch.“ +- **Ladefehler:** „Die Logos konnten nicht geladen werden.“ mit „Nochmal versuchen“. +- **Herz ohne Anmeldung:** Ein Klick öffnet den Login-Dialog, danach wird das Herz gesetzt. Das Herz schaltet sofort um (optimistisch). Scheitert die Anfrage, springt es zurück, und die Infozeile zeigt die Fehlermeldung. +- **Handy (unter 900 px):** Das Raster nutzt die volle Breite (`auto-fill`, mindestens 140 px je Kachel). + +## 4. Logo hochladen + +Der Dialog „Logo hochladen“ (eigenes `` wie die bisherigen Dialoge, alle Texte als Text, nie als HTML) enthält: + +- **Datei:** PNG, JPG, WebP oder SVG, höchstens 5 MB und 40 Megapixel. +- **Vorschau:** das fertig verarbeitete Logo, einmal auf dunklem und einmal auf hellem Grund. Bei „Einfarbig“ ist es in Weiß bzw. Schwarz gefüllt. +- **Name:** Pflicht, höchstens 40 Zeichen. +- **Anzeigename:** optional, höchstens 30 Zeichen. Leer bedeutet „Anonym“, die E-Mail erscheint nie. Vorbelegt mit dem zuletzt benutzten Namen aus `localStorage` (`aircard.displayName`; Zugriffe in try/catch). Der Veröffentlichen-Dialog liest und schreibt denselben Schlüssel. +- **Häkchen „Einfarbig – passt sich Hell/Dunkel an“:** nur wählbar, wenn das Logo transparente Bereiche hat. +- **Pflicht-Häkchen:** „Ich habe die Rechte an diesem Logo oder die Erlaubnis des Inhabers, und es darf öffentlich geteilt werden.“ +- **Knöpfe:** „Hochladen“ und „Abbrechen“. + +**Verarbeitung im Browser** (der Server prüft trotzdem): + +1. Datei dekodieren. SVG wird als Bild geladen, also ohne Skripte. Hat ein SVG keine eigene Größe, gilt 1000 × 1000. +2. Transparente Ränder abschneiden (Alpha ≤ 8 gilt als leer). +3. Auf höchstens 720 × 250 px verkleinern, nie vergrößern. SVG wird direkt in der Zielgröße gerastert. +4. Als PNG ausgeben. +5. Abbruch mit „Das Logo ist leer.“, wenn nichts Sichtbares bleibt, und mit „Das Logo ist zu klein (mindestens 8 × 8 Pixel).“ unter 8 × 8 px. + +**Ablauf:** + +1. Das PNG landet im Bucket `logo-images` mit den Rechten `read("any")` und `delete("user:")`. +2. `POST /api/logos/hochladen` wird aufgerufen. +3. Scheitert der Aufruf eindeutig, löscht der Client die Datei wieder (gleiche Regel wie beim Veröffentlichen). Bleibt der Ausgang offen, bleibt die Datei liegen. +4. Danach ist das Logo für alle sichtbar, wird gewählt (`config.logo`) und steht als erste Logo-Kachel. + +Pro Konto sind höchstens **30 Logos** erlaubt, ausgeblendete zählen mit. Darüber hinaus erscheint: „Du hast schon 30 Logos hochgeladen. Lösche eins, um ein neues hochzuladen.“ + +## 5. Herzen + +- Die Tabelle `logo_likes` hat eine Zeile je Konto und Logo (feste Zeilen-ID aus Logo und Nutzer). Der Zähler `likeCount` in `logos` wird nur von der API gesetzt, und zwar immer als Anzahl der Zeilen (neu gezählt, nicht hoch- oder runtergezählt). +- Der Client lädt den Herz-Zustand für die angezeigten Logos mit `equal('userId', me)` und `equal('logoId', [ids])`. Beim An- oder Abmelden wird er neu geladen bzw. geleert. +- Für ausgeblendete oder gelöschte Logos gibt es kein Herz (404 „Dieses Logo ist nicht (mehr) verfügbar.“). + +## 6. Melden, Löschen, Admin + +- **Melden** funktioniert wie bei Karten (gleicher Dialog, Text „dieses Logo“). + - Gründe für Logos: „Unangemessen“, „Urheberrecht/Marke“, „Spam“, „Sonstiges“. + - Ein Melder ist ein IP-Netz (IPv4 /24, IPv6 /48), gespeichert nur als Hash. + - Rate-Limit: 10 Meldungen pro Stunde je Anschluss, gemeinsam mit den Karten-Meldungen. + - Ab **3** verschiedenen Meldern wird das Logo ausgeblendet. Zeile und Datei sind dann nur noch für den Hochlader und Admins lesbar. +- **Löschen** (Hochlader oder Admin) entfernt Datei, Herzen, Meldungen und zuletzt die Zeile. Gespeicherte Karten zeigen das Logo danach nicht mehr (Hinweis siehe § 2). Bereits veröffentlichte Kartenbilder bleiben unverändert, weil das Logo im Bild steckt. +- **Admin** (Label `admin`): + - Filter „Gemeldet“ (`reportCount > 0`) und „Ausgeblendet“. + - Ein- und Ausblenden. Einblenden löscht die Meldungen und setzt `reportCount` auf 0. + - Löschen. + +## 7. Sprache und Kopfzeile + +**Kopfzeile, rechte Seite:** GitHub · Sprache · ☕ Unterstützen · Hell/Dunkel · Menü. + +- **GitHub:** GitHub-Zeichen und der Text „GitHub“. + - Ziel ist `https://github.com/Mak5er/AirCard-iOS` mit `target="_blank" rel="noopener noreferrer"`. Der Title lautet „AirCard-iOS auf GitHub“ bzw. „AirCard-iOS on GitHub“. + - Zwischen 600 und 1179 px zeigt der Link nur das Zeichen (wie der Spenden-Knopf). + - Unter 600 px steht der Link im aufgeklappten Menü statt in der Kopfzeile. +- **Sprachknopf:** Globus-Zeichen plus das Kürzel der Zielsprache, also „EN“ auf der deutschen und „DE“ auf der englischen Seite. + - `aria-label` und `title` stehen in der Zielsprache („Switch to English“ bzw. „Auf Deutsch umschalten“), dazu ein passendes `lang` am Knopf. + - Der Knopf bleibt bei jeder Breite in der Kopfzeile. + +**Startsprache:** zuerst `localStorage` `aircard.lang` (`de` oder `en`). Sonst Deutsch, wenn die erste bevorzugte Browsersprache (`navigator.languages[0]`, ersatzweise `navigator.language`) mit `de` beginnt, andernfalls Englisch. `` folgt der Sprache und wird wie das Theme vor dem ersten Paint per Inline-Skript gesetzt. + +**Umschalten** geht ohne Neuladen: + +- Kopfzeile, Spenden-Banner, Konto-Menü, Fußzeile und die aktuelle Seite werden neu aufgebaut. Der Editor-Entwurf bleibt erhalten, weil er im Store liegt. +- `document.title` wird neu gesetzt. +- Der Appwrite-Client bekommt die Sprache (`client.setLocale`), damit Passwort-Mails in dieser Sprache kommen. + +**Übersetzt** wird alles Sichtbare: Texte, `aria-label`, Platzhalter, `title`, Seitentitel, Fehlermeldungen (Client, Appwrite, API) und Datumsangaben (Deutsch `04.10.2026`, Englisch `Oct 4, 2026`). + +**Nicht übersetzt** werden: + +- Nutzerinhalte: Titel, Herausgeber, Tags, Logo-Namen, Anzeigenamen. +- Routen: Sie bleiben `/gestalten`, `/karten`, `/community/`, `/passwort-neu`. +- Download-Dateinamen, README und Dokumentation. +- Die Werte der Melde-Gründe in der API. + +**Technik (gettext-Stil):** + +- `t('Deutscher Text', { name })` übersetzt beim Aufruf. Platzhalter stehen als `{name}` im Text. +- `msg('…')` markiert Texte, die vorab in Konstanten stehen. Sie gehen erst beim Anzeigen durch `t()`. +- Das Wörterbuch `web/src/i18n/en.ts` ordnet Deutsch → Englisch zu. Fehlt ein Eintrag, bleibt der deutsche Text. +- Ein Test sammelt alle Literale aus `t()`/`msg()` in `web/src/**` (ohne Tests). Er prüft, dass jedes eine englische Übersetzung hat, dass `en.ts` keine ungenutzten Einträge enthält und dass kein `t()` mit `${…}` aufgerufen wird. +- In Tests ist die Sprache Deutsch (Vitest-Setup). +- Englisch: kurz, freundlich, US-Schreibweise. „AirCard-IOS“ bleibt der Name der Seite, „AirCard-iOS“ der Name der App. + +**Fehler der API:** Antworten tragen zusätzlich `code`, den Schlüssel aus `MSG`, z. B. `{"error":"…","code":"alreadyPublished"}`. Der Client übersetzt über den Code. Ohne bekannten Code zeigt er auf Deutsch den Servertext und auf Englisch eine allgemeine Meldung je Status (400 „Invalid request.“, 403 „You are not allowed to do that.“, 404 „Not found.“, sonst „Something went wrong. Please try again.“). + +**Statisches `index.html`:** `AirCard-IOS – Card designs for Apple Wallet` und ``, damit Link-Vorschauen englisch sind. Der Router setzt danach den Titel in der gewählten Sprache. + +**Fußzeile:** + +- Deutsch: „Inoffizielle Community-Seite für die App AirCard-iOS – kein Teil des Original-Projekts und nicht mit Apple verbunden. Logos und Motive stammen von Nutzern; alle Marken gehören ihren Inhabern.“ +- Englisch: „Unofficial community site for the AirCard-iOS app – not part of the original project and not affiliated with Apple. Logos and artwork are uploaded by users; all trademarks belong to their owners.“ + +## 8. Daten (Appwrite, Projekt `6ac2130a00289343d6d5`, Datenbank `aircard`, Setup-Skript erweitert) + +**Tabelle `logos`** (rowSecurity an, **keine** Tabellenrechte für Clients): + +| Spalte | Typ | Inhalt | +|---|---|---| +| `name` | string 40, Pflicht | Name des Logos | +| `displayName` | string 30 | Anzeigename oder leer | +| `fileId` | string 36, Pflicht | PNG im Bucket `logo-images` | +| `ownerId` | string 36, Pflicht | Nutzer-ID des Hochladers | +| `mono` | boolean, Standard false | einfarbig | +| `likeCount` | integer, Standard 0, min 0 | Anzahl Herzen | +| `hidden` | boolean, Standard false | ausgeblendet | +| `reportCount` | integer, Standard 0, min 0 | Anzahl verschiedener Melder | + +Indizes: `owner` (key, `ownerId`), `hidden` (key, `hidden`), `likes` (key, `likeCount`). + +Zeilen-ID = Datei-ID. Damit kann eine Datei nie zwei Logos tragen, und ein zweiter Versuch ergibt 409. + +Zeilenrechte (setzt nur die API): sichtbar `['read("any")']`, ausgeblendet `['read("user:")','read("label:admin")']`. + +**Tabelle `logo_likes`** (rowSecurity an, keine Tabellenrechte): + +- Spalten: `logoId` string 36 Pflicht, `userId` string 36 Pflicht. +- Zeilen-ID = `hash32("|")`. +- Zeilenrecht `['read("user:")']`. +- Indizes: `logo` (key, `logoId`), `user_logo` (key, `userId`, `logoId`). + +**Tabelle `logo_reports`** (rowSecurity aus, Tabellenrecht `read("label:admin")`, Schreiben nur API): + +- Spalten: `logoId` string 36 Pflicht, `reason` enum (unangemessen, kartendaten, urheberrecht, spam, sonstiges) Pflicht, `reporterHash` string 64 Pflicht. +- Zeilen-ID = `hash32("logo||")`. +- Index `logo` (key, `logoId`). + +**Bucket `logo-images`:** + +- Bucket-Recht `create("users")`, fileSecurity an, höchstens 1 MiB, Endung nur `png`, ohne Kompression und ohne Virenscan. +- Dateirechte beim Hochladen: `read("any")`, `delete("user:")`. +- Nach dem Anlegen setzt die API genau `read("any")`. Der Hochlader verliert damit `delete`, sonst könnte er das Bild unter derselben ID tauschen. +- Ausgeblendet: `read("user:")`, `read("label:admin")`. + +Bestehende Tabellen und Buckets bleiben unverändert. + +## 9. API-Dienst `aircard-api` (Erweiterung) + +Die neuen Endpunkte stehen in einem eigenen Modul `deploy/api/logos.js`. `app.js` reicht ihm die gemeinsamen Helfer (Anmeldung, Sperre je Schlüssel, Rate-Limit, Log, Melder-Hash) und hängt seine Routen an. Der Betrieb kopiert künftig vier Dateien (`appwrite.js`, `app.js`, `logos.js`, `server.js`). `appwrite.js` bekommt `countRows(table, queries)`, das `total` einer Abfrage mit `limit(1)` liefert. + +| Endpunkt | Auth | Wirkung | +|---|---|---| +| `POST /api/logos/hochladen` `{name, displayName?, mono, fileId}` | Nutzer-JWT | Prüft Felder; die Datei existiert, liegt in `logo-images`, trägt `delete("user:")` und `read("any")`, ist PNG, höchstens 1 MiB, Maße zwischen 8 × 8 und 720 × 250 (aus dem PNG-Kopf); höchstens 30 Logos je Konto. Legt die Zeile an (sichtbar), setzt die Datei auf `read("any")` und nimmt die Zeile zurück, wenn das scheitert. → `201 {id}`; 409 bei schon verwendeter Datei; 429 bei Limit. | +| `POST /api/logos/herz` `{id, herz}` | Nutzer-JWT | 404 bei fehlendem oder ausgeblendetem Logo. `herz: true` legt die Like-Zeile an (409 = schon da, kein Fehler), `false` löscht sie (404 = schon weg, kein Fehler). Danach `likeCount = countRows`. → `200 {herz, likeCount}` | +| `POST /api/logos/melden` `{id, reason}` | keine | Wie `/api/melden`: Rate-Limit (gemeinsam), Melder-Hash, Duplikat → `200 {schonGemeldet:true}`, `reportCount` = Zeilen, ab 3 ausblenden. 404 bei fehlendem oder ausgeblendetem Logo. | +| `POST /api/logos/loeschen` `{id}` | Nutzer-JWT (Hochlader **oder** Admin) | Löscht Datei, Herzen, Meldungen und zuletzt die Zeile. 403 bei fremdem Logo, 404 bei fehlendem. → `200 {ok:true}` | +| `POST /api/admin/logo-sichtbarkeit` `{id, sichtbar}` | Admin-JWT | Wie `/api/admin/sichtbarkeit` für Logos (Datei- und Zeilenrechte, `hidden`; Einblenden löscht Meldungen, `reportCount = 0`). | + +Alle Fehlerantworten (auch die bestehenden) haben die Form `{error: , code: }`. Dynamische Prüftexte ohne festen Schlüssel bekommen `code: 'badRequest'`. + +Body höchstens 8 KB, nur `application/json`, sonst unverändert wie in der Community-Spec § 8. + +## 10. Was Kenso tun muss + +Nichts Pflicht. Der vorhandene Provisioning-Key (`AIRCARD_APPWRITE_API_KEY`) legt das Schema an. Der API-Key `aircard-api` hat die nötigen Scopes (Zeilen und Dateien). Fürs Testen der Admin-Knöpfe braucht Kensos Konto weiterhin das Label `admin` (steht noch aus der Community-Runde offen). + +## 11. Fehlerbehandlung + +| Fall | Meldung | +|---|---| +| Datei falscher Typ / zu groß / zu viele Pixel | „Bitte ein PNG-, JPG-, WebP- oder SVG-Bild wählen.“ / „Die Datei ist größer als 5 MB.“ / „Das Bild ist zu groß (max. 40 Megapixel).“ | +| Datei nicht lesbar | „Das Logo konnte nicht gelesen werden.“ | +| Logo leer / zu klein | siehe § 4 | +| Name fehlt / zu lang | „Bitte gib einen Namen an.“ / „Der Name ist zu lang (höchstens 40 Zeichen).“ | +| Häkchen fehlt | „Bitte bestätige, dass du das Logo teilen darfst.“ | +| Limit 30 | siehe § 4 | +| Hochladen gescheitert | „Hochladen fehlgeschlagen – “, Datei wird bei eindeutigem Fehler gelöscht | +| Herz/Melden für weg/ausgeblendet | „Dieses Logo ist nicht (mehr) verfügbar.“ | +| Melden doppelt / Rate-Limit | „Danke, du hast dieses Logo schon gemeldet.“ / „Zu viele Meldungen – bitte später nochmal.“ | +| API nicht erreichbar | „Der Dienst ist gerade nicht erreichbar.“ | +| Nicht angemeldet (Hochladen, Herz) | Login-Dialog (`withLogin`/`requireLogin`), Abbruch → nichts passiert | + +Alle Texte gibt es auf Deutsch und Englisch. + +## 12. Tests + +- **Vitest:** + - i18n: Startsprache, Umschalten, Platzhalter, Abdeckung aller `t()`/`msg()`-Texte, Datum. + - Konfiguration: alte Konfiguration mit `network`/`apple`, `logo`. + - Renderer: Logo-Box, einfarbig, ohne Logo. + - Logo-Verarbeitung: Rand-Zuschnitt, Skalierung, leer, zu klein, Transparenz-Erkennung. + - Logo-Abfragen: Beliebt/Neu/Admin-Filter, Cursor, eigene Herzen. + - Hochladen-Ablauf mit Fakes: Aufräumen nur bei eindeutigem Fehler. + - Logo-Bereich: Kacheln, Auswahl, Herz optimistisch und Rücksprung, Login-Pflicht, angeheftete Auswahl, Infozeile je Rolle. + - Kopfzeile: GitHub-Link, Sprachknopf. + - Bestehende Tests bleiben grün (Sprache im Test = Deutsch). +- **node:test (`aircard-api`):** + - Hochladen: fremde Datei 403, falsches Format/Maße 400, Limit 429, doppelt 409, Rechte nachher genau `read("any")`, Rücknahme bei Fehler. + - Herz: an/aus idempotent, Zähler = Zeilen, ausgeblendet 404. + - Melden: Dedup, gemeinsames Rate-Limit, ab 3 ausgeblendet. + - Löschen: Hochlader/Admin/fremd. + - Admin-Sichtbarkeit. + - `code` in allen Fehlerantworten. +- **Setup-Skript:** `--check` deckt `logos`, `logo_likes`, `logo_reports` und `logo-images` inkl. Indizes ab. +- **Live (aircard-ios.org):** + - Hochladen (PNG transparent, einfarbig; SVG; JPG ohne Transparenz). Das Logo ist für Gäste sichtbar und sitzt auf der Karte; Download-PNG enthält es. + - Herz an/aus (Zahl, Sortierung). + - Melden über 3 Netze → ausgeblendet; Gast sieht es nicht mehr. + - Löschen. + - Sprachwechsel mit offenem Editor-Entwurf (bleibt erhalten). + - Englisch nach Browsersprache; GitHub-Link. + - Breiten 1440 und 390 px, Hell/Dunkel. + +## 13. Nicht im Umfang + +Logo-Suche, Community-Logos oben links, Herzen für Community-Karten, Zähler „wie oft benutzt“, Logos bearbeiten (nur löschen und neu hochladen), englische Routen, weitere Sprachen, Mail an Kenso bei Meldungen, SVG-Speicherung (gespeichert wird immer PNG). + +## 14. Risiken + +- **Marken über Nutzer:** Nutzer können Visa & Co. selbst hochladen. Dagegen stehen das Pflicht-Häkchen, der Melde-Grund „Urheberrecht/Marke“, das Auto-Ausblenden und der Admin. Kenso haftet ab Kenntnis, sollte also gemeldete Logos regelmäßig ansehen. Ohne Kontaktadresse/Impressum fehlt ein Beschwerdeweg außerhalb von „Melden“ (Kenso-Entscheidung aus der Website-Runde, bekannt). +- **Herzen:** Konten sind kostenlos, viele Konten ergeben viele Herzen (akzeptiert). Auth-Limits in der Console bleiben Kensos offener Schritt. +- **Zählung:** `countRows` nutzt `total` von Appwrite (zählt bis 5000). Das reicht. +- **Übersetzungslücken:** Der Abdeckungstest fängt fehlende Einträge. Server-Prüftexte ohne Code erscheinen auf Englisch als allgemeine Meldung.