Files
AirCardIOS/docs/superpowers/specs/2026-10-04-aircard-community-design.md
2026-10-04 13:05:59 +00:00

13 KiB
Raw Permalink Blame History

AirCard-IOS – Startseite & Community-Galerie – Design

Stand: 2026-10-04 · Status: zur Freigabe durch Kenso · baut auf 2026-10-04-aircard-website-design.md auf (live, main 562fac0)

1. Ziel und Rahmen

Die Website bekommt eine Startseite im Stil von Kensos Vorbild-Screenshot (bestehende „AirCards“-Seite), aber im eigenen AirCard-IOS-Look: kompakter Hero, darunter sofort die Community-Galerie, Spenden-Banner und -Knopf, Hell/Dunkel-Schalter. Nutzer können ihre gespeicherten Karten sofort öffentlich veröffentlichen; Besucher können melden; ab 3 Meldungen wird eine Karte automatisch ausgeblendet; Kenso moderiert als Admin.

Punkt Entscheidung (Kenso)
Umfang Startseite + Community-Galerie; keine „Offiziellen Karten“ (echte Bank-Kartenmotive), keine Texte/Bilder vom Vorbild
Galerie-Inhalt nur Community-Karten
Freigabe sofort öffentlich + „Melden“; Auto-Ausblenden ab 3 verschiedenen Meldern
Extras nur Hell/Dunkel-Schalter (kein Englisch, keine Anleitung-Seite, keine Highlights)
Hero nicht Vollbild: erste Reihe Community-Karten muss im ersten Bildschirm anschneiden
Spenden Buy Me a Coffee; öffentlicher Link kommt noch (Konfig-Wert)
Technik Ansatz A: Appwrite + kleiner eigener API-Dienst aircard-api

2. Seiten und Routen

