# AirCard-Website – Implementierungsplan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Kartenmotiv-Editor unter https://aircard-ios.org (Bild hochladen, Vorlage/Logo/Hell-Dunkel wählen, PNG 1536 × 969 herunterladen) mit Konten und gespeicherten Karten über ein eigenes Appwrite-Projekt. **Architecture:** Statische Vite-+-TypeScript-Seite ohne Framework im Unterordner `web/` von `knso/AirCardIOS`. Reine Kartenlogik (`web/src/card/*`) ist von UI und Appwrite getrennt und mit Vitest getestet. Konten/Speichern über das Appwrite-Web-SDK gegen `https://aircard-ios.org/v1`, das Traefik per Header-Matcher an Appwrite weiterreicht (first-party Cookie). Deploy nach dem Vorbild ticket.webklar.com: Gitea-Push → Webhook → Build-Container → `out/` → nginx. **Tech Stack:** TypeScript 6.0.3, Vite 8.3.2, Vitest 5.0.3, happy-dom 20.14.5 (Tests), appwrite (Web-SDK) 22.4.1, @fontsource-variable/nunito-sans 5.3.0, Node 22 (Build-Container `node:22-alpine`, lokal 22.23.3), nginx:alpine, Traefik v3.6 (vorhanden), Appwrite 1.8.1 (vorhanden). **Spec:** `docs/superpowers/specs/2026-10-04-aircard-website-design.md`. Jeder Task liest die Spec-Abschnitte, die er umsetzt. ## Global Constraints - Arbeitskopie: `/home/webklar/apps/aircard-ios.org/dev` (Branch `main`, Remote `https://git.webklar.com/knso/AirCardIOS.git`). Pfade unten relativ dazu, außer sie beginnen mit `/`. - Versionen **exakt** pinnen (kein `^`): `vite 8.3.2`, `vitest 5.0.3`, `typescript 6.0.3`, `happy-dom 20.14.5`, `appwrite 22.4.1`, `@fontsource-variable/nunito-sans 5.3.0`. - Kein UI-Framework, keine externen CDNs/Fonts zur Laufzeit. - Alle sichtbaren Texte auf Deutsch; Wortlaut aus der Spec bzw. aus diesem Plan. - Karte: `CARD_W = 1536`, `CARD_H = 969`, Rand `56`, Vorschau-Radius `57` (nur Vorschau, nie im Export). - Upload: nur `image/png`, `image/jpeg`, `image/webp`, max. `5_242_880` Byte. - Details-Farben: Hell `#FFFFFF`, Dunkel `#111111`. Kartengrund ohne Bild bzw. unter transparenten Bildern: `#1B2738`. - Appwrite-IDs: Projekt `aircard`, Datenbank `aircard`, Tabelle `cards`, Bucket `card-images`. - Frontend-Endpoint: `import.meta.env.VITE_APPWRITE_ENDPOINT ?? location.origin + '/v1'`, Projekt `import.meta.env.VITE_APPWRITE_PROJECT_ID ?? 'aircard'`. - Dateien aus Appwrite **nie** über `getFileView`/`getFilePreview`-URLs laden (Projekt als Query, kein Header → trifft die Traefik-Route nicht), sondern über `fetchFile` (Task 9). - Produktion: vor jeder Änderung an `/home/webklar/apps/docker-compose.yml` Backup `docker-compose.yml.bak-2026-10-04-aircard`; **nie** nacktes `docker compose up -d`; `webklar-appwrite*`, `webklar-traefik` und `preview-portal` nicht neu starten oder ändern; keine Secrets in Ausgaben, Logs oder Commits. - Konten anlegen und Passwörter auf der Live-Seite eingeben macht **Kenso**, nicht Claude. Claude arbeitet danach in Kensos angemeldeter Chrome-Sitzung weiter. - Tests: `cd web && npx vitest run` (Host-Node 24); Typprüfung `cd web && npx tsc --noEmit`; Deploy-Tests `node --test deploy/webhook-server.test.js`. - Zahlen mit Nachkommastellen in Tests mit `toBeCloseTo(wert, 1)` vergleichen. - Commit-Nachrichten enden mit `Co-Authored-By: Claude Opus 5.5 `. - Subagents: Modell `sonnet` (Implementierung/Review), `haiku` für kleine Nachprüfungen. ## Review Focus - **Transparentes PNG hochgeladen** → exportiertes PNG ist deckend (Grund `#1B2738`, Canvas ohne Alpha). Test in Task 4. - **iPhone-Foto als HEIC oder > 5 MiB** → klare deutsche Meldung, Editor unverändert. Test in Task 5. - **Sitzung läuft zwischen Seitenaufruf und Speichern/Laden ab (401)** → Login-Dialog, danach wird die Aktion genau einmal wiederholt. Tests in Task 9. - **Doppelklick/-tipp auf „Speichern“** → genau eine Karte. Test in Task 9. - **Neues Bild nach Verschieben/Zoomen eines anderen Bildes** → Ausschnitt wird zurückgesetzt bzw. begrenzt, keine leeren Streifen im PNG. Tests in Task 4 und Task 5. --- ### Task 1: Projektgerüst `web/` **Files:** - Create: `.gitignore`, `README.md` (Kurzfassung, wird in Task 10 ergänzt), `web/package.json`, `web/package-lock.json`, `web/tsconfig.json`, `web/vite.config.ts`, `web/index.html`, `web/public/favicon.svg` (Platzhalter, Task 3 ersetzt ihn), `web/src/main.ts`, `web/src/styles.css`, `web/src/pages/router.ts`, `web/src/vite-env.d.ts` - Test: `web/src/pages/router.test.ts` **Interfaces:** - Produces: `type RouteId = 'editor' | 'cards' | 'reset'`; `matchRoute(pathname: string): RouteId`; `navigate(path: string): void`; `startRouter(render: (route: RouteId, root: HTMLElement) => void | (() => void)): void` (ruft den Cleanup der vorherigen Seite, fängt Klicks auf `a[data-link]` ab, reagiert auf `popstate`). DOM-Gerüst in `index.html`: `
`, `
`, `