13 KiB
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
localStorageaircard.donateBannerClosed): „☕ Gefällt dir AirCard-IOS? Spendier uns einen Kaffee – damit das Projekt weiterläuft.“ + Link „Unterstützen →“. Ziel =DONATE_URLausweb/src/config.ts. IstDONATE_URLleer, werden Banner und Spenden-Knopf nicht angezeigt (bis Kenso den öffentlichen Linkbuymeacoffee.com/<name>liefert). - Kopfzeile: Logo + „AirCard-IOS“ (→
/), Navigation „Community“ (→/#community), „Gestalten“, „Meine Karten“; Suchfeld „Titel, Tags oder Herausgeber“ (Enter →/?q=…#community); Knopf „☕ Unterstützen“ (wennDONATE_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 inlocalStorageaircard.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 Beispielmotivweb/public/hero-beispiel.png, markenfrei, von Claude gestaltet) mit dezenter Schwebe-Animation (prefers-reduced-motionrespektieren). - 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
- Label
adminam eigenen Konto setzen. - Neuen API-Key
aircard-api(Scopesrows.read,rows.write,files.read,files.write) anlegen und mit dem verdecktenread -s-Befehl in.enveintragen (nicht im Chat). - Danach den alten, im Chat geposteten Key löschen (Setup-Skript nutzt ab dann
aircard-apinicht – es bekommt bei Bedarf einen neuen Provisioning-Key). - Ö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, leeresDONATE_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:
--checkdecktgallery,reports,gallery-imagesinkl. 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
reportsbleibt).