# Help center (help.html)

## Aufgabe
Eine Help-Center-Seite bauen, stark am expo.dev-Stil orientiert — im bestehenden
CloakDrop-Designsystem, das die expo.dev-Tokens bereits 1:1 übernimmt.

## Plan
- [x] Designsystem kartieren: geteiltes CSS (Zeilen 31–1569, byte-identisch auf allen Seiten),
      Nav-/Footer-/Cookie-/JS-Boilerplate isolieren
- [x] Pflicht-Skills laden (`emil-design-eng`, `design-taste-frontend`)
- [x] Recherche: echte expo.dev-Maße (Typo-Skala, Shell-Breite, Card- und Suchmuster)
      + Produktwahrheit aus index/docs/pricing/status/roadmap/opensource/changelog
- [x] `help.html` aus den geteilten Slices + neuem Abschnitt-CSS/Markup/JS zusammensetzen
- [x] „Help center"-Platzhalter (`href="#"`) in Nav-Dropdown und Footer aller Seiten verdrahten
- [x] Playwright: hell/dunkel, 360–1440 px, alle Interaktionen, Konsolenfehler
- [x] Adversarialer Review über 7 Lenses, jeder Befund von einem Skeptiker widerlegt
- [x] Bestätigte Befunde fixen und erneut verifizieren

## Aufbau der Seite
1. Hero: Badge, H1 (48 px / −2 px = expo `heading-4xl`), Suchfeld mit ⌘K/Ctrl-K-Hinweis, 5 Vorschlags-Chips
2. Suchergebnisse: client-seitiger Filter über alle 34 Artikel, Treffer-Markierung, Leerzustand
3. Kategorien-Bento: 6 Karten asymmetrisch (2 breit, 3 mittel, 1 versetzt mit zweispaltiger Liste)
4. Kontakt: zentrierte 660-px-Spalte mit einem Formular
5. Pre-Footer-CTA (geteiltes `.cta-section`, voll­breit wie auf index.html)

### Kontaktformular
Themen-Chips (Sending / Links / Preview build / Security / Account), mitwachsendes
Textfeld mit Zeichenzähler ab 75 %, optionales Browser-Feld. Absenden validiert inline
und setzt eine `mailto:`-Nachricht im Client des Nutzers zusammen — die Preview hat
keinen Server, also wird auch nichts von der Seite verschickt. Der Button tauscht seinen
Inhalt hinter einem kurzen Blur, der Haken zeichnet sich über `stroke-dashoffset`,
nach 3,6 s steht wieder der Ausgangszustand.

## Review — Ergebnis
41 Behauptungen erhoben, 25 vom Skeptiker widerlegt, **16 bestätigt und behoben**:

| # | Schwere | Befund | Fix |
|---|---------|--------|-----|
| 1 | hoch | `--text-quaternary` erreicht im Hellmodus nur 3,30:1 auf 9 Info-Labels | auf `--text-secondary` (5,65–5,94:1) |
| 2 | hoch | Live-Region lag in einem Container, der genau im Leerfall `display:none` wird | dauerhafte `.hc-sr`-Region im Hero, eigener Ansagetext |
| 3 | hoch | Pre-Footer-CTA steckte in `.hcwrap` (1080 px) → 64-px-Headline klemmte auf Telefonen | CTA als direktes `<main>`-Kind, wie index.html |
| 4 | hoch | Aus changelog.html geerbtes `class="active"` am Footer-Changelog-Link | entfernt, `Help center` ist der einzige Aktiv-Marker |
| 5 | hoch | `.faq-plus` rotierte nie: geteilte Regel verlangt `.faq`-Vorfahr | `.hcq[open] .faq-plus::before/after` ergänzt |
| 6 | mittel | Einzeltaste `/` als Kurzbefehl (WCAG 2.1.4) | entfernt, nur noch ⌘/Ctrl+K |
| 7 | mittel | `.hcstart` blieb zwischen 641–900 px zweispaltig | Umbruch in den 900-px-Block verschoben |
| 8 | mittel | `prefers-reduced-motion` verlor auf Spezifität gegen `.hcchan > svg.hcchan-arrow` | Selektor angeglichen, Hover-Transforms neutralisiert |
| 9 | mittel | dito Akkordeon-Icon (JS-Lens) | siehe 5 |
| 10 | mittel | Dokument-Klick-Handler schluckte Cmd/Ctrl/Shift-Klick | Modifier-Guard vor `closest()` |
| 11 | mittel | Suchindex listete 9 Antworten doppelt | Dedupe über `href`, Antworttext wandert in denselben Eintrag |
| 12 | mittel | Trefferzahl wurde vor dem Einblenden geschrieben | Reihenfolge in `run()` gedreht |
| 13 | mittel | 2 Artikel-Links zeigten auf Ziele ohne die Antwort | umgehängt; neue Antwort `#hcq-addfiles` |
| 14 | niedrig | `.hcchan:hover` verdunkelte im Dark Mode (Elevation invertiert) | Hover auf `--bg-element` |
| 15 | niedrig | fehlendes `</head>` | ergänzt |
| 16 | niedrig | Label „Preview build" vs. „Preview" im selben UI | auf „Preview" vereinheitlicht |

Zusätzlich ohne Befund korrigiert: `.hccard > p` überschrieb per Spezifität `.hccard-foot`
(Zählerzeile in falscher Größe/Farbe und ohne `margin-top:auto`), Support-Karten erbten die
Browser-Linkfarbe, Ergebniszeilen trugen ein „extern"-Pfeilicon, `--brand-blue` (im Dark Mode
nicht definiert) durch das themenfähige `--green-11` ersetzt, `.scroll-reveal`-Override exakt
an status.html angeglichen.

## Verifikation
- 9/9 Seiten: keine Konsolenfehler, kein horizontaler Überlauf (360 / 375 / 700 / 860 / 1440 px)
- Geteiltes CSS in help.html byte-identisch zu changelog.html
- Kontrast der Mikro-Labels: 5,65–5,94:1 hell, 8,21–9,29:1 dunkel
- Suche, Chips, Clear, Escape, ⌘K, Gruppenfilter, Akkordeon, Deep-Link (`help.html#hcq-…`) geprüft
- Alle internen Anker lösen auf, alle verlinkten Seiten existieren

## Spätere Änderungen auf Zuruf
- Support-Bereich: die Karten „Docs" und „Roadmap and changelog" entfernt, Email in ein
  Formular überführt; die Seitenkarte „Worth mentioning" ebenfalls entfernt, Sektion auf
  eine zentrierte 660-px-Spalte gestellt.
- Sektionen „If you only read six answers" und „When something goes wrong" entfernt.
  Dabei mussten die 10 Artikel-Links, die auf `#hcq-*` zeigten, auf echte Ziele umgehängt
  werden (index#faq, index#security, index#login, docs#sending, docs#troubleshooting,
  pricing) — sonst wären tote Anker zurückgeblieben. Verwaistes CSS (`.hcstart`, `.hctro`,
  `.hcq`) und JS (Gruppenfilter, `openAnswer`, Deep-Link-Handler, Index-Dedupe) ebenfalls
  entfernt. Der Chip „Decryption failed" traf danach keinen Artikeltitel mehr und wurde
  durch „Lost link" ersetzt.

- Menü-Platzhalter ersetzt (Nav „Resources › Learn" und Footer „Resources", alle 9 Seiten):
  „Security whitepaper" und „Verify a transfer" ersetzt durch **How it works**
  (`docs.html#overview`) und **What a link contains** (`docs.html#the-link`). Beide alten
  Einträge zeigten auf `#` und benannten Dinge, die es nicht gibt — das Whitepaper steht
  auf der Roadmap unter „Up next", „Verify a transfer" existiert nirgends.
  Drei verworfene Zwischenschritte: „FAQ" (Nav „Product › Transfer" hatte den Link bereits),
  „Anatomy of a drop" (auf Wunsch durch zwei allgemeinere Einträge ersetzt) und
  „Security model" (auf Wunsch wieder entfernt — damit ist auch die Ziel-Dublette auf
  `index.html#security` weg). Die Spalte hat jetzt vier Einträge: Help center, How it works,
  What a link contains, Developer API. Icons aus dem Haus-Set: Kompass, Kettenglied.
  Die Roadmap-Karte „Security whitepaper" und die Docs-Überschrift „Anatomy of a drop"
  bleiben unangetastet — das ist echter Inhalt.

## Offen / bewusst nicht getan
- Die 34 Artikel verweisen auf reale Anker in docs/index/pricing/status/roadmap. Eigene
  Artikelseiten gibt es nicht — das entspricht der Konvention der Bestandsseiten
  (docs.html-Sidebar nutzt ebenfalls Platzhalter).
- `--text-quaternary` ist auch außerhalb des Help-Centers knapp im Kontrast. Das ist ein
  seitenweites Token; nur die neuen Regeln wurden angepasst.
- `status.html` ist eine vollwertige Seite ohne jeden Menüeintrag — sie hängt nur an der
  „All systems operational"-Pille. Wäre ein Kandidat für den nächsten freien Slot.
