Files
Webklar-Kundenbereich/docs/superpowers/specs/2026-10-04-analyse-uebersicht-design.md
T
2026-10-04 21:34:43 +00:00

18 KiB
Raw Blame History

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):

{
  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. <Vorwert>“, 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 <title>.
  • 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.