Route Inhalt
/ Startseite: Hero + Community-Galerie (Suche über ?q=, Anker #community)
/gestalten der bisherige Editor (?karte=<id> öffnet eine gespeicherte Karte, ?vorlage=<galleryId> übernimmt ein Community-Bild als „Nur Bild“)
/karten Meine Karten (+ Veröffentlichen / Veröffentlicht-Status / Zurückziehen)
/community/<id> Detailansicht einer Community-Karte
/passwort-neu unverändert

Alte Links /?karte=<id> leiten clientseitig auf /gestalten?karte=<id> um. document.title je Route („AirCard-IOS – Kartendesigns für Apple Wallet“, „Gestalten – AirCard-IOS“, „Meine Karten – AirCard-IOS“, „ – AirCard-IOS“).

3. Kopfzeile, Banner, Theme

  • Spenden-Banner (ganz oben, schließbar, Zustand in localStorage aircard.donateBannerClosed): „☕ Gefällt dir AirCard-IOS? Spendier uns einen Kaffee – damit das Projekt weiterläuft.“ + Link „Unterstützen →“. Ziel = DONATE_URL aus web/src/config.ts. Ist DONATE_URL leer, werden Banner und Spenden-Knopf nicht angezeigt (bis Kenso den öffentlichen Link buymeacoffee.com/<name> liefert).
  • Kopfzeile: Logo + „AirCard-IOS“ (→ /), Navigation „Community“ (→ /#community), „Gestalten“, „Meine Karten“; Suchfeld „Titel, Tags oder Herausgeber“ (Enter → /?q=…#community); Knopf „☕ Unterstützen“ (wenn DONATE_URL); Hell/Dunkel-Schalter (Sonne/Mond-Icon); Konto: abgemeldet „Anmelden“, angemeldet Avatar-Kreis mit Initiale (Menü: E-Mail, „Meine Karten“, „Abmelden“).
  • Unter 900 px: Navigation in ein Menü-Icon, Suchfeld als eigene Zeile unter der Kopfzeile.
  • Theme: data-theme="light|dark" auf <html>; Standard = System (prefers-color-scheme); Schalter speichert in localStorage aircard.theme. Helle Tokens: --bg #F4F6FB; --panel #FFFFFF; --panel-2 #EEF1F7; --line #D9DFEA; --line-strong #B8C2D3; --sel-bg #E3ECFB; --sel-line #3A6FD8; --text #0F1828; --muted #5B6B82; --primary #2A4566; --primary-hover #34557C. Die Kartenvorschau selbst bleibt unverändert (Kartenfarben hängen nicht vom Theme ab).

4. Startseite

  • Hero (kompakt): links Zeile „Ein bisschen Kunst für dein Wallet“, Überschrift „Kartendesigns für Apple Wallet“, ein Satz „Entdecke Motive der Community, gestalte dein eigenes und lade es als PNG herunter.“, Knöpfe „Karten entdecken ↓“ (scrollt zu #community) und „Gestalten“. Rechts eine große Karte (neueste sichtbare Community-Karte; ist die Galerie leer: eigenes Beispielmotiv web/public/hero-beispiel.png, markenfrei, von Claude gestaltet) mit dezenter Schwebe-Animation (prefers-reduced-motion respektieren).
  • Höhe: Hero höchstens 60 vh (Desktop) bzw. so, dass bei 1440 × 900 und 390 × 844 die Überschrift „Community-Karten“ und der obere Rand der ersten Kartenreihe im ersten Bildschirm sichtbar sind (Abnahmekriterium).
  • Community-Galerie (#community): Überschrift „Community-Karten“, Untertitel „Entdecke Motive der Community – und veröffentliche deine eigenen.“, Knopf „Gestalten & veröffentlichen“. Suchzeile mit aktivem Suchbegriff + „ד. Raster aus Kacheln (Kartenformat 1536:969, abgerundet): Vorschau, Titel, Herausgeber, bis 3 Tag-Chips (Klick = Suche). Sortierung neueste zuerst, 24 pro Seite, „Mehr laden“ (Cursor). Leer: „Noch keine Community-Karten – sei die erste!“ + „Gestalten“. Keine Treffer: „Nichts gefunden für „…““.

5. Detailseite /community/<id>

Große Karte, Titel, Herausgeber, Tags, Datum (dd.mm.yyyy), Anzeigename („von “ bzw. „von Anonym“). Knöpfe: Herunterladen (PNG 1536 × 969, Dateiname aircard-<titel-slug>.png), Als Vorlage gestalten (→ /gestalten?vorlage=<id>), Melden (Dialog mit Grund: „Unangemessen“, „Echte Kartendaten“, „Urheberrecht/Marke“, „Spam“, „Sonstiges“). Autor (angemeldet, ownerId = ich): zusätzlich Zurückziehen. Admin (Label admin): Ausblenden/Einblenden, Löschen, Anzeige „ Meldungen“ und Liste der Gründe. Nicht vorhanden/ausgeblendet für mich: „Diese Karte ist nicht (mehr) verfügbar.“ + Link zur Galerie.

6. Veröffentlichen (aus „Meine Karten“)

Kachel-Aktion „Veröffentlichen“ → Dialog: Titel (Pflicht, ≤ 60), Herausgeber/Bank (optional, ≤ 40), Tags (optional, bis 5, je ≤ 20, Komma-getrennt, kleingeschrieben gespeichert), Anzeigename (optional, ≤ 30, Standard leer → „Anonym“; nie die E-Mail), Pflicht-Checkbox „Das Bild gehört mir bzw. ich darf es verwenden, und es enthält keine echten Kartendaten.“ Ablauf: Karte rendern (PNG 1536 × 969 + WebP-Vorschau 480) → beide Dateien in Bucket gallery-images hochladen (Rechte read("any"), delete("user:<ich>")) → POST /api/veroeffentlichen → Zeile entsteht. Scheitert die API, löscht der Client die zwei Dateien wieder. Danach zeigt die Kachel „Veröffentlicht“ + „Ansehen“ + „Zurückziehen“; ist die Karte ausgeblendet: „Ausgeblendet (gemeldet)“. Eine private Karte kann einmal veröffentlicht sein; „Erneut veröffentlichen“ = Zurückziehen + Veröffentlichen.

7. Daten (Appwrite, Projekt 6ac2130a00289343d6d5, Setup-Skript erweitert)

Tabelle gallery (rowSecurity an, keine Tabellenrechte für Clients):

Spalte Typ Inhalt
title string 60, Pflicht Titel
issuer string 40 Herausgeber/Bank
tags string 120 Tags, Komma-getrennt, klein
displayName string 30 Anzeigename oder leer
imageFileId string 36, Pflicht PNG im Bucket gallery-images
previewFileId string 36, Pflicht WebP-Vorschau
sourceCardId string 36 private Karte, aus der veröffentlicht wurde
ownerId string 36, Pflicht Nutzer-ID des Autors
hidden boolean, Standard false ausgeblendet
reportCount integer, Standard 0 Anzahl verschiedener Melder
searchText string 300 title + issuer + tags, klein; Volltext-Index

Indizes: Volltext auf searchText; Key-Index auf ownerId; Key-Index auf hidden. Zeilenrechte (setzt nur die API): sichtbar ["read(\"any\")"]; ausgeblendet ["read(\"user:<owner>\")","read(\"label:admin\")"].

Bucket gallery-images: Bucket-Recht create("users"), fileSecurity an, max. 5 MiB, Endungen png/webp. Dateirechte beim Hochladen read("any"), delete("user:<owner>"); beim Ausblenden setzt die API read("user:<owner>"), read("label:admin"), delete("user:<owner>").

Tabelle reports (rowSecurity aus, Tabellenrecht read("label:admin"), Schreiben nur API): galleryId string 36, reason enum (unangemessen, kartendaten, urheberrecht, spam, sonstiges), reporterHash string 64 (HMAC-SHA256 der IP mit Server-Salz), Index auf galleryId.

Lesen im Client: Galerie listRows(gallery, [equal('hidden', false), orderDesc('$createdAt'), limit(24), cursorAfter]), Suche zusätzlich search('searchText', q); eigene Veröffentlichungen equal('ownerId', me); Admin-Filter „Gemeldet/Ausgeblendet“ greaterThan('reportCount', 0) bzw. equal('hidden', true).

8. API-Dienst aircard-api

Node 22, ohne Abhängigkeiten, Container webklar-aircard-api (node:22-alpine, init: true, mem_limit 128m), Traefik-Router Host(`aircard-ios.org`) && PathPrefix(`/api`) Priorität 200, Port 8088. Spricht Appwrite intern (http://webklar-appwrite/v1, Header X-Appwrite-Project, X-Appwrite-Key = AIRCARD_API_KEY). Secrets aus .env: AIRCARD_API_KEY (neuer Key aircard-api, Scopes rows.read/write, files.read/write), AIRCARD_API_SALT (zufällig, 32 Byte hex). Antworten JSON, Fehlertexte deutsch.

Endpunkt Auth Wirkung
POST /api/veroeffentlichen {title, issuer?, tags?, displayName?, imageFileId, previewFileId, sourceCardId} Nutzer-JWT prüft Nutzer, Feldlängen, dass beide Dateien existieren, im Bucket gallery-images liegen und delete("user:<ich>") tragen, und dass für sourceCardId noch keine Veröffentlichung existiert → legt Zeile an (sichtbar) → 201 {id}
POST /api/zurueckziehen {id} Nutzer-JWT (Autor oder Admin) löscht Zeile, beide Dateien und ihre Meldungen
POST /api/melden {id, reason} keine Rate-Limit 10/Stunde je IP (Speicher), ein Bericht je Melder-Hash und Karte (sonst 200 {schonGemeldet:true}), speichert Meldung, setzt reportCount = Anzahl verschiedener Melder; ab 3 → Ausblenden (Zeilen- und Dateirechte, hidden=true)
POST /api/admin/sichtbarkeit {id, sichtbar} Admin-JWT Einblenden (öffentliche Rechte, hidden=false, reportCount=0, Meldungen löschen) oder Ausblenden
GET /api/gesundheit keine {ok:true}

JWT-Prüfung: GET /v1/account mit X-Appwrite-JWT → Nutzer ($id, labels); Admin = labels enthält admin. Echte Client-IP aus X-Real-Ip (Traefik). Körper max. 8 KB. Alles andere 404. Wichtig: JWT muss in den Auth-Methoden des Projekts an bleiben (die frühere Klickliste sagte „aus“ – wird korrigiert).

9. Admin (Kenso)

Kenso bekommt in der Appwrite-Console das Label admin (Auth → Users → Konto → Labels). Die Website erkennt es über account.get().labels und zeigt Admin-Knöpfe und den Galerie-Filter „Gemeldet“/„Ausgeblendet“.

10. Was Kenso selbst erledigt

  1. Label admin am eigenen Konto setzen.
  2. Neuen API-Key aircard-api (Scopes rows.read, rows.write, files.read, files.write) anlegen und mit dem verdeckten read -s-Befehl in .env eintragen (nicht im Chat).
  3. Danach den alten, im Chat geposteten Key löschen (Setup-Skript nutzt ab dann aircard-api nicht – es bekommt bei Bedarf einen neuen Provisioning-Key).
  4. Öffentlichen Buy-Me-a-Coffee-Link schicken.

11. Fehlerbehandlung

Veröffentlichen scheitert (Netz/API/Validierung) → hochgeladene Dateien löschen, Meldung im Dialog („Veröffentlichen fehlgeschlagen – …“). Doppelt veröffentlicht → „Diese Karte ist schon veröffentlicht.“ Melden doppelt → „Danke, du hast diese Karte schon gemeldet.“ Rate-Limit → „Zu viele Meldungen – bitte später nochmal.“ Nicht angemeldet bei Veröffentlichen → Login-Dialog (bestehendes withLogin). API nicht erreichbar → „Der Dienst ist gerade nicht erreichbar.“ Galerie-Ladefehler → Hinweis + „Nochmal versuchen“.

12. Tests

  • Vitest: Router (neue Routen, Umleitung /?karte=), Theme (System/Speichern), Banner (ausblenden, leeres DONATE_URL), Galerie-Abfragen (Queries für Liste/Suche/Admin-Filter), Veröffentlichen-Ablauf mit Fake-API (Aufräumen bei Fehler), Such-Text-Bildung, Tags-Normalisierung.
  • node:test für aircard-api: JWT-Prüfung (Fake-Appwrite), Validierung, Datei-Eigentum, doppelte Veröffentlichung, Melden (Dedup, Rate-Limit, Auto-Ausblenden bei 3, Rechte-Strings), Admin ein/aus, Zurückziehen (Autor/Admin/fremd → 403).
  • Setup-Skript: --check deckt gallery, reports, gallery-images inkl. Indizes ab.
  • Live: Veröffentlichen, Suche, Detailseite, Herunterladen, Als Vorlage, Melden aus 3 verschiedenen Netzen/Fake-IPs (über die API mit X-Real-Ip-Test im Container), Admin aus-/einblenden, Startseite bei 1440 × 900 und 390 × 844 (erste Kartenreihe angeschnitten), Hell/Dunkel.

13. Nicht im Umfang

Offizielle/Bank-Karten, Likes/„Beliebt“-Sortierung, Kommentare, Englisch, Anleitung-Seite, Highlights, öffentliche Profilseiten, Bearbeiten veröffentlichter Karten (nur zurückziehen + neu), E-Mail-Benachrichtigung an Kenso bei Meldungen.

14. Risiken

  • Öffentliche Inhalte ohne Vorab-Freigabe: Kenso haftet ab Kenntnis; Melden + Auto-Ausblenden + Admin-Löschen mildern. Ohne Kontaktadresse/Impressum fehlt ein Weg für Beschwerden von außen (Kenso-Entscheidung, hier vermerkt).
  • Marken/Urheberrecht: Nutzer können Bank-Kartenmotive hochladen; Pflicht-Checkbox + Melden-Grund „Urheberrecht/Marke“.
  • Speicher: keine Kontingente je Nutzer; Bucket 5 MiB je Datei. Missbrauch → Admin löscht; Auth-Limits in der Console (Kensos Schritt 3) empfohlen.
  • Rate-Limit im Speicher geht bei Neustart verloren (Dedup über reports bleibt).