- „Developer API" und „About" zeigen weiterhin auf `#`. Nicht angefasst, weil nicht
  beauftragt; für die API gibt es auch kein echtes Ziel (Roadmap: „Exploring").
- Die geteilte Nav öffnet Dropdowns beim Hover und schließt sie beim anschließenden Klick
  wieder. Vorbestehendes Verhalten auf allen Seiten, nicht angerührt.

---

# Developer API (api.html)

## Aufgabe
Eine Seite fuer den Menuepunkt „Developer API", strukturell an https://elevenlabs.io/api
orientiert, im bestehenden CloakDrop-Designsystem.

## Ausgangslage, die den Zuschnitt bestimmt hat
Die API existiert nicht. roadmap.html fuehrt „Developer API" unter **Exploring** — der
spekulativsten Stufe — und die Roadmap sagt selbst „Honest about order, silent about dates".
Eine Seite mit fertigem API-Key-Flow, SDKs und SOC-2-Badges haette der Kernaussage der Seite
widersprochen. Deshalb: volle Referenzstruktur, aber „Exploring"-Badge im Hero, ein
Statusmarker in der Endpunktsektion, ehrliche CTAs („Read the design", „Follow on the
roadmap") und in jedem Codebeispiel eine erste Kommentarzeile „Planned API - not callable".

## Was uebernommen und was weggelassen wurde
Uebernommen: Hero mit Headline links / Absatz rechts unten ausgerichtet, direkt darunter
das Code-Panel ohne Abstand, Section-Kopf + Karten-Raster, zweispaltiger Erklaerblock,
FAQ mit Ueberschrift links und Akkordeon rechts, Pre-Footer-CTA.
Weggelassen: Kundenlogo-Leiste und Testimonial-Karussell — beides waeren erfundene Kunden.

## Aufbau
1. Hero: „Exploring"-Badge, H1 (48 px / -2 px), CTA-Paar, Absatz rechts
2. Code-Panel: cURL / JavaScript / Python mit gleitendem Tab-Thumb, Syntax-Hervorhebung,
   fester Hoehe (470 px) und Kopierknopf
3. Endpunktliste: sieben Zeilen, Methode / Pfad / Beschreibung
4. Capability-Raster: sechs Karten
5. Constraint-Split: „What the relay sees" gegen „What that does to the API"
6. Zugang: ehrlicher Preisblock, an die realen Plaene gekoppelt
7. FAQ: zehn Entwicklerfragen
8. Pre-Footer-CTA

Die Codebeispiele sind gegen das echte Kryptomodell in index.html gebaut: AES-256-GCM,
4-MiB-Chunks, 12-Byte-IV aus 8-Byte-Praefix und 32-Bit-Big-Endian-Zaehler, Manifest bei
Zaehler 0, Dateichunks ab 1, unpadded base64url, Linkform `#t=<id>&k=<key>`, Manifest-Keys
v/created/files und name/size/type/start/chunks.

## Review — Ergebnis
47 Behauptungen ueber sieben Lenses erhoben, 24 vom Skeptiker widerlegt,
**23 bestaetigt und behoben**. Die wichtigsten:

| Schwere | Befund | Fix |
|---------|--------|-----|
| hoch | Endpunkttabelle las sich wie eine Referenz einer laufenden API; der Hero-CTA scrollt am einzigen Hinweis vorbei | Statusmarker in die Sektion selbst |
| hoch | Sprach-Tabs ohne roving tabindex trotz ARIA-Pfeiltastenmuster | tabindex im Markup und in `select()` |
| hoch | Kommentarfarbe im Code nur 3,14:1 | `--api-com` auf #6f7178 / #8a8e97 |
| hoch | Hero brach mobil so um, dass Buttons vor dem Erklaertext standen | `display: contents` + `order` |
| hoch | Aus changelog.html geerbtes `class="active"` am Footer-Changelog-Link | entfernt |
| hoch | Schnelle Tabklicks stapelten unabgebrochene WAAPI-Animationen | `cancel()` + Token-Guard + `oncancel` |
| hoch | Pfeiltasten animierten, obwohl der Kommentar das Gegenteil versprach | `select(i, instant)` |
| mittel | `//` in einem Regex-Literal wurde als Kommentar getoktet und frass den Zeilenrest | Lookbehind im Highlighter |
| mittel | Endpunktzahl widersprach sich („Seven endpoints" gegen „no seventh") | Zaehlsprache entfernt |
| mittel | `--red-11` ohne Dark-Wert, DELETE nur 3,70:1 | eigenes `--api-del`-Tokenpaar |
| mittel | Panelhoehe sprang beim Sprachwechsel um mehrere hundert Pixel | feste Hoehe mit Innenscroll |
| mittel | Abgelehnter Clipboard-Zugriff verwies auf Ctrl+C ohne Auswahl | `selectPre()` in beiden Pfaden |
| mittel | Nav-Dropdown mit `.active` ohne passende Regel | Regel aus help.html uebernommen |
| mittel | rechtsbuendige Endpunkttexte brachen ab ~915 px mehrzeilig um | eigener 914-px-Block |
| mittel | reduced-motion liess Press-Scales und den Blur laufen | Block erweitert |

Nach dem Neubau selbst gefunden und behoben: doppeltes `<style>`-Tag (haette den gesamten
`:root`-Block verschluckt) und eine beim Neubau verlorene `.apicon-cap`-Regel.

## Verifikation
- 10/10 Seiten ohne Konsolenfehler und ohne horizontalen Ueberlauf (360 / 640 / 700 / 901 /
  914 / 915 / 1440 px)
- Panelhoehe konstant 470 px ueber alle drei Sprachen
- Schnelles Tabklicken: genau ein Panel sichtbar, passend zum gewaehlten Tab
- Pfeiltaste wechselt sofort, Fokus bleibt synchron
- Kontraste: Kommentar 4,63 hell / 5,41 dunkel, DELETE 5,21 / 6,65, Hero-Notiz 5,94 / 9,29
- Kopieren landet echt in der Zwischenablage
- Geteiltes CSS byte-identisch zu changelog.html

## Offen
- „About" im Footer zeigt weiterhin auf `#`; kein echtes Ziel vorhanden.
- `status.html` hat weiterhin keinen Menueeintrag.

---

# Glossary + Verify a transfer (glossary.html, verify.html)

## Aufgabe
Die Menue-Slots von „How it works" und „What a link contains" sollten nicht auf Docs-Anker
zeigen, sondern auf **zwei eigenstaendige Unterseiten**. Aus fuenf Vorschlaegen gewaehlt:
Glossary und Verify a transfer.

## glossary.html
16 Begriffe A-Z, jeder mit Term, Mono-Meta (der technische Wert, wo es einen gibt),
1-2 Saetzen Definition und optionalem Querverweis. Aufbau: zentrierter Hero mit Filterfeld
und Cmd/Ctrl-K-Hinweis, klebrige A-Z-Sprungleiste, Definitionsliste als 232px/1fr-Raster,
Leerzustand, CTA. Der Filter blendet Eintraege, leere Buchstabengruppen **und** die
zugehoerigen Sprungbuchstaben aus.

Begriffe: base64url, chunk, cloak, drop, expiry, fragment, GCM tag, IV, key, manifest,
preview build, relay, transfer id, WebCrypto, zero-egress, zero knowledge. Jede Zahl ist
aus dem echten Quellcode belegt.

## verify.html
Vier Checks als Timeline (Schiene mit Nummer und Linie), jeder Schritt mit einem
„What you do"- und einem „What you should see"-Feld: (1) Netzwerk-Tab beobachten,
(2) den Link lesen, (3) ein Byte kaputt machen, (4) den Speicher inspizieren.
Danach „What this does not prove" mit fuenf Punkten.

## Review — Ergebnis
42 Behauptungen ueber sechs Lenses, 19 widerlegt, **23 bestaetigt und behoben**.
Der wichtigste war ein Ehrlichkeitsfehler auf der Seite, deren ganzer Zweck Ehrlichkeit ist:

| Schwere | Befund | Fix |
|---------|--------|-----|
| hoch | Check 4 bestritt „no per-file size" — aber jeder Chunk liegt als eigener Eintrag im Speicher, also ist die Dateilaenge ablesbar (Ciphertext minus 16 Byte GCM-Tag) | Verneinung ersetzt durch die Tatsache, plus fuenfter Punkt „It does not hide how much you sent" |
| hoch | Suchfeld hatte `outline: none` und einen Ring mit 1,15:1 | zusaetzlicher `:has(input:focus-visible)`-Outline mit 2px |
| hoch | `.glbar a` deklariert `display: inline-flex` und schlaegt damit das `[hidden]`-Attribut — der Filter versteckte nie einen Buchstaben | `.glbar a[hidden] { display: none }` |
| hoch | `.glbar` ist ein `<nav>` und erbte `nav { height: 68px }` | `height: auto` |
| hoch | `.glgroup { scroll-margin-top: 62px }` war toter Code (geteiltes `section[id]` gewinnt) und die Leiste ist mobil dreizeilig | Offset kommt jetzt per JS aus `bar.offsetHeight` in `scrollPaddingTop` |
| mittel | Hero-Satz „Every security claim … you can watch happen" wurde 65 Zeilen spaeter widerlegt | auf „The claims that can be checked today" eingegrenzt |
| mittel | base64url-Eintrag legte nahe, der IV-Praefix stecke im Link | umformuliert |
| mittel | „deletion" als heutige Operation genannt | „Deleting one early is planned with the relay" |
| mittel | Live-Region feuerte bei jedem Tastenanschlag, schwieg beim Zuruecksetzen | 400ms entprellt, Reset wird angesagt |
| mittel | `.vfcard p` schlug per Spezifitaet `.vfcard-cap` (10,5px-Caption rendert als 14px) | `.vfcard > p:not(.vfcard-cap)` |
| mittel | `.vfpair`/`.vflimit` brachen bei 900px um, obwohl der Container noch 852px breit war | Umbruch auf 700px, plus `max-width: 62ch` |
| mittel | `.vfmono` mit `white-space: pre` schnitt bei 360px 43% ab | Unterschrift raus aus dem Block, `pre-wrap` unter 640px |
| mittel | Cmd/Ctrl+K ohne Hinweis und ohne Schutz fremder Eingabefelder | `.glkbd` ergaenzt, Guard fuer INPUT/TEXTAREA/SELECT und offenen Cookie-Dialog |
| mittel | `.glsearch` war eine driftende Kopie von `.hcsearch` (6 Abweichungen) | angeglichen; help.html hat denselben Fokusring und dieselben Tokens bekommen |
| niedrig | Buchstabensprung aktualisierte URL nicht und verschob den Fokus nicht | `history.replaceState` + temporaeres `tabindex="-1"` |
| niedrig | Reveal-Verzoegerungen liefen nach dem `order`-Wechsel rueckwaerts | `sr-d3` plus Remapping in der Media Query |

## Verifikation
- 12/12 Seiten ohne Konsolenfehler und ohne horizontalen Ueberlauf (360/500/640/700/720/900/1440 px)
- Leiste 51px statt 68px; `scrollPaddingTop` 65px, aus der echten Hoehe berechnet
- Sprung zu einem Buchstaben laesst bei jeder Breite 38-69px Luft unter der Leiste
- Filter „counter": 3 Eintraege, 3 Gruppen, 3 Sprungbuchstaben, Ansage „3 terms match"
- Clear sagt „Filter cleared. Showing all 16 terms." an
- Ctrl+K im Newsletter-Feld stiehlt den Fokus nicht mehr
- `.vfmono` bei 360px 0px abgeschnitten
- Kontraste 5,94:1 hell / 9,29:1 dunkel
- Geteiltes CSS in beiden neuen Dateien byte-identisch zu changelog.html

## Offen
- „About" im Footer zeigt weiterhin auf `#`.
- `status.html` hat weiterhin keinen Menueeintrag.

---

# About (about.html)

## Aufgabe
Eine About-Seite. Der Footer-Link „About" zeigte auf `#` und war damit der letzte tote
Menuelink der Seite.

## Der Zuschnitt
Eine About-Seite ist die Stelle, an der Websites Dinge erfinden: Team, Gruendungsjahr,
Standort, Nutzerzahlen, Funding. Zu keinem davon gibt es auf dieser Seite eine belegbare
Angabe. Statt sie zu erfinden, ist der Umstand selbst zum Inhalt geworden.

Aufbau:
1. Hero: „Why this exists, and what it refuses to do."
2. Essay (vier Absaetze, 62ch Zeilenmass): warum E2EE-Transfer, und vor allem was das
   Design dafuer aufgibt — kein Passwort-Reset, keine Wiederherstellung, keine
   serverseitige Suche. „Lose the link and the files are gone. That is not a gap in the
   design. It is the design, seen from the side that costs something."
3. Vier Prinzipien als Karten, jede mit einer „Proof:"-Zeile, die auf die Seite verlinkt,
   auf der man es nachpruefen kann.
4. „Where it stands": vier Zeilen in den exakt vier Stufen der Roadmap.
5. „What an About page usually has, and this one does not": vier Auslassungen mit Begruendung.
6. Kontaktkarte, CTA.

## Review — Ergebnis
31 Behauptungen ueber fuenf Lenses, 19 widerlegt, **12 bestaetigt und behoben**.
Bemerkenswert: **keine einzige erfundene Person, Firma, Zahl oder Datumsangabe.** Die zwei
Treffer der Fabrication-Lens waren Abweichungen gegenueber der Roadmap:

| Schwere | Befund | Fix |
|---------|--------|-----|
| hoch | „Up next" listete server-side accounts, die Roadmap fuehrt sie unter „In progress"; erste Stufe hiess „Runs today" statt „Shipped" | beide Zeilen an die Roadmap angeglichen |
| hoch | Die vier Karten in „The honest part" nutzten `<b>` statt `<h3>` — der Abschnitt war fuer Ueberschriften-Navigation unsichtbar | auf `<h3>` umgestellt, CSS mitgezogen |
| hoch | Inline-Links nur ueber Farbe unterscheidbar (1,17:1 gegen den Fliesstext) | dauerhafte Unterstreichung mit `color-mix`, Hover verstaerkt sie |
| hoch | Zwei Links in `.abnot-item` trafen keine einzige Regel — UA-Default inklusive Besucht-Lila | eigene Regel ergaenzt |
| hoch | **Vierte Spezifitaetskollision derselben Art**: `.abcard > p` (0,1,1) schlug `.abproof` (0,1,0) | `.abcard > p:not(.abproof)` |
| mittel | „no previews" widersprach der Roadmap, die lokale Vorschauen fuehrt | auf serverseitige Funktionen eingegrenzt |
| mittel | „the one principle on this page that costs something" widersprach dem Essay, der mehrere Kosten aufzaehlt | Superlativ entfernt |
| mittel | `.abrow > a` zog sich unter 700px ueber die ganze Zeile, Hover feuerte auf Leerraum | `justify-self: start` |
| mittel | `.footer-col a.active` war eine wortgleiche Dublette der geteilten Regel | geloescht |
| niedrig | `.abproof a` fiel auf der gehoverten Karte auf 4,48:1 | Hover-Farbe auf #1e53d0 |

## Verifikation
- 13/13 Seiten ohne Konsolenfehler und ohne horizontalen Ueberlauf (360/640/700/900/1440 px)
- Ueberschriftenfolge in `<main>`: h1 h2 h2 h3×4 h2 h2 h3×5 h2 — keine uebersprungene Stufe
- Kontraste 5,10-5,94:1 hell, 8,26-9,29:1 dunkel
- Alle vier Zeilen von „Where it stands" gegen roadmap.html gegengelesen
- Geteiltes CSS byte-identisch zu changelog.html
- **Der Footer hat jetzt keinen toten Link mehr** (Privacy, Cookie settings und Imprint
  liegen in der Bottom-Leiste, nicht in den Spalten)

## Offen
- `status.html` hat weiterhin keinen Menueeintrag; sie haengt nur an der Status-Pille.
- Privacy, Cookie settings, Imprint, GitHub und die Social-Icons zeigen weiter auf `#`.

---

# Contact (contact.html)

## Aufgabe
Eine Kontaktseite mit Formular. Der Footer-Link „Contact" war ein direktes `mailto:`.

## Aufbau
1. Hero: „One address. No ticket queue."
2. Formular mit fuenf Anlaessen als Chips (Support, Security, Open source, Feedback,
   Something else). Die Wahl aendert Betreffzeile **und** eine Hinweisbox darunter, die den
   Anlass erklaert &mdash; Security warnt ausdruecklich davor, einen Drop-Link mitzuschicken.
   Dazu optionaler Betreff, mitwachsendes Textfeld mit Zeichenzaehler.
3. Seitenspalte: „What happens then" (ehrlich: kein Ticketsystem, keine Reaktionszeit) und
   „Reporting a security problem" (kein Bug-Bounty, kein veroeffentlichter Schluessel).
4. Drei Karten „Three places that answer faster than we do".
5. CTA.

Wie auf help.html hat die Preview keinen Server, also setzt das Formular eine `mailto:`
zusammen. Der Knopf heisst deshalb „Open in your mail app" und nicht „Send".

## Review — Ergebnis
31 Behauptungen ueber fuenf Lenses, 13 widerlegt, **18 bestaetigt und behoben**.
Zwei davon betrafen **help.html**, gefunden von einem eigenen Duplikations-Lens:

| Schwere | Befund | Fix |
|---------|--------|-----|
| hoch | Der Fokus-Outline existierte nur auf contact.html; help.htmls Formularfelder hatten weiter `outline: none` plus einen 1,15:1-Ring | Outline auch dort ergaenzt |
| hoch | Der Erfolgs-Haken konnte auf **beiden** Seiten nie animieren: `display: none` -> `block` im selben Frame startet keine Transition | bleibt im Layout, `width`/`opacity` statt `display` |
| hoch | Hinweisbox war verwaist: kein `id`, kein `aria-live`, nichts verwies darauf &mdash; der Wechsel war rein visuell | `id` + `aria-live`, `aria-describedby` von Chipgruppe und Betreff |
| hoch | `--red-11` hat keinen Dark-Wert; Fehlertext lag bei 3,27:1 | eigenes `--cf-red`-Tokenpaar |
| hoch | `mailto:`-URL ohne Laengenschutz, Erfolg wurde unbedingt gemeldet | Guard bei 1900 Zeichen mit ehrlicher Meldung |
| hoch | `grow()` lief nie bei Resize, waehrend `overflow-y` auf hidden stand &mdash; Text konnte unerreichbar abgeschnitten werden | Resize- und Font-Ready-Bindung |
| hoch | **Fuenfte Spezifitaetskollision derselben Art**: `.cfsec-head > p` schlug `.cfeyebrow` | `:not(.cfeyebrow)` |
| mittel | „Draft opened in your mail app" behauptete etwas Unbeobachtbares | umformuliert, plus dauerhafter Rueckfallweg mit echtem mailto-Link |
| mittel | `role=alert` und `role=status` sagten denselben Fehler doppelt an | Fehler nur ueber `role=alert`, Text erst im Fehlerfall eingesetzt |
| mittel | Das 1500-Zeichen-Limit war fuer Screenreader unsichtbar | `cf-limit`-Hinweis via `aria-describedby`, Ansage beim Anschlagen |
| mittel | Ausgewaehlter Chip nur ueber 1,1:1 Fuellung erkennbar | invertiert (Primaerfarbe) |
| niedrig | „answered in seconds" / „most questions" ueberzeichnete die Abdeckung | durch die pruefbare Zahl ersetzt: 34 Antworten |
| niedrig | Knopf hiess „Something else", verschickte aber „Other" | angeglichen |
| niedrig | `swap()`-Timer ungetrackt, `grow()` mass mit reserviertem Scrollbalken | beides gefixt |
| niedrig | Platzhalterfarbe wich vom Rest des Systems ab | auf `--text-quaternary` |

## Verifikation
- 14/14 Seiten ohne Konsolenfehler und ohne horizontalen Ueberlauf (360/640/700/900/1440 px)
- Alle fuenf Anlaesse: Chip, Hinweis und Betreff-Platzhalter wechseln synchron
- Leer absenden -> Fehler, `aria-invalid`, Fokus zurueck ins Feld
- Autogrow 136 -> 361 px, Zaehler ab 75 %, Erfolg und Ruecksetzung nach 3,6 s
- Kontraste 5,65-5,94:1 hell, 8,21-9,29:1 dunkel
- Geteiltes CSS in contact.html und help.html byte-identisch zu changelog.html

## Stand der Navigation
Alle Footer-Spalten sind jetzt vollstaendig verlinkt. Offen bleiben nur die Eintraege der
Fusszeile (Privacy, Cookie settings, Imprint), die Social-Icons und der GitHub-Link &mdash;
sowie `status.html`, das weiterhin keinen Menueeintrag hat.

### Nachtrag: contact.html auf ein einziges Formular reduziert
Auf Zuruf alle Texte und Karten entfernt. Weg sind: die Hero-Nebentexte, der Sektionskopf,
die Seitenspalte („What happens then", „Reporting a security problem"), die drei Karten
„Three places that answer faster than we do" und der Pre-Footer-CTA. Uebrig bleiben ein
zentrierter Hero (Eyebrow + H1) und **ein** Formular, zentriert auf 820 px, mit groesseren
Feldern: Eingaben 15,5 px bei 14/16 px Innenabstand, Textfeld ab 220 px Hoehe, Chips
8/16 px, Knopf 54 px.

Bewusst behalten: die Hinweisbox unter den Anlass-Chips. Sie ist kein Fliesstext, sondern
die Funktion der Chips &mdash; ohne sie waeren die Chips nur eine Betreffauswahl, und die
Security-Warnung („Never paste a drop link") faellt ersatzlos weg, weil die Karte, die sie
sonst noch enthielt, entfernt wurde.

Verwaistes CSS und die zwei nicht mehr genutzten Icon-Konstanten mitentfernt; ein beim
Umbau verschlucktes `</div>` wurde von der Tag-Balance-Pruefung gefangen und repariert.
Seiten-CSS jetzt 108 statt 190 Zeilen, keine Klasse ohne Vorkommen im Markup.

---

# Cookie-Popup: Kaestchen wird Schalter

Das Analytics-Kaestchen im Cookie-Dialog ist jetzt eine Schalter-Pille, an-Zustand im
Markenblau (`--green-9`: #2563f0 hell, #4b83ff dunkel).

Umsetzung: der native `<input type="checkbox">` bleibt und wird per `appearance: none` und
einem `::after`-Knopf gezeichnet. Damit funktionieren Tastatur, Formularwert und das
vorhandene JS (`analytics.checked`) unveraendert. Zusaetzlich `role="switch"`, damit
Screenreader den Zustand als an/aus statt als angehakt melden. Bahn 38x22, Knopf 16 px,
Bewegung 220 ms auf `--ease-out-strong`, beim Druecken eine iOS-artige Stauchung auf 19 px.
Fokus bekommt denselben 2-px-Outline wie die Formularfelder, `prefers-reduced-motion`
schaltet Bewegung und Stauchung ab.

Die Aenderung liegt im geteilten Block und wurde in allen 14 Dateien identisch angewandt.

## Zwei Dinge, die beim Pruefen aufgefallen sind
- **Aus-Zustand war unlesbar:** weisser Knopf auf `--bg-hover` ergab 1,22:1 &mdash; man sah
  nicht, ob der Schalter an oder aus ist. Hell laeuft die Bahn jetzt auf
  `--text-quaternary` (3,3:1 zum Popup und zum Knopf). Dunkel bleibt sie zurueckhaltend,
  weil dort der helle Knopf den Zustand traegt (11,5:1).
- **Blau erschien im Dunkelmodus nicht:** `html[data-theme="dark"] .cookie-switch` (0,2,1)
  schlaegt `.cookie-switch:checked` (0,2,0), die Bahn blieb grau. Eine dunkelspezifische
  `:checked`-Regel (0,3,1) behebt es. Gefangen vom Test, nicht vom Auge.

## Gemessen
| Zustand | Bahn / Popup | Knopf / Bahn |
|---------|--------------|--------------|
| hell aus | 3,30:1 | 3,30:1 |
| hell an | 5,10:1 | 5,10:1 |
| dunkel aus | 1,28:1 | 11,53:1 |
| dunkel an | 4,86:1 | 3,03:1 |

Klick und Leertaste schalten, Fokusring 2 px sichtbar, „Save" liest den Wert weiterhin aus,
14/14 Seiten ohne Konsolenfehler und ohne Ueberlauf, geteiltes CSS weiterhin identisch.


---

# privacy.html & imprint.html

Auftrag: „Erstelle eine Privacy & Imprint Seite".

## Ausgangslage
Beide Links standen in der Fussleiste jeder Seite auf `href="#"`. Ein Impressum ist ein
Rechtsdokument: Firmenname, Rechtsform, Anschrift, Register, USt-IdNr., Vertretungs-
berechtigter, Aufsichtsbehoerde. Nichts davon existiert im Projekt. Erfunden wurde nichts;
die Identitaetsfelder stehen sichtbar leer.

## Faktenerhebung vor dem Schreiben
Jede technische Aussage der Datenschutzseite ist aus dem Quelltext belegt:

| Behauptung | Beleg |
|------------|-------|
| 0 Netzwerkaufrufe | kein `fetch`, `XMLHttpRequest`, `sendBeacon`, `WebSocket`, `EventSource`; kein `<script src>`, kein externes Stylesheet, kein `@import`, kein Remote-Bild |
| 0 Cookies | `document.cookie` kommt in keiner der 16 Dateien vor |
| 4 localStorage-Schluessel | `cloakdrop-theme`, `-consent`, `-account`, `-session` |
| 2 IndexedDB-Stores | `indexedDB.open('cloakdrop', 1)`, `transfers` (keyPath `id`) und `chunks` (Key `id + ':' + n`) |
| Konto-Inhalt | `{ email, username, hash: await hashPw(pw) }`, `hashPw` = SHA-256 ueber `'cloakdrop:' + pw` |
| Transfer-Satz | `{ id, manifest: <Chiffrat>, ivPrefix, created: Date.now() }` |
| Ablauf | `EXPIRE_MS = 7 * 24 * 3600 * 1000` |

`cloakdrop-consent` haette der erste Scan beinahe verpasst: der Schluessel wird ueber die
Variable `KEY` geschrieben, nicht als Literal. Zweiter Durchlauf ueber alle Aufrufformen.

## Zwei Befunde, die in die Seiten eingeflossen sind
- **Nichts wird je geloescht.** Kein `delete`, kein `clear`, kein `deleteDatabase` im ganzen
  Projekt. Der 7-Tage-Ablauf verweigert nur das Oeffnen; die verschluesselten Chunks bleiben
  bis zum manuellen Loeschen der Website-Daten liegen. Die Fehlermeldung der App sagt aber
  „Cloaked transfers vanish after 7 days". Das steht jetzt als eigener Hinweiskasten auf
  privacy.html &mdash; siehe Offen.
- **`hashPw` ist keine Passwort-Hashfunktion.** SHA-256 mit festem Praefix, ohne Salt und
  ohne Arbeitsfaktor, also offline sehr schnell durchprobierbar. Der Eintrag verlaesst das
  Geraet nie, deshalb begrenzt auf den, der ohnehin Zugriff auf das Browserprofil hat.
  Steht als Warnkasten in „The preview account", mit dem Rat, kein anderswo genutztes
  Passwort zu verwenden.

## Aufbau
Beide Seiten teilen ein Geruest (`.lg*`): Kopf ueber die volle Breite, links eine klebende
Sprungleiste, rechts die Textspalte. Muster aus changelog.html/docs.html uebernommen.

privacy.html, 9 Abschnitte: Kurzfassung (Zahlenleiste 0/0/4/2) &middot; was gespeichert wird
(zwei Tabellen) &middot; das Cookie-Banner setzt kein Cookie &middot; die zwei Formulare &middot;
das Vorschau-Konto &middot; was die Seite nicht weiss &middot; was sich mit dem Relay aendert
(als „gilt heute nicht" gerahmt) &middot; Rechte samt Loeschanleitung &middot; was offen ist.

imprint.html, 6 Abschnitte: Status &middot; Pflichtangaben als Feldliste mit sichtbaren
Platzhaltern &middot; was jedes Feld bedeutet &middot; Kontakt &middot; Haftung und Urheberrecht
&middot; Streitbeilegung.

## Verdrahtung
- Fussleiste aller 16 Seiten: Privacy und Imprint zeigen auf die neuen Dateien, die jeweils
  eigene Seite traegt `aria-current="page"`.
- `index.html`: der Link „Privacy Policy" im Registrierungsformular zeigte ebenfalls auf `#`
  und wurde mitgezogen.
- „Cookie settings" bleibt auf `#`; der Link ist ueber seine id funktionsfaehig.
- Neue Regel `.footer-legal a[aria-current="page"]` im geteilten Block, in allen 16 Dateien
  identisch ergaenzt. Der Generator zieht sie selbst nach, damit ein Neubau sie nicht verliert.

## Beim Pruefen gefunden und behoben
| Befund | Ursache | Fix |
|--------|---------|-----|
| Zwischenueberschriften im Relay-Kasten voellig ungestylt | `.lgsec > h3` ist Direktkind; die h3 liegen in `.lgfuture` eine Ebene tiefer | Nachfahren-Selektor `.lgsec h3` |
| Sprungleiste markierte am Seitenende den falschen Eintrag | eigene Fassung waehlte den ersten schneidenden Abschnitt, ein frueherer holte die Markierung zurueck | Hausmuster aus docs.html: Ueberschriften in einem schmalen Band beobachten, Markierung rastet ein |
| Feldhinweise im Impressum 3,30:1 | `--text-quaternary`, obwohl der Text Information traegt | `--text-secondary`, gemessen 5,65:1 hell / 8,55:1 dunkel |
| Tabellen rissen auf dem Telefon grosse Luecken | Zeilenhoehe kam von den Spalten rechts ausserhalb des Scrollfensters | unter 640 px zur Liste gestapelt, jede Zelle traegt ihre Spaltenueberschrift in `data-label`; ARIA-Rollen halten die Tabellensemantik |
| Absatz klebte an der folgenden Tabelle | `.lgscroll`/`.lgfields` mit 4 px Aussenabstand | 15 px |

Vorbeugend beachtet: kein zweites `<style>`-Tag (der Slice bringt es mit), kein geerbtes
`class="active"` am Footer, `section[id].lgsec` (0,2,1) schlaegt das geteilte
`section[id]` (0,1,1) fuer den Ankersprung, klassenlose Absaetze per `p:not([class])`
angesprochen, damit klassierte Absaetze nicht verlieren.

## Verifikation
- 16/16 Seiten ohne Konsolenfehler; ohne horizontalen Ueberlauf bei 360/640/900/1280/1440 px,
  ausser den zwei unten genannten vorbestehenden Faellen.
- Schlechtester gemessener Kontrast auf beiden neuen Seiten: 5,65:1 hell, 8,55:1 dunkel.
- Sprungleiste klebt bei 96 px, markiert am Seitenende korrekt den letzten Abschnitt.
- Ankersprung landet unter der Navigation.
- Alle internen Links und Anker der neuen Seiten aufgeloest.
- Geteilter CSS-Block in allen 16 Dateien byte-identisch, Style-Tags ueberall 2/2,
  Tag-Balance beider neuer Dateien ausgeglichen.

## Offen
- **`docs.html` laeuft bei 360 px um 88 px und bei 900 px um 17 px ueber, `pricing.html` bei
  360 px um 3 px.** Nachweislich vorbestehend: mit zurueckgesetzten Aenderungen exakt
  dieselben Werte. Ursachen: `.dnav-right` (Docs-Navigation), die Footer-Newsletter-Spalte
  bei genau 900 px, und die Vergleichstabelle `.cmp` mit 560 px Mindestbreite ohne
  Scroll-Container. Nicht angefasst, weil ausserhalb des Auftrags.
- **Identitaetsangaben fehlen bewusst:** Betreiber, Rechtsform, Anschrift, Vertretung,
  Telefon, Register, USt-IdNr., inhaltlich Verantwortlicher, Aufsichtsbehoerde, Hoster,
  Auftragsverarbeiter. Vor einer oeffentlichen Veroeffentlichung auszufuellen und
  anwaltlich pruefen zu lassen.
- **„Terms of Service"** im Registrierungsformular von `index.html` zeigt weiter auf `#`.
  Es gibt keine AGB-Seite; nicht beauftragt.
- **Widerspruch im Produkt:** die App meldet beim Ablauf „Cloaked transfers vanish after
  7 days", loescht aber nichts. Entweder die Meldung praezisieren oder das Loeschen
  nachziehen. privacy.html benennt den Widerspruch derzeit offen.
- `status.html` haengt weiterhin nur an der Statuspille, ohne Menueeintrag.


## Nachtrag: Umbau auf einspaltigen Fliesstext

Rueckmeldung: „Mache einfach nur Text untereinander wie hier", mit den Expo-Rechtsseiten
als Vorlage. Beide Seiten wurden vom Seitenraster auf eine einzige Textspalte umgebaut.

Entfernt: die klebende Sprungleiste samt Scrollspy-JS, die Zahlenleiste 0/0/4/2, die
Hinweiskaesten, der Rahmen um den Relay-Abschnitt, die gestrichelten Platzhalter-Pillen,
der Abschluss-Button. Ebenso beide Tabellen: vier Spalten brachen in einer 716-px-Spalte
auf drei bis fuenf Zeilen um. Der Speicherbestand steht jetzt als verschachtelte Liste,
so wie es die Vorlage auch macht.

Geblieben ist: Titel, Datumszeile mit Kalendersymbol, Vorspann, dann nur `h2`, `h3`, `p`
und Listen. Textspalte 764 px, zentriert.

Damit ist auch das gesamte responsive Tabellen-CSS entfallen (Stapeln unter 640 px,
`data-label`, ARIA-Rollen) &mdash; es gibt keine Tabellen mehr.

Beim Umbau korrigiert: im Impressum und in „What is not settled yet" war zunaechst der
gesamte Vorspann fett, inklusive „&mdash; not filled in". Jetzt traegt nur der Feldname
die Auszeichnung.

Geprueft: 16/16 Seiten ohne Konsolenfehler und ohne Ueberlauf (ausser den zwei
vorbestehenden Faellen in docs.html und pricing.html), schlechtester Kontrast 5,94:1 hell
und 9,29:1 dunkel, geteiltes CSS weiterhin in allen 16 Dateien byte-identisch.


---

# Status-Pille: schraeger Pfeil

Auftrag: „Fuege hier in den Button so einen schraegen Pfeil nach oben rechts hinzu",
bezogen auf die Pille „All systems operational" in der Fussleiste.

Die Pille steht im geteilten Footer und kommt genau einmal in jeder der 16 Seiten vor.
CSS und Markup wurden deshalb ueberall byte-gleich ergaenzt.

## Umsetzung
- SVG 24er-Box, dargestellt bei 13 px &mdash; dieselbe Groesse wie `.tp-chevron`, das
  vorhandene kleine Icon des Hauses.
- Farbe ueber `currentColor` plus `opacity: 0.55` statt eigener Farbe. Damit zieht der
  Pfeil in beiden Themes automatisch mit und erbt die Farbtransition der Pille; eine
  eigene Regel je Theme entfaellt.
- Hover: Deckkraft auf 1 und `translate(1.5px, -1.5px)`, 200 ms auf `--ease-out-strong`.
  Bewusst klein &mdash; der Effekt wird oft gesehen.
- Die Hover-Regel liegt in demselben `@media (hover: hover) and (pointer: fine)` wie die
  vorhandene Hover-Regel der Pille und nutzt wie diese `a.footer-status`.
- `prefers-reduced-motion` schaltet nur die Bewegung ab, die Deckkraft bleibt. Der Block
  steht spaeter in der Quelle und gewinnt bei gleicher Spezifitaet (0,3,1) ohne `!important`.
- `aria-hidden="true"`: rein dekorativ, die Bedeutung traegt der Text daneben.

## Zwei optische Korrekturen nach dem Messen
- **Strichstaerke 1.8 -> 2.2.** Bei 13 px Darstellung aus einer 24er-Box ergibt 1.8 knapp
  1 px; der Textstamm misst bei 12 px/500 rund 1,2 px. Der Pfeil wirkte duenner als die
  Schrift daneben.
- **Rechts stand mehr Luft als links: 17 px gegen 11,6 px.** Ursache ist der Leerraum, den
  die Glyphe in ihrer eigenen Box mitbringt und der sich zur Polsterung der Pille addiert.
  Glyphe von 7.5..16.5 auf 6.75..17.25 vergroessert und `margin-right: -3px` gesetzt.
  Danach gemessen: 13 px links, 13,7 px rechts, 9,7 px zwischen Text und Pfeil.

## Verifikation
16/16 Seiten tragen den Pfeil, 13 px, Deckkraft 0,55, `aria-hidden`, keine Konsolenfehler.
`prefers-reduced-motion` liefert `transform: none` bei weiterhin animierter Deckkraft.
Geteilter CSS-Block weiterhin in allen 16 Dateien byte-identisch.

## Anmerkung
Auf `status.html` zeigt die Pille auf die Seite, auf der man bereits steht &mdash; der Pfeil
verspricht dort einen Sprung, der keiner ist. Das war schon vor dieser Aenderung so
(`href="status.html"` im geteilten Footer). Ein `aria-current` nur dort wuerde den Footer
zwischen den Seiten unterschiedlich machen; deshalb unveraendert gelassen.


---

# Engine-Audit (gemessen, nicht nur gelesen)

Testaufbau: Playwright, Chromium, 9-MiB-Datei (3 Chunks) plus eine 0-Byte-Datei.
Skript liegt im Scratchpad als `engine.py`.

## Was nachweislich funktioniert
| Test | Ergebnis |
|------|----------|
| Rundlauf 9 MiB | entschluesselt byte-identisch zum Original |
| 0-Byte-Datei | erhaelt einen eigenen Chunk, laeuft durch |
| 1 Bit im Chiffrat gekippt | „Failed", **kein** Download ausgeloest |
| Chunk geloescht | „Failed", **kein** Download ausgeloest |
| falscher Schluessel | „The key doesn't fit" |
| Chunk-Schluessel | `<id>:1..4`, Manifest 218 Byte als Chunk 0 |

Der IV-Aufbau (8 Byte Zufall + 32-Bit-Zaehler) bindet jeden Chunk an seine Position.
Ein vertauschter oder verschobener Chunk scheitert an der GCM-Pruefung. Der Schluessel
ist pro Transfer frisch, IV-Wiederverwendung ist damit ausgeschlossen. Das ist solide.

## Was fehlt
1. **Kein Relay.** Ein Transfer existiert nur im erzeugenden Browser. Alles Weitere haengt
   daran.
2. **Download haelt die ganze Datei im RAM.** `parts.push(plain)` sammelt alle Chunks, dann
   kopiert `new Blob(parts)` erneut. Bei der beworbenen 500-MiB-Grenze rund 1 GB Spitze;
   „Download all" startet alle Dateien gleichzeitig. `showSaveFilePicker` ist im Testbrowser
   verfuegbar und ungenutzt.
3. **Es wird nie geloescht.** Kein `delete`, `clear` oder `deleteDatabase` im Projekt. Der
   Store `chunks` hat zudem keinen Index nach Transfer-Id; ein spaeteres Loeschen muesste
   ueber `IDBKeyRange.bound(id+':', id+':')` laufen.
4. **Keine Quota-Behandlung, keine Transaktion ueber den ganzen Transfer.** Jeder Chunk ist
   eine eigene IndexedDB-Transaktion. Bricht es mittendrin ab, bleiben die Chunks liegen und
   der Transfer-Satz wird nie geschrieben: unauffindbarer Muell.
5. **Die Empfangsansicht prueft nichts.** Sie liest das Manifest und verspricht „2 cloaked
   files &middot; 9.0 MB", ohne dass ein einziger Chunk existieren muss. Mit geloeschtem
   Chunk behauptet sie unveraendert Vollstaendigkeit; der Fehler faellt erst beim Klick auf.
6. **Der Schluessel bleibt in Adresszeile und Verlauf.** `route()` raeumt das Fragment nach
   `enterReceiveMode` nicht weg. Ein `history.replaceState` waere eine Zeile.
7. **Das Konto ist ein UI-Schalter.** `localStorage.setItem('cloakdrop-session','x')` fuehrt
   direkt in die App, `cloakdrop-account` wird dabei nie angelegt. Gemessen.
8. **Robustheit:** kein Abbrechen, kein Fortsetzen, `alert()` als Fehlerausgabe, keine
   Feature-Erkennung fuer `crypto.subtle` (ausserhalb eines Secure Context stirbt die App
   mit einer TypeError-Meldung), `parseHash` nutzt `kv.split('=')` ohne
   `decodeURIComponent`.
9. **Produktebene, die die Krypto hergaebe:** keine optionale Passphrase, kein View-once,
   kein Download-Zaehler, kein Widerruf durch den Absender, keine Kompression.


---

# dashboard.html

Auftrag: „Erstelle ein Dashboard das wichtig nur vom Aufbau dem Dashboard vom Bild gleicht,
aber vom Design das cloakdrop index.html design."

## Aufbau (aus dem Entwurfsbild)
Seitenleiste mit Wortmarke, vier Eintraegen (Active drops, Received, Expired, Settings) und
Tarifzeile unten &middot; Kopfleiste mit Suchfeld, einem Primaerknopf und Monogramm &middot;
Inhalt mit H1, Zaehlzeile und Tabelle Name / Recipient / Expires in / State / Aktion.

## Gestaltung (aus dem System)
Keine neue Farbe, kein neues Token. Zwei Entscheidungen, die vom Bild abweichen und
bewusst so sind:

- **Der Primaerknopf ist schwarz bzw. weiss**, nicht blau. `--btn-primary-bg` ist im System
  `#000` hell und `#fff` dunkel; Blau (`--green-9`) ist die Akzentfarbe fuer Links und
  Marken-Details, nicht fuer Flaechen. Wollte man das Blau des Bildes, waere es eine
  Zeile in `.dsh-new`.
- **Der Markenakzent sitzt im Symbol des aktiven Navigationseintrags**, nicht in dessen
  Flaeche &mdash; sonst konkurriert die Leiste mit dem Primaerknopf.

Das Suchfeld folgt dem Rezept von `.clsearch` aus changelog.html, der Theme-Umschalter ist
die unveraenderte geteilte Komponente, das Cookie-Banner ebenfalls.

## Interaktion
- Zeilenaktion „Copy link" erscheint beim Ueberfahren und bei Tastaturfokus. Sechsmal
  dasselbe Wort dauerhaft anzuzeigen waere Wiederholung ohne Information; ohne
  Hover-Faehigkeit steht sie dauerhaft.
- Suche filtert Name und Empfaenger, schreibt die Zaehlzeile mit („3 of 6 drops") und
  zeigt einen Leerzustand. Escape leert.
- Sortierung auf Name und Expires in, `aria-sort` an der Kopfzelle, der Pfeil dreht sich.
- Strg/Cmd+K fokussiert die Suche, wie das Feld es verspricht.
- Kopieren nutzt dieselbe Unschaerfe-Blende wie der Kopierknopf der Startseite.
- `prefers-reduced-motion` schaltet alle Uebergaenge ab.

## Ehrlichkeit
Die Zeilen sind Beispieldaten und tragen deshalb eine Marke „Sample data" neben der
Ueberschrift. Der Kopierknopf legt einen als Beispiel erkennbaren Link ab
(`#t=SAMPLE-…&k=SAMPLE`), keinen echten Schluessel.

**Wichtiger Vorbehalt, der aus der Kritik am Entwurfsbild stammt:** mit echten Daten koennte
diese Tabelle Dateinamen und Empfaenger gar nicht anzeigen. Das Manifest liegt verschluesselt
in IndexedDB, der Schluessel steckt nur im Link. Ein echtes Dashboard kann aus dem Bestand
nur Transfer-Id, Anlagezeitpunkt, Restlaufzeit und Chunk-Zahl lesen. Namen und Empfaenger
muessten aus einem clientseitig gefuehrten, separat verschluesselten Index kommen &mdash;
und der existiert nicht.

## Beim Bauen gefunden und behoben
| Befund | Ursache |
|--------|---------|
| Navigationseintraege auf 20px gequetscht | geteiltes `nav { height: 68px }` griff auf `<nav class="dsh-nav">`; `height: auto` ergaenzt |
| „Actions" stand sichtbar in der Kopfzeile | der geteilte Block hat keine `.sr-only`-Klasse; eigene `.dsh-sr` ergaenzt |
| Spaltenueberschrift „Expires in" in Mono | `.dsh-num` galt fuer `th` und `td`; Mono jetzt nur `td.dsh-num` |
| Cookie-Dialog verlor sein `</div>` | Schnittgrenze im Extraktor; jetzt mit Tag-Balance-Assertion je Slice abgesichert |
| ⌘K-Kuerzel hell bei 2,90:1 | `--text-quaternary` fuer Text, der Information traegt → `--text-secondary`, 5,22:1 |
| Etikett „Name" vor dem Dateinamen (mobil) | ueberfluessig, der Name ist die Kartenueberschrift |

## Verifikation
Keine Konsolenfehler, kein Ueberlauf bei 360/640/720/900/1040/1280/1440/1800 px, Tabelle
stapelt unter 720 px, Seitenleiste wird unter 1040 px zur waagerechten Leiste. Schlechtester
Kontrast 5,22:1 hell und 7,37:1 dunkel. Hover, Suche, Leerzustand, Sortierung, Strg+K,
Kopieren und der Theme-Umschalter einzeln geprueft. Geteilter CSS-Block in allen 17 Dateien
byte-identisch.

## Offen
Die Seite ist von nirgends verlinkt &mdash; sie ist ein Entwurf der App-Schale, keine
Marketingseite. Die vier Navigationseintraege zeigen auf `#`.


## Nachtrag: Umbau auf die Expo-Dashboard-Struktur

Rueckmeldung: „Viel mehr in diesem Stil", mit einem Bildschirmfoto des echten
Expo-Dashboards (expo.dev, Usage-Seite).

### Uebernommene Muster
| Vorbild | Umsetzung |
|---------|-----------|
| Keine waagerechte Kopfleiste | Seitentitel ist die erste Zeile des Inhalts; die frueher oben stehende Suche sitzt jetzt in der Seitenleiste |
| Wortmarke plus Glocke und Lupe | `.dicon`-Knoepfe; die Lupe fokussiert das Suchfeld |
| Umschalter in gerahmten Feldern | `.dsw`, Kontowahl „Personal" |
| Gruppierte Navigation mit Versalien-Ueberschriften | `.dsh-group`: ACCOUNT, SECURITY, SETTINGS &mdash; oberste Gruppe mit Symbolen, Untergruppen ohne, wie im Vorbild |
| Benutzerzeile unten mit Monogramm und ⋮ | `.dsh-user` |
| Zeitraumwaehler oben rechts | `.dbtn` „Last 30 days" |
| „Showing … from **Datum** through **Datum**" | Datumsangaben in `--text-default`, Rest sekundaer |
| Kennzahlkarten mit Kopfleiste, Export-Knopf und geteilten Zellen | `.dcard` / `.dstat`, zwei Karten: Drops und Transfer |
| Sekundaerknopf mit fuehrendem Kreissymbol | `.dbtn` mit `I_EXPORT` |
| „?"-Hilfe neben der Ueberschrift | `.dhelp` |

Dichte an das Vorbild angeglichen: Seitenleiste 240px statt 264, Navigationseintraege 32px
statt 40, Tabellenschrift 13,5px statt 14,5.

### Ehrlichkeit
Die Kennzahlen werden aus den Beispielzeilen **gerechnet**, nicht getippt: 1,9 GiB gesamt,
496 Chunks bei 4 MiB, groesster Drop 812 MiB. Damit koennen Karten und Tabelle sich nicht
widersprechen. Die Marke „Sample data" steht weiterhin neben der Ueberschrift.

Positive Laufweite ist im System sonst nicht vorgesehen; bei den 10,5px-Versalien der
Gruppenueberschriften ist sie noetig und im CSS als bewusste Ausnahme vermerkt.

### Beim Umbau gefunden und behoben
- **Theme-Umschalter verloren.** Beim Neubau der Seitenleiste ist `#theme-pill`
  herausgefallen; die geteilte Theme-Logik lief damit ins Leere und die Seite war nicht
  mehr umschaltbar. Zurueck in den Fuss der Leiste, und die Mobilleiste von Raster auf Flex
  umgestellt, damit er dort erreichbar bleibt. Vom Test gefunden, nicht vom Auge.
- **`.dsh-user span` griff auch auf das Monogramm** und gab ihm `flex: 1` &mdash; der Kreis
  lief zu einer 90px-Pille auseinander. Jetzt `> span:not(.dsh-avatar)`.
- Tabelle klebte an ihrer Zaehlzeile; 14px Abstand ergaenzt.

### Verifikation
Keine Konsolenfehler, kein Ueberlauf bei 360/640/720/900/1040/1280/1440/1800 px,
schlechtester Kontrast 4,86:1 hell und 6,43:1 dunkel. Hover, Suche, Leerzustand,
Sortierung ueber drei Spalten, Strg+K, Kopieren und der Theme-Umschalter einzeln geprueft.
Geteilter CSS-Block weiterhin in allen 17 Dateien byte-identisch.


### Nachtrag: zurueck auf den abgestimmten Aufbau

„Viel mehr in diesem Stil" hatte ich als Uebernahme der Expo-**Struktur** gelesen; gemeint
war die Optik. Kennzahlkarten, Kontoumschalter, Versalien-Gruppen, Benutzerzeile und
Zeitraumwaehler sind wieder raus, ebenso die Size-Spalte, die nur wegen der Karten da war.
Der Generator prueft jetzt, dass keine Klasse aus dem Umweg uebrig bleibt &mdash; und zwar
als Klassenname, nicht als Zeichenkette: die 156 kB Base64-Schrift im Kopf enthalten Folgen
wie "dsw" zufaellig.

### Zwei gemeldete Punkte

**„Die Spaltentexte sind sehr verschoben" &mdash; bestaetigt, zwei Fehler, beide dieselbe
Kollision.** `.dsh-table th` und `.dsh-table td` sind (0,1,1) und schlagen eine blosse
Klasse (0,1,0):

| Regel | erwartet | war tatsaechlich |
|-------|----------|------------------|
| `th.dsh-num` Ausrichtung | rechts | **links** &mdash; Kopf ueber rechtsbuendigen Werten |
| `.dsh-name` Farbe | `--text-default` | **`--text-secondary`** |

Der zweite ist mir in jedem Bildschirmfoto durchgerutscht: der Dateiname sah nur kraeftiger
aus, weil `font-weight: 500` ankam &mdash; die Farbe nie. Behoben mit
`.dsh-table td.dsh-name` und `.dsh-table th.dsh-num` (beide 0,2,1).

**Die Konsolenmeldung stammt nicht aus der Seite.** „Unsafe attempt to load URL … 'file:'
URLs are treated as unique security origins" tritt im echten Browser nicht auf: keine
Konsoleneintraege, Seitenleiste 264 px. Nachgeprueft: das Dokument enthaelt kein `fetch`,
kein `XMLHttpRequest`, kein `<iframe>`, kein `window.open`, keine Zuweisung an
`location.href`; das einzige `src` ist das Base64-Logo, und `location` wird nur gelesen, um
eine Zeichenkette zu bauen. Die Meldung kommt aus einer eingebetteten Vorschau, die
`file:`-Dokumente in einen Rahmen laedt &mdash; dort ist jedes `file:`-Dokument ein eigener
undurchsichtiger Ursprung.

### Dauerhafte Gegenmassnahme
`verify.py` hat jetzt eine **Spezifitaets-Wache**: sie vergleicht berechnete Stile gegen die
Absicht (Ausrichtung, Farbe, Hoehe, Position) statt sich auf das Auge zu verlassen. Diese
Fehlerklasse ist in dieser Sitzung achtmal aufgetreten und ist auf Bildschirmfotos
grundsaetzlich unsichtbar.


### Nachtrag: Link-Spalte

Wunsch: eine Spalte rechts, in der der Link halb sichtbar steht und kopiert werden kann.
Sie ersetzt die frühere Aktionsspalte mit dem Textknopf „Copy link".

**Der Schnitt ist Absicht, nicht Zufall.** Der Teil hinter `&k=` ist der
Entschluesselungsschluessel. Die Ellipse schneidet genau ihn weg: sichtbar bleiben Host und
Transfer-Id, gemessen 44 % der Zeichen am Desktop, 63 % gestapelt. Kopiert wird der
vollstaendige Link.

Aufbau: mono, 12px, in einem gerahmten Feld auf `--bg-element`, rechts durch eine Haarlinie
abgetrennt das Kopiersymbol. Beim Klick wird es kurz zum Haken - mit derselben
Unschaerfe-Blende wie der Kopierknopf der Startseite - und kehrt nach 1,4 s zurueck.

Zwei Fallstricke dabei:
- **`width` am `td` ist bei `table-layout: auto` nur ein Vorschlag.** Die Spalte wuchs auf
  die volle Linklaenge und schnitt gar nichts ab. Die feste Breite gehoert an das Feld
  selbst (`.dsh-link { width: 278px }`).
- **Der Schluessel-Platzhalter braucht die echte Laenge.** Mit `k=SAMPLE` war der Link zu
  kurz, um ueberhaupt abgeschnitten zu werden. Jetzt 43 Zeichen wie ein echter
  base64url-Schluessel, inhaltlich aber unmissverstaendlich
  (`SAMPLE_KEY_NOT_REAL_…`) - was in der Zwischenablage landet, soll nicht wie ein
  Schluessel aussehen.

Die Spezifitaets-Wache hat nebenbei tote CSS gefunden: `.dsh-sr` war seit dem Wegfall der
Aktionsspalte ohne Element und ist entfernt. Sie prueft jetzt zusaetzlich Feldbreite,
`text-overflow` und `white-space` der Linkzelle.

**Offen zur Entscheidung:** die natuerliche Ellipse laesst die ersten Zeichen des
Schluessels stehen. Wer das nicht will, maskiert stattdessen fest - `…&k=•••` - und zeigt
den Schluessel nie an. Das ist eine Zeile, aber eine Produktentscheidung.


### Nachtrag: mehr Leben

Wunsch: mehr Elemente und Farbe. Bewusst **nicht** als Farbregen &mdash; jede Farbe traegt
hier eine Aussage.

**Palette.** Weiterverwendet wird, was das System schon hat: der Markenakzent
`--green-9` (trotz des Namens blau) und das semantische Gruen des Statuspunkts in der
Fussleiste, das im geteilten CSS ausdruecklich als Ausnahme kommentiert ist. Neu ist genau
**eine** Stufe: eine Warnfarbe. Die fehlt dem System, und „laeuft heute ab" ist die eine
Information auf dieser Seite, die draengt. Die Tokens sind seitenlokal auf `.dsh` gesetzt,
der geteilte Block bleibt unangetastet.

**Neue Elemente**
| Element | Was es zeigt |
|---------|--------------|
| Kennzahlleiste, drei schmale Kacheln | Active 6 · Picked up 2 · Expiring today 1, gerechnet aus denselben Zeilen wie die Tabelle |
| Restlaufbalken je Zeile | wie weit ein Drop durch seine 7-Tage-Frist ist &mdash; blau, unter 24 h in der Warnfarbe |
| Zustandspille statt nacktem Wort | gruen „Picked up", neutral „Waiting" |
| Zaehler in der Navigation | dieselbe 6 wie in der Zaehlzeile, nur beim aktiven Eintrag |
| Einzug beim Laden | 300 ms, 35 ms Versatz je Zeile; das JS nimmt die Klasse danach weg, damit Sortieren nicht erneut animiert |

Die Kacheln sind bewusst schmale Streifen ohne Kopfleiste und ohne Export-Knopf &mdash;
nicht die Expo-Karten von vorhin, der Aufbau bleibt wie abgestimmt.

**Beim Pruefen gefunden:** die gruene Pille lag hell bei 4,23:1. Das Gruen ist jetzt eine
Stufe dunkler als der Punkt in der Fussleiste (`#18794e` statt `#218358`) &mdash; als Punkt
reicht der hellere Ton, als Pillentext nicht.

Die Spezifitaets-Wache loest Variablen jetzt **am Element** auf statt am Wurzelelement,
sonst waeren die seitenlokalen `.dsh`-Tokens nicht pruefbar gewesen. Sie deckt zusaetzlich
Warnfarbe, Pillenfarbe, Balkenbreite und die Abgrenzung „soon gegen normal" ab.


---

# received.html

Zweite Dashboard-Seite, aus demselben Geruest wie dashboard.html erzeugt. Die Seitenleiste
ist jetzt echt verdrahtet: „Active drops" und „Received" zeigen aufeinander, beide tragen
einen Zaehler.

## Eine Produktfrage vorab
Bevor die Seite gebaut wurde: **weiss der Empfaenger, von wem ein Drop kommt?**
Nachgesehen im Quelltext &mdash; das Manifest ist `{ v, created, files:[{name,size,type,
start,chunks}] }`. Kein Absenderfeld, nirgends. Und laut pricing.html brauchen Empfaenger
kein Konto.

Daraus folgt zweierlei, und beides steht als Randnotiz sichtbar auf der Seite statt
verschwiegen zu werden:
- Es gibt **keine Spalte „From"**. Ein Drop traegt keinen Absender.
- Eine Empfangsliste kann es nur als **lokale Historie** geben &mdash; die Geraete, die die
  Links geoeffnet haben. Wer sie fuehrt, lagert die Schluessel mit.

Ein Unterschied zur Senderseite ist dabei bemerkenswert: dort heisst „Picked up" nur
*jemand hat das Chiffrat abgerufen*. Hier ist der Zustand exakt, weil der eigene Browser
weiss, was er heruntergeladen hat.

## Spalten
Name · Opened · Expires in (+ Restlaufbalken) · State · Link. Statt des Empfaengers steht
der Zeitpunkt, zu dem der Link geoeffnet wurde. Zustaende: „Downloaded" (gruen) und
„Not downloaded" (neutral).

## Beim Bauen gefunden
- **Kachel und Pille nannten dasselbe verschieden:** die Kachel sagte „Downloaded", die
  Pille daneben „Picked up". `tiles()` bekommt das Wort jetzt als Parameter aus derselben
  Quelle wie die Pille.
- **„5 received drops" war umstaendlich** &mdash; die Ueberschrift sagt schon, worum es
  geht. Beide Seiten zaehlen jetzt schlicht „drops".
- **Ein eigener Textersatz hat die JS-Verkettung zerrissen** (`total + ' drops' · '`)
  und beide Seiten mit „Invalid or unexpected token" lahmgelegt. Der Konsolen-Check hat es
  sofort gemeldet; im Bildschirmfoto waere nur eine leere Zaehlzeile aufgefallen.

## Verifikation
Beide Seiten ohne Konsolenfehler, kein Ueberlauf bei 360/720/1040/1440/1800 px,
schlechtester Kontrast 4,84:1 hell und 5,38:1 dunkel. Suche, Leerzustand, Sortierung,
Kopieren und der Wechsel zwischen den Seiten geprueft. Geteilter CSS-Block jetzt in allen
18 Dateien byte-identisch.


---

# Beide Dashboard-Seiten neu: je eine eigene Gestalt

Auftrag: die Seiten sollen sich wirklich unterscheiden und im Zuschnitt von index.html
erstaunen. Vorher waren es zweimal dieselbe Tabelle mit anderer Ueberschrift.

## Zuerst nachgesehen, was index.html tatsaechlich hat
Nicht aus dem Gedaechtnis, sondern im Quelltext:
- **Aurora** &mdash; weich geblurrte Farbflaechen (40px Blur) *hinter* opaken Karten, blau
  `#2788f3` und warm `#f76809`, mit langsamem Eigenleben (9&ndash;14 s).
- **Orbit-Kacheln** &mdash; 25 % Radius, doppelter weicher Schatten, Mono-Label.
- **`state-in`** &mdash; Zustandswechsel mit Blur und 4px Versatz, 250 ms.
- **Glas** (`--glass-bg` + `blur(12px)`) nur fuer ein schwebendes Bedienelement.

Das deckt sich mit der Apple-Regel aus der geladenen Anleitung: **Glas ist Chrom, Inhalt
bleibt opak**, Minimalismus als Standard, ein Akzent. Die Aurora liegt deshalb hinter der
Karte, nie in ihr &mdash; kein Glasmorphismus auf Inhaltsflaechen.

## A · dashboard.html - Dringlichkeit
Ein Brennpunkt: der Drop, der als naechstes ablaeuft, als grosse opake Karte mit Aurora
dahinter. Dateiname in 30px, die Restzeit als 52px-Mono-Zahl, darunter Fortschrittsbalken,
Linkfeld und die Hauptaktion. Die drei Kennzahlen sind in den Kartenfuss gewandert &mdash;
eine Ebene weniger als eine eigene Kachelreihe. Der Rest folgt bewusst leise als Tabelle
unter einer 15px-Ueberschrift.

Beim Suchen tritt die Heldenkarte ab: sonst stuende dort ein Treffer, der mit der Suche
nichts zu tun hat.

## B · received.html - Ankunft
Kein Brennpunkt. Stattdessen nach Tagen gruppierte Zeilen (Today / Yesterday / Earlier),
jede mit einer **Dateiglyphe im Zuschnitt der Orbit-Kacheln** &mdash; 12px Radius, doppelter
weicher Schatten, Endung als Mono-Versalie. Keine Tabelle, keine Kacheln, keine grossen
Zahlen; Rhythmus und Weissraum tragen die Seite. Gruppen ohne Treffer verschwinden beim
Suchen mitsamt Ueberschrift.

## Beim Bauen gefunden
| Befund | Ursache |
|--------|---------|
| Fortschrittsbalken im Helden unsichtbar | `<span>` ist inline; ohne `display: block` wirkt `height` nicht |
| Trenner „·" hell bei 3,30:1 | ein Trennzeichen ist ein grafisches Element, kein Text &mdash; jetzt als gezeichneter 3px-Punkt (3:1 statt 4,5:1) |
| **Aurora lief trotz `prefers-reduced-motion` weiter** | `.dhero-glow span` (0,1,1) verliert gegen `.dhero-glow .g1` (0,2,0). Neunter Treffer desselben Musters in dieser Sitzung; nur der Test hat es gefunden |

## Verifikation
Beide Seiten ohne Konsolenfehler, kein Ueberlauf bei 360/720/860/1040/1440/1800 px,
schlechtester Kontrast 4,84:1 hell und 7,26:1 dunkel. Suche mit Leerzustand, Sortierung,
Kopieren, Gruppen-Ausblendung, Heldenkarte-beim-Suchen und `prefers-reduced-motion`
(Aurora *und* Zeileneinzug auf `none`) einzeln geprueft. Geteilter CSS-Block weiterhin in
allen 18 Dateien byte-identisch.


### Nachtrag: Active drops nach Vorlage, dann beruhigt

Die Heldenkarte ist jetzt waagerecht in drei Feldern: **Datei** (Glyphe, Name, Empfaenger
und Groesse) | **Restzeit** (Label, 44px-Mono-Zahl, Balken) | **Link und Aktion**
(„Secure transfer link", Feld, blauer Knopf „View drop"). Darunter drei Kennzahlkacheln mit
Symbol, darunter die Tabelle mit blauer Textaktion „Copy link" statt eines Linkfelds.

**Nicht uebernommen: das Linkformat.** Die Vorlage zeigt `cloakdrop.com/t/et-82ALv…`, also
einen Pfad. Der Schluessel muss aber hinter `#` stehen, weil Browser das Fragment nicht an
Server senden; als Pfad wuerde er uebertragen und die Zero-Knowledge-Eigenschaft waere weg.
Die Seite behaelt `#t=<id>&k=<key>` (gepruefft: 7&times; Fragmentform, 0&times; Pfadform).

**Blau statt schwarz beim Hauptknopf**, wie in der Vorlage. Weiss auf `--green-9` dunkel
(#4b83ff) misst nur 3,44:1 &mdash; deshalb `#2563f0` in beiden Themes, gemessen 5,10:1.

### Drei Rueckmeldungen danach umgesetzt
1. **„Waiting" entfernt.** Der Normalfall braucht keine funfmal wiederholte Pille; die Zelle
   bleibt leer, nur „Downloaded" wird gezeigt. Drei von fuenf Zeilen haben jetzt eine leere
   Zustandszelle.
2. **Gruen raus.** „Downloaded" ist neutral (`--bg-hover` / `--text-default`), die Aussage
   traegt das Haken-Symbol. Damit bleibt Orange die einzige zweite Funktionsfarbe neben dem
   Markenblau. Die Tokens `--dsh-ok` und `--dsh-ok-bg` sind entfallen; die Kennzahlkachel
   „Downloaded" traegt ihr Symbol jetzt in `--text-secondary`.
3. **„5 more, 6 in total" ersetzt** durch die Ueberschrift selbst: **„5 other active drops"**,
   beim Suchen „2 of 5 matching". Der separate Zaehler entfaellt.

Dazu: **Zeilen rund 10 % hoeher** (Polsterung 13&rarr;15px, Schrift 14,5&rarr;15px,
Kopfzeile 12,5&rarr;13px), damit der obere Bereich nicht so klein gegen die Gesamtflaeche
wirkt. Gemessene Zeilenhoehe 61px.

### Beim Bauen gefunden
- **Das Filmsymbol zeichnete ein 3&times;2-Gitter** statt eines Filmstreifens. Jetzt mit
  Perforation an beiden Raendern.
- **Die Zeilentrenner sprangen unter der Namensspalte.** Ursache: `display: flex` direkt auf
  der Tabellenzelle nimmt sie aus dem Tabellenlayout, und `border-collapse` setzt die Linien
  dann versetzt. Der Flex-Kasten sitzt jetzt innen; gemessen liegen alle fuenf Unterkanten
  auf derselben Linie.
- **Meine eigene Tag-Balance-Pruefung hat falsch gemeldet:** das `td` in einem CSS-Kommentar
  wurde als Tag gezaehlt. Kommentar umformuliert.
- **9 tote CSS-Regeln** (`.dsh-sum` / `.dsh-tile` aus der vorigen Fassung) entfernt, 1135
  Bytes.

### Verifikation
Beide Seiten ohne Konsolenfehler, kein Ueberlauf bei 360/720/860/1040/1180/1440/1800 px,
schlechtester Kontrast 4,87:1 hell und 5,10:1 dunkel. Suche mit Heldenkarte-Ausblendung und
Rueckkehr per Escape, Sortierung, Kopieren in beiden Knopfformen (Symbol und Text) und
`prefers-reduced-motion` geprueft. Geteilter CSS-Block weiterhin in allen 18 Dateien
byte-identisch.


### Production-Polish der Active-drops-Seite

Geaenderte Quellen: `css.py`, `css2.py`, `gen.py` im Scratchpad. `index.html`, das Design von
`received.html` und der geteilte CSS-Block blieben unangetastet.

| Punkt | Umsetzung |
|-------|-----------|
| 1 Inhaltsbreite | `.dsh-body` auf 1500px begrenzt und zentriert. Kopfleiste und Seitenleiste bleiben ueber die volle App-Breite. Tabelle nutzt weiterhin 1444px bei 1920. |
| 2 Sample-Badge | Aus der Oberflaeche entfernt; `DEV_BADGE = False` in gen.py haelt ihn fuer Demostaende bereit. |
| 3 Kennzahlkacheln | Drei umrandete Karten ersetzt durch eine Zeile: `6 active · 2 downloaded · 1 expires today`. Kein Rahmen, keine Flaeche, keine Symbole, keine Farbpunkte. |
| 4 Heldenkarte | Dreiteilung mit `space-between` statt Raster. Rechts: Label, gekuerzter Link, **„Copy link" als Hauptaktion**, „View details" als leise zweite. „View drop" entfaellt. |
| 5 Restlaufbalken | Aus der Tabelle entfernt; im Helden bleibt der dezente Indikator. |
| 6 Kopieraktionen | Ruhe sekundaerfarben, Hover/Fokus/Aktiv in Markenblau, Symbol zieht mit. Kein Pill, kein Rahmen. |
| 7 Zustandsspalte | Unveraendert: nur zwei „Downloaded", Rest leer, kein Gruen. |
| 8 Proportionen | Restzeit, Zustand und Aktion auf Inhaltsbreite; die Freiflaeche faellt an Name und Recipient. Aktion sitzt dadurch an der Tabelle statt am Fensterrand. |
| 9 Aurora | Deckkraft von 0,26&ndash;0,46 auf 0,16&ndash;0,30 gesenkt. Bleibt hinter der opaken Karte. |

### Drei echte Fehler waehrend der Umsetzung
- **Riesige Luecke in der Heldenkarte bei 1920px.** Das Raster gab der ersten Spalte den
  gesamten Ueberschuss; zwischen „812 MB" und dem Trennstrich standen rund 540px Leerraum.
  Behoben durch eine eigene Maximalbreite der Karte (1240px) plus `space-between`; beide
  Zwischenraeume sind jetzt gleich gross (171px bei 1920).
- **Umbruch ohne Gestaltung.** Die Aktionszone brach rechnerisch schon bei rund 1360px um,
  der Haltepunkt lag aber bei 1180px &mdash; dazwischen fehlten Trennstrich und Abstand.
  Haltepunkt auf 1360px gehoben.
- **Linkfeld ohne Auslassungspunkte.** `text-overflow: ellipsis` wirkt nicht auf einem
  Flex-Container; der Link wurde hart abgeschnitten. Als Block umgesetzt, jetzt 54 % sichtbar
  mit Ellipse.

Ausserdem ein **falscher Alarm meinerseits**: Bei Tastaturfokus schien die Kopieraktion die
Farbe nicht zu wechseln. Ursache war die Messung mitten im 160-ms-Uebergang, nicht das CSS.
Nach Abwarten: hell 96,100,108 &rarr; 37,99,240, dunkel 177,180,187 &rarr; 75,131,255.

### Abnahme
Kein Ueberlauf und keine Konsolenfehler auf beiden Seiten bei 360/420/640/720/860/1040/1180/
1440/1600/1800px. Suche „pdf" (Hero blendet aus), Escape (Hero kehrt zurueck), Sortierung
nach Name und Restzeit, Kopieren in Held und Tabelle mit Rueckmeldung, Tastaturfokus mit
2px-Umriss, `prefers-reduced-motion` (Aurora, Zeilen und Uebergaenge auf `none`).
Schlechtester Kontrast 4,87:1 hell und 5,10:1 dunkel. Linkformat unveraendert im Fragment
(7&times; `/#t=`, 0&times; Pfadform). Geteilter CSS-Block in allen 18 Dateien byte-identisch.

## Breitenkorrektur der Active-drops-Seite (Schlussmessung)

Gefordert war: linke Kante behalten, den gemeinsamen Inhaltsrahmen verbreitern, Held und
Tabelle auf dieselbe Breite ziehen, Spalten 32/30/10/13/15&nbsp;%. Vor der Aenderung wurde in
Playwright geprueft, welche Regel die Breite begrenzt &mdash; **eine 900px-Regel gibt es auf dem
Dashboard nicht**. Die einzigen Werte in dieser Groessenordnung sind `.price-grid { max-width:
880px }` (index.html) und `.dstream { max-width: 940px }` (nur received.html). Begrenzt hat
`.dsh-body-active { max-width: 1376px }` abzueglich 2&times;28px Polsterung.

Geaendert wurden die Generatorquellen, nicht die erzeugte Datei:
`css2.py` &rarr; `width: min(1320px, calc(100% - 48px)); max-width: 1320px; margin-left: 28px;
margin-right: auto; padding-left: 0; padding-right: 0`, Heldenzonen `flex: 1 1 auto` /
`0 0 auto` / `0 0 auto`, Aktionsspalte `width: 15%`; `css.py` &rarr; feste Spaltenanteile
32/30/10/13/15&nbsp;% statt `width: 1%`.

Messung bei exakt 1760&times;900, dunkel:

| Wert | gemessen |
| --- | --- |
| Sichtfenster | 1760 (Layoutbreite 1745, `scrollbar-gutter: stable` haelt 15px frei) |
| Inhaltsrahmen `.dsh-body-active` | links 292, Breite **1320**, rechts 1612 |
| Heldenkarte `.dhero` | links 292, Breite 1320, rechts 1612 |
| Tabelle `.dsh-table` | links 292, Breite 1320, rechts 1612 |
| Spalten | 422,4 / 396,0 / 132,0 / 171,6 / 198,0 px = 32/30/10/13/15&nbsp;% |
| rechter Randstreifen | 133px bis zur echten Seitenkante |

`left + width = right` &mdash; die geforderte linke Kante bei rund 200px und die rechte bei
1450&ndash;1520px sind mit der 264px breiten Seitenleiste nicht gleichzeitig erfuellbar
(264 + 1250 = 1514 ist das Minimum). Gewaehlt: Breite 1320 bei linker Kante 292.

received.html unveraendert: `.dsh-body` 264/1481 bei 1760, `.dstream` 292/940.

### Offener Befund aus der Gegenpruefung: das Theme-Menue ist auf beiden Dashboard-Seiten unerreichbar

Nicht durch diese Aenderung verursacht, aber gemessen und bestaetigt (Treffertest per
`elementFromPoint`, dashboard.html und received.html, jeweils frischer Aufruf):

- **ab 1360px:** der Cookie-Dialog (`#cookie-pop`, `position: fixed`, `z-index: 90`) liegt
  ueber der Themenpille unten links in der Seitenleiste. Die Pille selbst ist nicht klickbar
  (`elementFromPoint` liefert `#cookie-pop`); Playwright bricht den Klick nach 30s ab. Das
  `z-index: 95` des Menues hilft nicht, weil `.dsh-side` `position: sticky` ist und damit
  einen eigenen Stapelkontext bei `z-index: auto` aufmacht. Nach Wegklicken des Dialogs
  funktioniert es.
- **bis 1040px:** die Seitenleiste wird zum 63px hohen Streifen oben, das Menue oeffnet aber
  weiter nach oben (`.theme-menu { bottom: calc(100% + 8px) }`, ohne Ausnahme fuer schmale
  Fenster). Gemessen: Menue von -113 bis 10,8 &mdash; **8,8&nbsp;% sichtbar**, keine der drei
  Optionen trefferbar. Dafuer gibt es keinen Ausweg.
- index.html bei 720px zeigt dieselbe Ueberdeckung an der dritten Fussleisten-Option,
  docs.html ist unauffaellig. Der Kern des Problems ist die Dashboard-Schale, wo die Pille
  dauerhaft unten links sitzt &mdash; genau dort, wo der Cookie-Dialog erscheint.

Nicht behoben, weil die Anweisung zur Breitenkorrektur ausdruecklich lautete, sonst nichts zu
aendern.

### Nachtrag: Tabelle ueber die volle Seitenbreite, Karte gekappt

Die 1320px-Kappung ist entfallen. `.dsh-body-active` traegt jetzt `width: auto; max-width:
none; margin-left: 28px; margin-right: 28px` &mdash; ein Blockelement nimmt von selbst den Rest
der Zeile, ohne Rechnerei mit der Seitenleiste. Gemessen (dunkel):

| Fenster | Rahmen und Tabelle | Karte |
| --- | --- | --- |
| 2556 | 292 &rarr; 2513 (2221) | 292 &rarr; 1532 (1240) |
| 1920 | 292 &rarr; 1877 (1585) | 292 &rarr; 1532 (1240) |
| 1760 | 292 &rarr; 1717 (1425) | 292 &rarr; 1532 (1240) |
| 1440 | 292 &rarr; 1397 (1105) | fliessend, gleiche Kante |
| 900 | 28 &rarr; 857 (829) | fliessend, gleiche Kante |

Die Heldenkarte behaelt als einziges Element `max-width: 1240px`: sie traegt drei kurze Zonen,
darueber hinaus entsteht zwischen ihnen nur Leerraum. Linke Kante bleibt bei allen dreien
buendig. Spaltenanteile ab 1280px unveraendert 32/30/10/13/15&nbsp;%; darunter greift
`table-layout: auto` mit den Mindestbreiten der Inhalte, unter 700px die Kartenansicht.
Abnahme ohne Fehler, received.html unveraendert (1760: `.dsh-body` 264/1481, `.dstream`
292/940), geteilter CSS-Block in allen 18 Dateien byte-identisch.

## Received-Seite: Raster statt Zeilenstapel

Der Generator war weg (er lag im Scratchpad einer alten Sitzung). Er liegt jetzt
im Projekt: `tasks/build_received.py` erzeugt aus den eingefrorenen Dateien in
`tasks/_baseline/` sowohl received.html als auch dashboard.html. Zweimal laufen
lassen liefert dasselbe Byte-Ergebnis; der Lauf bricht ab, wenn ein Anker nicht
mehr eindeutig ist oder eine der Zusicherungen faellt.

| Punkt | Umsetzung |
|-------|-----------|
| 1 Inhaltsrahmen | `.dsh-body-received`: linke Kante 28px hinter der Seitenleiste wie bei Active drops, nach rechts auf 1320px gekappt. Gemessen bei 1760: **292 / 1320 / 1612**, erste Zeile ebenfalls 1320. |
| 2 Sample-Badge | Aus dem Markup entfernt, kein Ersatz. |
| 3 Erklaerung | Sichtbar nur noch „Drops opened on this device." Der alte Absatz sitzt hinter dem ⓘ und klappt per Tastatur oder Klick auf (`aria-expanded`, `aria-controls`). |
| 4 Datumsgruppen | Today / Yesterday / Earlier bleiben, ohne Linie, Versalien im Kleinsatz. |
| 5 Zeilenraster | Sechs Spalten 32/16/10/12/15/15&nbsp;%, einmal als `--rcols` notiert und von Kopfzeile und Zeilen geteilt. Gemessen 422,4 / 211,2 / 132 / 158,4 / 198 / 198px - Kopf und Werte auf denselben Kanten. |
| 6 Zustand | Nur drei „Downloaded", zwei Zellen leer, kein Gruen, keine Pille. „Not downloaded" ist raus. |
| 7 Aktion | Die Link-Pillen sind weg. Statt ihrer die vorhandene Textaktion `.dact`: in Ruhe `--text-secondary` (177,180,187), bei Hover und Tastaturfokus Markenblau (75,131,255), ohne Flaeche, ohne Rahmen. Kopiert wird der volle Fragment-Link. |
| 8 Zeilenpräsenz | Zeilenhoehe 71px, duenne Trenner, keine Karten. Die Hover-Flaeche ist ein `::before` hinter dem Inhalt, das ueber die Deckkraft geschaltet wird - kein Neuzeichnen des Kastens bei jedem Wechsel. |
| 9 Schmale Schirme | Ab 700px kippt die Zeile in zwei Ebenen (Name und Aktion oben, die vier Werte darunter mit gezeichneten Trennpunkten). `display: contents` loest dafuer die beiden Huellen auf, die Reihenfolge im Quelltext bleibt. Gruppenueberschriften bleiben. |

### Drei echte Fehler waehrend der Umsetzung
- **Der Rahmen mass 551px statt 1320.** `.dsh-main` ist ein Spalten-Flexkasten,
  und ein `margin-right: auto` auf der Querachse hebt das Strecken auf - das
  Element schrumpft dann auf seine Inhaltsbreite. Mit einer festen Zahl streckt
  es sich und `max-width` kappt.
- **Kopfzeile und Werte liefen auseinander.** Die vier Werte stehen in einer
  Huelle, damit sie schmal umbrechen koennen; ohne `display: contents` sass die
  ganze Huelle in Spalte 2 und die Aktion in Spalte 3.
- **10px Ueberlauf zwischen 740 und 760px.** Die Hover-Flaeche greift 10px ueber
  die Inhaltskante hinaus, und der Rahmen hatte rechts keinen Aussenabstand.
  Kein `getBoundingClientRect` findet das, weil ein Pseudoelement keine Box im
  Elementbaum hat - nur die Breitenreihe von 360 bis 1800px hat es gezeigt.

Dazu ein Befund aus der Kontrastmessung: die Versalien im Kleinsatz standen in
`--text-quaternary` und lagen hell bei **3,30:1**. Sie sind Text und brauchen
4,5:1, nicht die 3:1 fuer grafische Elemente - jetzt `--text-secondary` wie der
Tabellenkopf von Active drops. Derselbe Ton war in dieser Datei schon einmal am
Trennpunkt aufgefallen.

Ausserdem entfernt: ein `'''` mitten im Stylesheet, Rest einer Python-Zeichen-
kette aus dem alten Generator. Als CSS ein Parse-Fehler, ohne sichtbare Wirkung.

### Abnahme
Keine Konsolen- oder Seitenfehler. Kein Ueberlauf in Zwanzigerschritten von 360
bis 1800px. Schlechtester Kontrast 4,87:1 hell (das orange „18 h", unveraendertes
Token) und 7,37:1 dunkel. Geprueft: Suche mit Gruppen-Ausblendung („zip" &rarr;
2 von 5, Yesterday verschwindet), Escape, ⌘K, Kopieren mit Rueckmeldung und
Ruecksprung, Tastaturfokus mit 2px-Umriss auf Aktion und Info-Knopf,
`prefers-reduced-motion` (Einzug und Uebergaenge auf `none`), 1760/1440 dunkel,
1440 hell, 400 schmal. Der Schluessel steht nur in `data-link`, nie im Text
(7&times; Fragmentform, 0&times; Pfadform).

**Active drops ohne Regression:** dashboard.html ist ausserhalb des CSS-Blocks
byte-identisch, und die Vollbild-Aufnahmen bei 1760/1440/1040/720/400px sind
gegen die Ausgangsfassung **pixelgleich** (0 abweichende Pixel, gemessen mit
abgeschalteter Aurora - sonst vergleicht man nur deren Phase). Gemessen
weiterhin 292 / 1425 / 1717, Karte 1240. Der geteilte CSS-Block ist in allen 18
Dateien byte-identisch und unveraendert (152071 Bytes, `bfbf06a8`).

### Nachtrag: Received ueber die volle Seitenbreite

Die 1320px-Kappung ist entfallen. `.dsh-body-received` traegt jetzt dieselben
Werte wie `.dsh-body-active`: `width: auto; max-width: none; margin-left: 28px;
margin-right: 28px` - beide Dashboard-Seiten stehen damit auf exakt denselben
Kanten.

| Fenster | Rahmen, Spaltenkopf und Zeilen |
| --- | --- |
| 1760 | 292 &rarr; 1717 (**1425**) |
| 1440 | 292 &rarr; 1397 (1105) |
| 400 | 18 &rarr; 382 (364) |

Spaltenanteile unveraendert 32/16/10/12/15&nbsp;%/15&nbsp;%, bei 1760 gemessen
456 / 228 / 142,5 / 171 / 213,8 / 213,8px. Kopfzeile und Werte weiterhin auf
denselben sechs Kanten, alle fuenf Zeilen 292 &rarr; 1717.

Abnahme wiederholt: keine Konsolenfehler, kein Ueberlauf 360&ndash;1800px, Suche,
Escape, ⌘K, Kopieren und Tastaturfokus unveraendert. Active drops weiterhin
pixelgleich bei 1760/1440/1040/720/400px, geteilter CSS-Block in allen 18 Dateien
byte-identisch.


---

# Redesign: dashboard.html + received.html im index-Zuschnitt

## Auftrag
Beide Dashboard-Seiten gefallen noch nicht; komplett neu im "richtig smoothen,
Apple-like" Design von index.html.

## Plan
- [x] Ist-Zustand + index-Designsprache erheben (Screenshots, Tokens, Muster)
- [x] Konzept: Schale ohne Trennlinien, Glas nur im Scroll-Chrom, Inset-Grouped-Lists,
      Squircle-Glyphen, Segmented-Pill-Sortierung, state-in-Motion
- [x] Generator tasks/build_dashboard_pages.py: ersetzt Seiten-CSS/Markup/JS,
      geteilter Block bleibt byte-identisch
- [x] Playwright: Themes, 360-1800px, Konsole, Interaktionen, Kontraste
- [x] Adversarialer Review (7 Linsen, je Befund ein Skeptiker) + Fixes
- [x] Doku

## Das neue Design
Leitidee: eine ruhige Flaeche statt eines Admin-Rasters. Alle harten Trenn-
linien der Schale sind weg (Sidebar-Border, Topbar-Border, Tabellenlinien);
Struktur entsteht aus Weissraum wie auf index.html.

- **Schale:** transparente Seitenleiste mit Pill-Navigation (44px, Radius 12,
  aktiver Eintrag traegt den Akzent im Symbol), Sticky-Kopfzeile wird erst beim
  Scrollen zu Milchglas (Scroll-Edge-Effekt: --glass-bg + blur(16px) in einem
  Opacity-Layer ::before). Glas nur im Chrom, Inhalt opak.
- **dashboard.html:** Heldenkarte "Next to expire" (Radius 32, opak, Aurora
  dahinter im index-Rezept, Squircle-Dateikachel 25%-Radius mit Orbit-Schatten,
  46px-Mono-Countdown "22 h left" eng gesetzt, Link-Pill, blaue Copy-Pill).
  Darunter die Liste als Inset-Grouped-Flaeche (bg-subtle, Radius 20, Zeilen
  Radius 16 mit Hover-Elevation) statt Tabelle; Sortierung ueber die geteilte
  Segmented-Pill mit Slide-Thumb ("Sort · Expiry/Name").
- **received.html:** gleiche Zeilensprache, gruppiert nach Tagen mit
  Mono-Micro-Labels (TODAY/YESTERDAY/EARLIER), Info-Toggle in der Zaehlzeile.
- **Motion:** dsh-in-Einzug (Blur 4px + 6px Versatz, 320/380ms,
  --ease-out-strong) mit 35ms-Stagger; Interaktion beendet den Einzug sofort;
  prefers-reduced-motion deckt Aurora, Einzug und Transitions ab.
- **Breite (2 Feedback-Runden):** linke Kante fest, Inhalt laeuft rechts bis
  1700px Inhaltsbreite (Kappung 1780 inkl. Polster); Kopfzeile, H1, Hero und
  Liste enden gemessen auf derselben Kante (1732px bei 1920). Listen-Typo eine
  Stufe groesser (Name 16px, Sub 13.5, Aktion 14).

## Generator
`tasks/build_dashboard_pages.py` ersetzt in beiden Dateien Seiten-CSS,
App-Markup (Kopfzeile + main; Seitenleiste bleibt verbatim) und den letzten
Script-Block; idempotent, mit Zusicherungen (Marker 1x, Tag-Balance, geteilter
Block unveraendert, Seiten-CSS/JS beider Dateien identisch, Fragment-Linkform,
kein '$1' in der ganzen Datei). **tasks/build_received.py ist stillgelegt**
(SystemExit-Guard): es wuerde das Vorgaenger-Design aus tasks/_baseline
wiederherstellen.

## Adversarialer Review — Ergebnis
32 Behauptungen ueber 7 Linsen, 4 vom Skeptiker widerlegt, 28 bestaetigt
(darunter 5 Mehrfachmeldungen desselben Hero-Suchbefunds). Alle eindeutigen
Befunde behoben:

| Schwere | Befund | Fix |
|---------|--------|-----|
| hoch | Geschlossenes Theme-Menue nur opacity:0 — unsichtbar fokussierbar, Enter schaltet "aus dem Nichts" | `.dsh .theme-menu { visibility: hidden }` mit verzoegertem Flip |
| mittel | Suche verleugnete den Hero-Drop ("render" -> "No drops match that" trotz "6 active") | Hero traegt data-name/data-meta und gehoert zum Korpus; bleibt bei Treffer stehen; Zaehlbasis "of 6" |
| mittel | role="menu" ohne Fokusmanagement/Pfeiltasten | Fokus wandert beim Oeffnen auf die gewaehlte Option, zurueck zur Pille; Arrow/Home/End |
| mittel | Suchfilterung + Copy-Feedback fuer Screenreader stumm | `.dsh-sr`-Statuszeile (role="status"): Trefferzahl entprellt, "Link copied./Copy failed." |
| mittel | Clipboard-Fallback meldete Erfolg ohne Kopie | isSecureContext-Pruefung + execCommand-Fallback + ehrliches "Copy failed" |
| mittel | Copy-Blur sprang hart (keine transition auf filter/opacity) | Inline-Transition wie auf index.html |
| mittel | Inaktiver Sortierknopf bei 3,1:1 (geteiltes opacity 0.5) | Farbe statt Deckkraft: secondary/default |
| mittel | Primaer-CTA "New drop" war tot | jetzt `<a href="index.html#app">` |
| mittel | "View details" ohne Funktion | entfernt (samt CSS) — eine klare Aktion |
| mittel | Sidebar-Fuss bei niedrigen Fenstern unerreichbar (sticky 100dvh ohne overflow) | `overflow-y: auto` (ohne overscroll-contain — das schluckte das Mausrad, vom Test gefangen) |
| mittel | iOS zoomt beim Suchfokus (14px < 16px) | `@media (pointer: coarse)` -> 16px |
| niedrig | Suchfeld-Fokus unsichtbar (outline: none, Ring 1,1:1) | `:has(input:focus-visible)` mit 2px-Outline |
| niedrig | Sortieren/Suchen < 900ms startete den Einzug neu | settle() bei erster Interaktion |
| niedrig | "⌘K" hart auf allen Plattformen | navigator.platform -> "Ctrl K" |
| niedrig | Natives Loesch-Kreuz des search-Inputs in der Pille | ::-webkit-search-cancel-button ausgeblendet (Konvention von glossary/help) |
| niedrig | Expired/Settings (href="#") sprangen an den Seitenanfang | preventDefault; bleiben dokumentierte Platzhalter |

## Nebenbei repariert: das '$1'-Artefakt im GETEILTEN Block (alle 18 Seiten)
Vier Zeilen der Form `$1background: …;}` (kaputte Regex-Ersetzung, vorbestehend)
hatten ihre Selektoren verloren; die CSS-Fehler-Recovery verschluckt zusaetzlich
die jeweils folgende Regel. Empirisch bestaetigt: auf index.html fehlten dadurch
`.progress-wrap/.done-wrap/.open-wrap/.error-box` (Panelflaeche, Radius, Rand)
UND `.progress-title/.done-head/.open-head/.error-box h3` (20px/600-Titel) —
die Upload-/Open-/Fehlerzustaende der Drop-Card rendern seither mit Browser-
Defaults. Fix: Selektoren rekonstruiert, verlorene Layout-Eigenschaften
konservativ im Zuschnitt der Nachbarrezepte ergaenzt, in allen 18 Dateien
byte-identisch angewandt (ein gemeinsamer Block-Hash). Gemessen: `.open-head`
wieder 20px/600, `.progress-wrap` mit Panel-Regel, index.html ohne Konsolen-
fehler und ohne Ueberlauf. tasks/_baseline bleibt eingefroren (traegt das
Artefakt noch — der alte Generator ist ohnehin stillgelegt).

## Verifikation (Playwright, Chromium)
74/74 Checks: kein Ueberlauf 360-1800px (20er-Schritte, beide Themes, beide
Seiten), keine Konsolenfehler; Suche inkl. Hero-Treffer ("render" -> Hero
bleibt, "1 of 6 matching"), Escape, Sortierung beide Richtungen mit Thumb,
Copy in Held und Zeile (voller Fragment-Link in der Zwischenablage, nie
sichtbar), Live-Region-Ansagen, Ctrl+K, Plattform-Kuerzel, Theme-Menue
(geschlossen unfokussierbar, offen mit Fokus + Pfeiltasten, Escape zurueck zur
Pille), Cookie-Dialog rechts und Pille klickbar, Mobilleisten-Menue voll im
Fenster, reduced-motion (Aurora + Einzug stehen), Kontraste >= 4,5:1 in beiden
Themes (Gruppenlabel, Restzeit, Warnwert, Pille, Aktion, Sub), Kanten
Kopfzeile/Hero/Liste auf exakt derselben Linie.

## Nachtrag: vierte Feedback-Runde (9/10 -> Feinschliff)
1. **Hero-Verteilung 50/18/32:** `.dhero-grid` ist jetzt ein Raster mit
   `minmax(0,50fr) / minmax(max-content,18fr) / minmax(max-content,32fr)` —
   der Breitenueberschuss verteilt sich proportional statt sich als Leerraum
   zwischen Datei und Uhr zu sammeln. Ab 1360px zweispaltig, Aktionszone
   volle Breite; ab 860px gestapelt.
2. **Spaltenzeile ueber den Listen:** `.dcols` (Mono 10,5px Versalien,
   aria-hidden) mit exakt derselben Rastermetrik wie `.drow` — dafuer bekam
   die Aktionsspalte eine feste Breite (124px statt auto), sonst haetten sich
   die beiden getrennten Raster von rechts her verschoben. Gemessen: Etiketten
   pixelgleich ueber den Werten (384/1415/1545). Verschwindet unter 720px und
   im Leerzustand. Auf received.html einmal ueber dem Strom, nicht je Gruppe.
3. **"Sort by"-Etikett:** In Runde 3 war nur das CSS des Etiketts entstanden,
   der Markup-Wrapper fehlte — es wurde nie gerendert (deshalb wirkte die
   Pille weiter wie ein Filter). Jetzt echt im Markup, die Gruppe traegt
   `aria-labelledby` auf das sichtbare Etikett.
4. **Link + Copy in einer Zeile:** `.dhero-linkbox` (flex 1 1 220px) und der
   Copy-Knopf teilen sich die `.dhero-row`; die Aktionszone ist eine Zeile
   flacher und geschlossener.

Abnahme wiederholt: alle Playwright-Checks bestehen, Ausrichtung gemessen,
1920 hell/dunkel und received gegengeprueft.

## Offen / bewusst nicht getan
- Expired und Settings bleiben Platzhalter (Entwurfs-Schale); jetzt ohne
  Scroll-Sprung, aber weiterhin ohne Ziel.
- Auf den App-Seiten gibt es nach gespeicherter Consent-Entscheidung keinen
  Wiedereinstieg in die Cookie-Einstellungen (kein Footer); ueber jede
  Marketing-Seite weiterhin moeglich.
- tasks/_baseline traegt noch das '$1'-Artefakt (eingefroren, Generator
  stillgelegt).

## Nachtrag: fuenfte Feedback-Runde (9,4/10 -> zwei Mikrodetails)
1. **"22 h left" typografisch korrigiert:** Markup jetzt <b>22</b> +
   <span class=u>h</span> + <span class=l>left</span>; die Einheit steht mit
   11px Abstand (visuell ~8,5px - das negative letter-spacing der Mono-Zahl
   wirkt auch hinter der letzten Ziffer), "left" 12px sekundaer unmittelbar
   daneben. Dabei denselben Fehler wie beim Sort-Etikett gefunden: die
   .u/.l-Regeln existierten seit Runde 3, das Markup trug aber noch den alten
   Sammel-Span - erst der Screenshot-Abgleich hat es gezeigt.
2. **Spaltenetiketten eine Spur heller:** .dcols-Farbe auf
   color-mix(35% text-default in text-secondary) - nur Helligkeit, nicht
   Groesse/Gewicht/Laufweite.

Grundlayout eingefroren wie gefordert; volle Playwright-Suite weiterhin ohne
Befund, beide Themes gegengeprueft.

## Nachtrag: sechste Feedback-Runde - received.html auf den Empfaenger-Workflow
"Copy link" war dort die falsche Hauptaktion (Absender-Logik). Jetzt je Zustand:

- **Download** (noch nicht geladen) bzw. **Download again** (Zeilen mit
  "Downloaded") als Text-Hauptaktion mit Tray-Symbol - ein ECHTER Link in den
  Empfangsmodus der Vorschau (index.html#t=<id>&k=<key>). Eine Sample-Id endet
  dort im ehrlichen, designten "Nothing here"-Zustand der App; per E2E-Test
  verifiziert (Klick -> Empfangsmodus -> Fehlerkarte). Kein toter Knopf.
- **Copy link** ist zum kleinen Sekundaersymbol geworden (.dact-ico, 
  aria-label je Datei); der Haken/Kreuz-Zustand antwortet im Symbol, die
  Live-Region meldet weiterhin "Link copied./Copy failed.".
- Die Aktionsspalte ist auf received 188px statt 124px (via .dsh-received,
  nur >=721px) - Zeilen UND Spaltenzeile, gemessen fluchtend.
- Eigene .dgo-Klasse statt .dact fuer den Download-Link, damit der delegierte
  Copy-Handler ihn nicht faengt.
- Seitentitel bleibt "Received" (Feedback selbst: verstaendlicher als das
  fachlich praezisere "Opened drops"; die Praezisierung steht im Info-Toggle).

Active drops unveraendert (Absender-Logik: Copy link bleibt dort Hauptaktion).
Volle Playwright-Suite inkl. neuer Empfaenger-Checks ohne Befund.

## Runde 7: Expired- & Settings-Seite (Plan)

Auftrag: "Nun erstelle eine Expired & Settings Seite" - die beiden bisherigen
Platzhalter-Links der Seitenleiste werden echte Seiten im selben App-Zuschnitt.

### Leitplanken
- [ ] Geteilter CSS-Block bleibt byte-identisch (jetzt 20 Seiten); neue Seiten
      entstehen als Kopie von dashboard.html und werden vom Generator gespleisst
- [ ] Seiten-CSS und Seiten-JS bleiben ueber alle 4 App-Seiten byte-identisch
      (JS-Bloecke no-open per Element-Check)
- [ ] Grundlayout unangetastet (Feedback-Runden 4-6): linke Kante, Breiten,
      Typografie, Spaltenraster bleiben wie abgenommen

### Expired
- [ ] Ruhige Liste ohne Hero: H1 + Zaehlzeile + Info-Toggle (Relays loeschen,
      nur lokale Aufzeichnung bleibt) + Spaltenzeile File/Expired/State/Action
- [ ] 4 Zeilen (gesendete, abgelaufene Drops); State: Downloaded oder Never
      opened (wichtige Information: verfallen ohne Abruf)
- [ ] Hauptaktion "Send again" als echter Link in den Composer (index.html#app);
      kein Copy - der Link ist tot
- [ ] Suche filtert wie auf den anderen Seiten; Einzug-Animation wie gehabt

### Settings
- [ ] Inset-Grouped-Sektionen (Apple System Settings), Kappung ~760px - eine
      Formularspalte darf nicht 1700px laufen
- [ ] Appearance: Theme-Segment (Auto/Light/Dark), ECHT verdrahtet mit dem
      bestehenden cloakdrop-theme-Mechanismus, synchron mit der Themenpille
- [ ] New drops: Default-Expiry-Segment (24 h / 3 d / 7 d) + Switch "Delete
      after first download" - persistiert in localStorage
- [ ] On this device: "Clear received history" mit Bestaetigungsschritt;
      received.html respektiert das Flag wirklich (leerer Zustand, Zaehler,
      Seitenleisten-Badge) - keine falschen Erfolgsmeldungen
- [ ] About: Verschluesselungs-Hinweis + Version (reine Info-Zeilen)
- [ ] Suche filtert Settings-Zeilen (Apple-Muster), leerer Zustand vorhanden
- [ ] Destruktiv-Farbe nur im Bestaetigungsschritt: --red-11 hell, hellere
      Dark-Variante (AA)

### Generator/Nav
- [ ] Nav wird generiert statt verbatim: echte hrefs, aria-current je Seite,
      Counts (6/5/4), Platzhalter-preventDefault-Block entfaellt
- [ ] Titel + Meta-Description je Seite
- [ ] .dsort-Klasse wird zum generischen .dseg (Segment-Rezept app-weit)
- [ ] Zusicherungen auf 4 Dateien: Hash, CSS/JS-Gleichheit, genau ein
      aria-current, kein href="#", kein $1

### Verifikation
- [ ] verify_new.py: Overflow-Sweep beide Themes, Konsole, Nav-Zustaende,
      Theme-Sync beide Richtungen, Persistenz nach Reload, Clear-E2E bis
      received.html, Kontraste, Spaltenflucht, Suche, Tastatur
- [ ] verify.py Regression (received-JS hat neues Flag)
- [ ] Screenshots 1920px beide Themes; adversarialer Review-Workflow; Fixes

## Runde 8: Active drops — Redesign nach Referenzbild (Plan)

Auftrag: "Redesigne Active Drops Page komplett neu, sodass es wie auf dem Bild
aufgebaut ist und aussieht. Verwende den Apple Like Stil von index.html fuer
die Farben usw." — Referenz ist ein Dashboard-Mockup mit voller App-Schale.

### Architektur
- [x] Seiten-CSS/-JS wandern in echte Dateien (tasks/app.css, tasks/app.js);
      der Generator liest sie nur noch ein (Byte-Gleichheit bleibt zugesichert)
- [x] Generator ersetzt jetzt die GANZE Schale (Kopfzeile + Leiste + Arbeits-
      flaeche), nicht nur alles nach </aside> — die Kopfzeile liegt neu ueber
      der Leiste. Logo-Base64 wird aus der bestehenden Datei uebernommen
- [x] Tokens ausschliesslich aus dem geteilten Block von index.html
      (--bg-panel, --border-secondary, --green-9/#2563f0, --ease-out-strong)

### Neue Schale (alle 4 App-Seiten)
- [x] Kopfzeile voll breit: Markenzelle (Leistenbreite) + Leisten-Umschalter,
      Brotkrume, Suche, 4 runde Symbolknoepfe (Theme-Pille, Help, Settings,
      Received). Kein Glocken-Attrappenknopf — jedes Ziel ist echt
- [x] Seitenleiste: Gruppenlabel "MAIN MENU", 4 Filter-Eintraege (Active drops,
      Expiring today, Downloaded, All active -> Tabs der Tabelle, ueber #hash
      auch von anderen Seiten aus), 2. Gruppe Received/Expired/Settings
- [x] Leiste einklappbar (72px, Labels bleiben als Namen im A11y-Baum),
      persistiert in localStorage
- [x] Fuss: Plan-Karte (Free plan, 3-GB-Balken, Upgrade -> pricing.html) und
      Geraete-Zeile. KEIN erfundener Nutzername: CloakDrop hat keine Konten
      (Settings sagt das woertlich) — die Zeile nennt das Geraet

### Active drops (Inhalt)
- [x] Kopf: H1 + Zaehlzeile + Primaerknopf "New drop" mit Split-Menue
      (echte Ziele: Composer, Drop-Vorgaben, Help)
- [x] 4 Kennzahlkarten (Next to expire / Downloaded / Shared with team /
      Ready to copy) — Klick waehlt den Drop in der Detailspalte
- [x] "Recent drops": horizontaler Streifen mit 6 Karten, echter Blaetterknopf
      (verschwindet am Ende), Klick waehlt den Drop
- [x] "All drops": Segment-Tabs (All/Downloaded/Waiting/Expiring soon/Expired),
      zweites Suchfeld (mit dem Kopfzeilen-Feld synchron), Sortier-Popover,
      Dichte-Umschalter
- [x] Echte <table> statt Raster-Attrappe: Auswahl-Kaestchen (inkl. Select-all
      mit indeterminate), Name/Recipient/Expires in/State/Created/Link/Menue
- [x] Fusszeile: "N of 6 row(s) selected" + Sammelaktion "Copy N links" +
      Seitenschalter (echt deaktiviert, es gibt genau eine Seite)
- [x] Detailspalte rechts: Drop details (Size/Recipients/Expires in/Created),
      Transfer-Link + Kopierknopf, E2E-Hinweis, Recent downloads, "View all
      downloads" -> #downloaded
- [x] Ein einziges Kontextmenue fuer alle "..."-Knoepfe, nur echte Aktionen
      (Copy link, Show details, Email recipient)

### Leitplanken aus den Lektionen
- [x] Suchkorpus = alles was die Seite behauptet: Suche filtert Kennzahlkarten,
      Streifen UND Tabelle
- [x] Neue Selektoren sofort im erzeugten Markup gegenpruefen
- [x] Zustand ueber Farbe statt Deckkraft; Orange nur fuer "laeuft heute ab"
- [x] Kopieren meldet nur echten Erfolg (bestehender Fallback bleibt)
- [x] Linke Kante fest, Pruefung bei 1440/1920/2560
- [x] prefers-reduced-motion deckt jede neue Transition ab

### Verifikation
- [x] Playwright: hell/dunkel, 360-2560px, alle Interaktionen, Konsole leer
- [x] Alle 4 Seiten neu gebaut, CSS/JS byte-identisch, Nav-Zusicherungen gruen

## Runde 8 — Ergebnis

Gebaut: `tasks/build_app.py` (loest `build_dashboard_pages.py` ab, das jetzt
einen SystemExit-Guard traegt). Seiten-CSS und -JS leben in `tasks/app.css`
und `tasks/app.js`; der Generator spleisst sie byte-identisch in alle vier
App-Seiten und ersetzt neu die GANZE Schale.

Bewusste Abweichungen vom Referenzbild — jede aus Produktwahrheit:

| Bild | Umsetzung | Grund |
|------|-----------|-------|
| Nutzerprofil "Salung Prastyo / Product Designer" | "This device / No account, no sync" | CloakDrop hat keine Konten; die Settings-Seite sagt das woertlich |
| Glockensymbol in der Kopfzeile | Theme-Pille an derselben Stelle | es gibt kein Benachrichtigungssystem, das die Glocke bedienen wuerde |
| `cloakdrop.io/d/4f7a8b` | `cloakdrop.com/#t=…` | der Schluessel MUSS im Fragment stehen; Pfadform ist per Zusicherung verboten |
| Menge "12 active / 4 downloaded" | "6 active / 2 downloaded / 1 expires today" | die Zahlen der echten sechs Drops |
| Zeilen nach Erstelldatum | Standard "Expires first" | die Seite handelt von Ablauf; Erstelldatum liegt im Sortiermenue |
| Zweiter Eintrag "All active" | "Waiting" | "Active drops" waere derselbe Filter zweimal |

Verifikation (`tasks/verify_app.py`): 4 Seiten x 6 Breiten (400-2560) x hell
und dunkel ohne Ueberlauf und ohne Konsolenfehler, plus 38 Interaktions-
Zusicherungen (Auswahl, Tabs, Suchsynchronitaet, Sortiermenue, Dichte,
Blaettern, Kontextmenue im Fenster, Theme-Sync, Persistenz nach Reload,
Adresszeile, ehrliche Kopierantwort) und Regression auf received/expired/
settings. Alles gruen.

Befunde aus dem eigenen Review, die noch gefixt wurden: `--dsh-pop` erreichte
das Menue nicht (Schatten fiel aus), `.dsh-search` schrumpfte nicht (Ueberlauf
bei 400px), Klickflaeche der Kennzahlkarte lag unter dem Text, `#hash` ohne
`hashchange`, Sortierung liess die Trennlinie der letzten Zeile stehen,
Karte 4 lag mit ihrem sichtbaren Linktext nicht im Suchkorpus.

Nachtrag: `.dsh-body` trug noch eine zweite Kappung (max-width 1500). Neben
der Detailspalte kappt schon die Rasterspalte — auf breiten Schirmen blieb
dadurch eine Luecke von ~500px zwischen Inhalt und Spalte stehen. Die
Kappung gilt jetzt nur noch ohne Detailspalte (received/expired/settings);
verify_app.py misst den Abstand Inhalt-zu-Spalte bei 1600 und 2560px.

## Runde 9: Dateityp-Marken

Auftrag: "Fuege auch paar richtige Logos hinzu fuer paar Icons" — auf
Rueckfrage praezisiert: die Dateisymbole.

Aus dem grauen Dokument-Glyph wird eine echte Marke je Endung: Seitenform mit
geknickter Ecke, getoente Flaeche, ein Typzeichen (Textzeilen / Reissverschluss
/ Abspielzeichen / Wellenform) und das Kuerzel im farbigen Band. Vier Toene,
je zwei Werte fuer hell und dunkel:

| Endung | hell | dunkel |
|--------|------|--------|
| PDF | #c4453d | #f08a80 |
| ZIP | #3a7a60 | #6cbd97 |
| MP4 | #1f6f8f | #63b8d6 |
| WAV | #a8477a | #e58bb4 |

Keiner der Toene liegt nahe an Markenblau (#2563f0) oder der Warnstufe
(#9a6700/#ffb224) — sonst laese sich eine PDF wie eine Warnung. Die
Bandschrift (--ft-ink) kippt mit dem Theme: hell Weiss, dunkel #17181b, weil
die dunklen Baender heller sind.

Die Marke traegt ihre Flaeche selbst; die grauen Kacheln darunter sind
entfallen (Tabelle, Streifen, Detailspalte, Recent downloads) — ebenso das
Mono-Kuerzel-Chiclet auf received/expired, das jetzt dieselbe Marke zeigt.
Damit sieht dieselbe Datei auf allen vier Seiten gleich aus.

Zusicherungen: Generator prueft je Endung Farbklasse UND Kuerzel im Markup;
verify_app.py prueft im Render, dass jede Endung eine eigene, aufgeloeste
Farbe hat (--ft-* lebt auf .dsh, ausserhalb faellt die Deklaration still aus)
und dass das Band seine Schrift traegt.

## Runde 10: Received, Expired & Settings nach den Referenzbildern

Alle drei Seiten bekommen denselben Zuschnitt wie das Dashboard: Seitenkopf
mit Primaerknopf, Liste in weissem Material, Detailspalte rechts.

### Received
- Gruppen (Today/Yesterday/Earlier) als weisse Flaechen mit Haarlinien statt
  grauer Inset-Gruppen; leerer Zustandswert wird zum Strich statt zur Luecke
- Detailspalte: Size / Opened / Expires in / State, Transfer-Link mit Kopie,
  "Download again" als Konturaktion, Hinweis "Opened on this device"
- "Downloaded here" listet die drei wirklich heruntergeladenen Drops; der
  Knopf darunter filtert die Liste echt auf sie (und wieder zurueck)
- Geleerte Historie blendet die Detailspalte mit aus — eine Spalte, die einen
  Drop zeigt, den die Seite gerade verneint, waere eine Behauptung zu viel

### Expired
- Jede Zeile eine eigene Karte (Referenz), die ausgewaehlte mit Akzentkontur
- Detailspalte: Size / Recipient / Expired / State, "Send again", Hinweis
  "This link has expired", "Recently expired" mit Wiederholen-Symbolen
- Der tote Link steht durchgestrichen und OHNE Kopierknopf da (Abweichung vom
  Bild): kopieren liesse sich nur ein Link, der nichts mehr oeffnet

### Settings
- Sektionen in weissem Material, Segmente im Formularrahmen statt als Pille
  (gleiche Mechanik, gleitender Thumb)
- Detailspalte "This device" fasst den echten Zustand zusammen (Theme,
  Default expiry, Received history, Encryption, Account, Version) und folgt
  jeder Umschaltung
- "Clear history" gibt es zweimal (Zeile und Spalte) — ein Vorgang, ein
  Zustand: der erste Klick bewaffnet beide, der zweite loescht

### Abweichungen von den Bildern
- Seitenleiste bleibt zweigruppig wie im Dashboard-Bild (die drei neuen
  Bilder zeigen drei verschiedene, untereinander widerspruechliche Varianten)
- Kein erfundenes Nutzerprofil, keine Glocke (siehe Runde 8)
- Dateisymbole bleiben die farbigen Marken aus Runde 9 statt der grauen
  Mono-Kachel der Bilder

Verifikation: 24 neue Zusicherungen (Detailspalte folgt der Zeile auf beiden
Listenseiten, Filterknopf, kein Kopierknopf am toten Link, Settings-Spalte
synchron, zwei Knoepfe ein Loeschvorgang, Spalte weg bei geleerter Historie,
Breite ohne Spalte). Gesamte Suite gruen, beide Themes, 400-2560px.
