diff --git a/docs/superpowers/specs/2026-10-04-analyse-uebersicht-design.md b/docs/superpowers/specs/2026-10-04-analyse-uebersicht-design.md new file mode 100644 index 0000000..e6866e0 --- /dev/null +++ b/docs/superpowers/specs/2026-10-04-analyse-uebersicht-design.md @@ -0,0 +1,239 @@ +# Analyse-Übersicht neu („Cockpit“) – Design + +Stand: 2026-10-04 · Status: Aufbau, Design-Richtung und Abschnitte 1–3 vom Nutzer im Chat freigegeben, Spec zur Durchsicht +Baut auf der Website-Analyse auf (Spec `2026-10-01-website-analyse-design.md`, live seit 01.10.2026). +Recherche: `/root/reports/analyse-ueberarbeitung-2026-10-04/recherche.md` (30 Tools, 25 Features, UI-Muster). +Entwürfe: `/root/reports/analyse-ueberarbeitung-2026-10-04/entwurf-a-cockpit.png` (gewählt), `entwurf-b-bericht.png`. +Regel des Nutzers: bauen → live (Nutzer pusht `main`) → Nutzer testet live. + +## Ziel + +Die Analyse-Seite im Kundenportal (Funktionen → Analyse) soll für kleine Betriebe ohne Analytics-Kenntnisse +**auf einen Blick** sagen, wie die Website läuft: ein Satz in Klartext, Vergleich zum Vorzeitraum, +Kontakt-Aktionen als Hauptzahl. Das Interface darf sich vom restlichen Portal abheben, muss aber +aufgeräumt und einheitlich sein. + +## Entscheidungen (vom Nutzer, 04.10.2026) + +| # | Entscheidung | +|---|---| +| U1 | Umfang: nur das Paket **„Übersicht neu“** (Kennzahlen mit Vorzeitraum-Vergleich und Mini-Verlauf, Klartext-Zusammenfassung, Kontakt-Aktionen als Hauptzahl, Datenbasis-Hinweis). **Nicht** in diesem Projekt: Besucher-Details (Wochentag × Uhrzeit, Kanäle, Zeit je Seite), Website-Check (Erreichbarkeit, SSL, Links, SEO), Bericht per Mail. | +| U2 | Design-Richtung **A „Cockpit“**: dunkel wie das Portal, eigene Akzentfarbe Türkis nur innerhalb der Analyse, leuchtender Block „Das Wichtigste“. | +| U3 | Aufbau mit drei Reitern **Übersicht · Heatmap · Einstellungen**; Heatmap und „Ausschalten“ hängen nicht mehr unten an der Übersicht. | +| U4 | Nur **vorhandene Daten**. Keine Änderung an Messskript (`w.js`, `h.js`), Banner, AVV, Leistungsbeschreibung, Datenschutz-Zusatzseite oder Abo-Bedingungen. | +| U5 | Klartext entsteht durch **feste Regeln im Browser**, keine KI, kein externer Dienst. | + +Rechtliche Einordnung (keine Rechtsberatung): Der Vergleich nutzt die Tageswerte des Vorzeitraums (bis zu 180 +Tage zurück; Tageswerte werden 25 Monate aufbewahrt). Durchschnitte und Tageswerte für Besucher, Kontakte und +Verweildauer stammen aus bereits erhobenen und verdichteten Daten (`daily`: `visitors`, `ct_kind`, +`en_count`, `en_sec_sum`, `scroll_sum`). Die Leistungsbeschreibung (§ 5: „Kennzahlen und Diagramme für die +letzten 7, 30 oder 90 Tage“) deckt das ab; kein neuer Text, keine neue Einwilligung. + +## 1. Daten: Statistik-Antwort des Collectors + +`GET /a/admin/stats/:siteKey?range=7|30|90` (interner Listener, `analytics/src/stats.js`) liefert **zusätzlich** +zu den bisherigen Feldern (alle bisherigen Felder bleiben unverändert, damit der Wechsel abwärtskompatibel ist): + +```js +{ + range: 30, + from: '2026-09-05', to: '2026-10-04', // Berliner Kalendertage des Zeitraums (to = heute) + firstDay: '2026-08-20', // frühester Berliner Tag mit Daten dieser Website ('' = keine) + totals: { + pv, visitors, contacts, consentRate, // wie bisher + engaged, // Zahl der en-Ereignisse (Seitenbesuche mit Zustimmung und Abschluss) + avgSec, // Ø Sekunden pro Seitenbesuch = en_sec_sum / en_count, gerundet; null wenn engaged = 0 + avgScroll, // Ø Scrolltiefe in % = scroll_sum / en_count, gerundet; null wenn engaged = 0 + }, + series: [{ day, pv, visitors, contacts, avgSec }], // bisher nur {day, pv}; avgSec je Tag null ohne en + pages, refs, countries, devices, contacts, banner, // unverändert + previous: { + from: '2026-08-06', to: '2026-09-04', // die `range` Kalendertage direkt vor `from` + complete: true, // firstDay !== '' && firstDay <= previous.from + totals: { pv, visitors, contacts, consentRate, engaged, avgSec, avgScroll }, + series: [{ day, pv, visitors, contacts, avgSec }], + contacts: [{ key, count }], // je Art, für die Veränderung pro Zeile + }, +} +``` + +Regeln: +- Tageslisten werden **kalendarisch** gebildet (Berliner Tage, wie `dayList`), nicht durch Abziehen von + `range × 24 h` – sonst verrutscht der Vorzeitraum bei der Zeitumstellung um einen Tag. +- Gemeinsame Hilfsfunktion sammelt die Dimensionen für eine beliebige Tagesliste: verdichtete Tage aus `daily`, + nicht verdichtete Tage live aus `events_raw` (wie heute). Kein Tag wird doppelt gezählt. +- `firstDay` = kleinster Tag aus `daily` und `events_raw` dieser Website (`MIN(day)` bzw. `dayOf(MIN(ts))`). +- `visitors` bleibt die Summe der Tages-Besucher (Salz wechselt täglich, kein tagesübergreifender Abgleich). +- Der Portal-Server reicht die Antwort unverändert durch (`GET /api/funktionen/analyse/stats`). + +Portal-Server, einzige weitere Änderung: `analyseView` (Route `GET /api/funktionen`) gibt zusätzlich +`activatedAt` aus dem Analyse-Dokument zurück (`null`, wenn nicht aktiv). + +## 2. Oberfläche + +### 2.1 Aufbau der Seite (Zustand „aktiv“) + +``` +[ Kopfleiste: ● Website-Name · „Analyse aktiv · Stand: heute, 19:42 Uhr“ (Website-Auswahl) [7 | 30 | 90 Tage] ] +[ Reiter: Übersicht | Heatmap | Einstellungen ] +Übersicht: + [ Das Wichtigste · 5. Sep. – 4. Okt. — Klartext-Satz, darunter bis zu 4 Hinweise mit Symbol ] + [ Seitenaufrufe ] [ Besucher* ] [ Kontakt-Aktionen* ] [ Ø Zeit pro Seite* ] je: Zahl, ▲/▼-Chip, „vs. “, Mini-Verlauf + [ Seitenaufrufe pro Tag (Linie + gestrichelter Vorzeitraum) ] [ Kontakt-Aktionen: Anrufe, E-Mails, WhatsApp, Links nach außen + Quote ] + [ Was angesehen wird: Seiten | Herkunft ] [ Wer zu Besuch ist: Geräte | Länder ] + [ Datenbasis: Ring mit Zustimmungsquote + Erklärung ] [ Heatmap: Vorschaubild + „Öffnen“ → Reiter Heatmap ] + Fußzeile: „Länderdaten: IP-Geolocation by DB-IP“ +``` + +- **Kopfleiste**: Statuspunkt (türkis = aktiv). Bei `devMode` statt „Analyse aktiv“ gelb: „Im Dev-Modus wird + nicht gemessen.“ „Stand“ = Ladezeitpunkt der Daten. Bei mehreren Websites ein Auswahlfeld in der Leiste + (ersetzt das bisherige `fn-project`-Panel). Der Zeitraum-Schalter erscheint nur im Reiter Übersicht + (die Heatmap zeigt fest 30 Tage). +- **Reiter**: `role="tablist"`, Zustand bleibt beim Wechsel von Zeitraum oder Website erhalten + (`analyseSel.tab`), der URL-Hash bleibt `#analyse`. Auf dem Handy horizontal wischbar. +- **Das Wichtigste**: siehe 2.3. Unter 30 Seitenaufrufen im Zeitraum: „Sobald ein paar mehr Besuche da sind, + steht hier das Wichtigste über deine Website in zwei, drei Sätzen.“ +- **Kennzahlen-Kacheln** (4): Seitenaufrufe, Besucher*, Kontakt-Aktionen*, Ø Zeit pro Seite* (Format `m:ss`, + darunter klein „Ø 64 % gescrollt“). Chip mit Veränderung (2.4). Mini-Verlauf aus `series` (Fläche + Linie, + ohne Achsen). Ohne Vergleich steht statt Chip „Vergleich ab TT.MM.“ (2.4). +- **Diagramm**: Seitenaufrufe pro Tag, aktuelle Linie türkis mit Fläche, Vorzeitraum gestrichelt grau (nach + Tagesindex ausgerichtet; nur wenn `previous.complete`). Legende „30 Tage“ / „Zeitraum davor“. Antippen oder + Überfahren eines Tages zeigt eine Blase „Sa., 19.09.: 371 Aufrufe (davor 210)“. SVG behält ``. +- **Kontakt-Aktionen**: Summe im Kopf, Zeilen Anrufe (`tel`), E-Mails (`mail`), WhatsApp (`wa`), Links nach außen + (`out`) mit Symbol, Zahl und Veränderungs-Chip; Arten mit 0 in beiden Zeiträumen entfallen. Darunter die + Quote: „Auf 100 Besucher kommen 7,8 Kontakt-Aktionen (davor 6,5).“ (erst ab 30 Besuchern; Kontakt-Aktionen sind + Klicks, keine Personen – daher „pro 100 Besucher“, nicht „x % der Besucher“). Ohne Kontakte: „Noch keine + Kontakt-Aktionen. Sie zählen, wenn jemand auf deiner Website auf Telefonnummer, E-Mail-Adresse oder + WhatsApp tippt.“ +- **Ranglisten** in zwei Karten mit kleinem Umschalter: „Was angesehen wird“ (Seiten | Herkunft), „Wer zu Besuch + ist“ (Geräte | Länder). Zeile = Balken als Hintergrund des Namens, Zahl, Anteil in %. Erst 6 Zeilen, „Alle + anzeigen“ klappt bis 20 auf. `/` heißt „Startseite“. Herkunft mit lesbaren Namen für bekannte Hosts + (google.* → Google, bing.com → Bing, instagram.com → Instagram, facebook.com/m.facebook.com/l.facebook.com → + Facebook, duckduckgo.com → DuckDuckGo, ecosia.org → Ecosia), sonst der Host; `''` → „Direkt oder unbekannt“. +- **Datenbasis** (ersetzt Banner-Liste und Sternchen-Fußnote): Ring mit `consentRate`, Text „61 % haben dem + Banner zugestimmt (2.410 von 3.950 Entscheidungen). Seitenaufrufe zählen wir vollständig; Besucher, + Kontakt-Aktionen und Zeit pro Seite (*) nur bei Zustimmung.“ Ohne Entscheidungen: „Noch hat niemand über das + Banner entschieden.“ +- **Heatmap-Karte**: kleines statisches Vorschaubild (CSS-Verlauf, keine echte Website), Satz, Knopf „Öffnen“ + wechselt in den Reiter Heatmap. + +### 2.2 Reiter Heatmap und Einstellungen, andere Zustände + +- **Heatmap**: die bestehende Heatmap in Geräterahmen (`renderAnalyseHeat`, `geraete.js/.css`, iframes, + postMessage-Zähler, 20-s-Rückfall „In neuem Tab öffnen“) unverändert im Verhalten, optisch im Cockpit-Stil + (Seitenauswahl und Computer/Tablet-Schalter in einer Leiste). Die iframes laden erst, wenn der Reiter + geöffnet wird. +- **Einstellungen**: Karte „Status“ (aktiv seit `activatedAt`, Banner-Farbe als Farbfeld, Fassung + `avvVersion`), Karte „Unterlagen“ (Links AVV und Leistungsbeschreibung aus `legal`), Karte „Analyse + ausschalten“ (`renderAnalyseOff`, Texte und Ablauf unverändert). +- **Einschalten** (`renderAnalyseActivation`), **gesperrt**, **nicht verfügbar**, **unbekannt**, **noch nicht + freigeschaltet**: gleicher Cockpit-Rahmen (Kopfleiste ohne Zeitraum und Reiter). Alle Texte, Links, Häkchen, + Farbwahl und Banner-Vorschau bleiben **wörtlich** wie heute (Rechtsrunde 01./02.10.). +- **Laden**: Platzhalter-Kacheln in der Form der echten Kacheln statt „Lade…“. **Fehler 503**: Karte + „Auswertung gerade nicht erreichbar“ mit Knopf „Neu laden“. + +### 2.3 Klartext-Regeln („Das Wichtigste“) + +Reine Funktion `summarize(stats)` → `{ lead, items: [{ icon, text }] }` in `public/analyse-view.js` +(ohne DOM, Node-testbar). Zahlen deutsch formatiert. Nur bei `totals.pv >= 30`, sonst Leerzustand (2.1). + +**Satz** (`lead`): +- Vergleich möglich (2.4): „Deine Website wurde in den letzten N Tagen **X-mal** aufgerufen – **Y % mehr** / + **Y % weniger** als in den N Tagen davor.“ Bei |Veränderung| < 5 %: „… – etwa so oft wie in den N Tagen + davor.“ Bei kleinen Zahlen (2.4) absolut: „… – **12 Aufrufe mehr** als in den N Tagen davor.“ +- Kein Vergleich: „Deine Website wurde in den letzten N Tagen **X-mal** aufgerufen.“ Liegt `firstDay` im + Zeitraum: „Seit dem Start der Analyse am TT.MM. wurde deine Website **X-mal** aufgerufen.“ + +**Hinweise** (`items`, in dieser Reihenfolge, nur wenn die Bedingung erfüllt ist, höchstens 4): +1. Kontakte (`contacts >= 1`): „N-mal hat dich jemand direkt kontaktiert, meist per Telefon (96).“ + bei + Vergleich und Vorwert ≥ 1: „Das sind 45 mehr als zuvor.“ / „… weniger …“ / „Genauso oft wie zuvor.“ + Bei 0 Kontakten und `visitors >= 30`: „Von den Besuchern mit Zustimmung hat noch niemand über die Website + angerufen oder geschrieben.“ (Kontakt-Aktionen zählen nur bei Zustimmung.) +2. Wochentag (mindestens 7 Tage mit Daten im Zeitraum und `pv >= 70`): Ø Aufrufe je Wochentag über die Tage + ab `firstDay`; stärkster Wochentag, wenn sein Schnitt ≥ 1,2 × Gesamtschnitt: „Samstag ist im Schnitt dein + stärkster Tag.“ Zusätzlich Spitzentag, wenn ≥ 1,8 × Tagesschnitt: „Spitzentag war Sa., 19.09. mit 371 + Aufrufen.“ (beides in einem Hinweis, wenn beides zutrifft). +3. Herkunft (erster Eintrag in `refs` mit Host, Anteil ≥ 20 % der Herkunfts-Summe): „Die meisten, die über + einen Link kamen, kommen von Google (880).“ +4. Gerät (`m` oder `d` ≥ 55 % der Geräte-Summe): „66 % der Aufrufe kommen vom Handy.“ / „… vom Computer.“ +5. Seite (`pages[0]`): „Am meisten angesehen: die Startseite, danach /preise (1.310).“ bzw. „Am meisten + angesehen: /preise (1.310 Aufrufe).“ + +### 2.4 Vergleich und Veränderungs-Chips + +- Vergleich möglich, wenn `previous.complete` und `previous.totals.pv >= 20`. Sonst kein Chip; Kacheln zeigen + „Vergleich ab TT.MM.“ mit TT.MM. = `firstDay` + (2 × range − 1) Tage; ohne `firstDay` gar nichts. Fehlt + `previous` ganz (alter Collector), wie „kein Vergleich“ ohne Datum. +- Funktion `delta(cur, prev, { kind })` → `{ dir: 'up'|'down'|'flat', text, aria }`: + - Zählwerte: Prozent (ganzzahlig, „▲ 14 %“), wenn `prev >= 100` und `cur >= 100`; sonst absolut („▲ 12“, + „▼ 3“). Gleich → „± 0“ (`flat`). |Prozent| < 1 → `flat` „± 0 %“. + - Sekunden: absolut, „▲ 6 s“ / „▼ 1:05“ (ab 60 s als m:ss). + - `aria`: „plus 14 Prozent gegenüber dem Zeitraum davor“ usw. Farbe nie alleiniger Träger (Pfeile ▲▼). +- Farben: mehr = türkis, weniger = rosé, gleich = grau. Bewusst **keine** Wertung „gut/schlecht“ (weniger + Zeit pro Seite kann gut sein). + +### 2.5 Optik („Cockpit“) + +- Alles unter einem Wurzelelement `.an` mit eigenen CSS-Variablen; außerhalb ändert sich nichts. + Akzent `--an-acc: #2dd4bf`, stark `#5eead4`, weich `rgba(45,212,191,.1)`; weniger `#fda4af`; + Karten `rgba(14,16,20,.92)`, Rand `rgba(255,255,255,.09)`, Radius 16 px, Abstände 14 px. +- Kartentitel einheitlich: 13 px, fett, Großbuchstaben mit Sperrung, gedämpft; Zahlen `tabular-nums`. +- „Das Wichtigste“: Verlauf türkis → violett, türkiser Rand, Satz 21 px. +- Desktop: Raster wie Entwurf A (4 Kacheln; Diagramm 1,65 : 1 Kontakte; 2 × 2 Karten). Diagramm und + Kontaktkarte gleich hoch, das Diagramm füllt die Höhe. +- Handy (≤ 880 px, App-Ansicht): Kopfleiste zweizeilig, Zeitraum-Schalter volle Breite, Kacheln 2 × 2, Hinweise + einspaltig, alle Karten untereinander. Keine waagrechte Seiten-Scrollleiste. +- `prefers-reduced-motion`: keine Übergänge. + +## 3. Code-Aufteilung + +`public/funktionen.js` (980 Zeilen) wird entlastet: +- **`public/analyse.js`** (neu): gesamte Analyse-Oberfläche – `initAnalyse`, `renderAnalyseActivation`, + `renderAnalyseOff`, Übersicht, Heatmap, Einstellungen. `app.js` lädt `/analyse.js` für die Ansicht `analyse`. +- **`public/fn-util.js`** (neu): `esc`, `safeUrl`, `request` – bisher lokal in `funktionen.js`, jetzt von + beiden Modulen importiert. +- **`public/analyse-view.js`**: bekommt die reinen Funktionen `summarize`, `delta`, `fmtDuration`, + `contactRate`, `compareFrom`, `refName`, `weekdayStats` sowie `analyseOf`, `analyseState`, + `pickAnalyseProject` (bisher in `funktionen.js`, von der Kachel und der Analyse gebraucht). +- **`public/analyse.css`** (neu, in `dashboard.html` eingebunden): alle `.an`-Regeln; die alten + `.an-*`-Regeln in `funktionen.css` entfallen, soweit sie nur die Analyse betreffen. +- Kachel „Analyse“ auf der Funktionen-Seite bleibt in `funktionen.js` (unverändert im Verhalten). +- Bestehende Element-IDs, die Tests und Abläufe nutzen, bleiben: `#analyse-body`, `#an-stage`, `#an-preview`, + `#an-avv`, `#an-on`, `#an-error`, `#an-off-*`, `#an-heat-page`. + +## 4. Tests + +- **Collector** (`analytics/test/stats.test.js`): `previous` mit richtigen Tagen und Summen; `complete` + false/true je nach `firstDay`; `firstDay` aus `daily` und Rohdaten; `series` mit visitors/contacts/avgSec; + `avgSec`/`avgScroll` null ohne en; Vorzeitraum über eine Zeitumstellung (Ende Oktober) lückenlos und ohne + Überlappung; nichts doppelt (verdichtet + live). Vertragstest `test/analyticsContract.test.js` um die neuen + Felder ergänzen. +- **Portal, rein** (`test/analyseInsights.test.js`, neu): `summarize` für Wachstum, Rückgang, gleich, kleine + Zahlen, ohne Vergleich, neu gestartet, zu wenig Daten, ohne Kontakte, Wochentag/Spitzentag, Herkunft, Gerät, + höchstens 4 Hinweise; `delta` (Prozent/absolut/flat/Sekunden/aria); `compareFrom`; `contactRate`; + `refName`; HTML-Maskierung aller Werte, die aus Daten kommen (Seitenpfade, Hosts). +- **Portal, bestehend**: `funktionenTiles.test.js` und `analyseView.test.js` auf `analyse.js`/`fn-util.js` + umstellen; alle bisherigen Text-Prüfungen (Einschalten, Ausschalten) laufen unverändert weiter. + `analyseRoutes.test.js`: `activatedAt` in der Ansicht. +- **Browser** (`test/ui/analyse.ui.mjs`, erweitert; Playwright mit gemockter API): Desktop 1440 und Handy 402 × + Zustände aktiv (volle Daten), aktiv neu (ohne Vergleich), wenig Daten, Dev-Modus, Einschalten, fehlende + Angaben, 503; Reiterwechsel; Heatmap-iframes erst nach Reiterwechsel; keine `pageerror`; keine waagrechte + Scrollleiste. Screenshots nach `/root/reports/analyse-ueberarbeitung-2026-10-04/screens/`. +- Gesamte Suiten: `npm test` (Portal) und `analytics: npm test` grün; Node 20 (Portal) und Node 24 (Collector). + +## 5. Livegang + +1. Nutzer pusht `feature/analyse-uebersicht` nach `main` (mein Push nach `main` ist im Auto-Modus gesperrt). +2. `deploy.sh` startet Portal und – weil `analytics/` geändert – den Collector neu. Keine Schema-Änderung, + keine `.env`-Änderung. +3. Prüfung durch mich: Collector `/a/health`, Stats-Abruf einer echten aktiven Website im Container + (neue Felder vorhanden, alte unverändert), Portal-Bundle enthält `analyse.js`, Ansicht lädt ohne Fehler. +4. Nutzer testet live mit einer Website mit aktiver Analyse. + +Rückweg: Portal und Collector sind gegenseitig abwärtskompatibel (alte Oberfläche ignoriert neue Felder, neue +Oberfläche kommt ohne `previous` aus); Zurückrollen = Revert-Commit auf `main`. + +## Nicht in diesem Projekt + +Besucher-Details, Website-Check, Bericht per Mail (U1); Änderungen an Messskript, Banner und Rechtstexten (U4); +WOMS; Anpassen der Banner-Farbe nach dem Einschalten; Export.