18 KiB
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-04', to: '2026-10-03', // Berliner Kalendertage des Zeitraums: abgeschlossene Tage (to = gestern)
today: { day: '2026-10-04', pv, visitors, contacts }, // heute bis jetzt, live, nicht im Zeitraum enthalten
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 vonrange × 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 ausevents_raw(wie heute). Kein Tag wird doppelt gezählt. firstDay= kleinster Tag ausdailyundevents_rawdieser Website (MIN(day)bzw.dayOf(MIN(ts))).visitorsbleibt 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).
Nachtrag 04.10.2026 (Entscheidung im Final-Review, F1): Der Zeitraum besteht aus abgeschlossenen Tagen
(endet gestern), damit der angebrochene heutige Tag nicht gegen volle Vortage verglichen wird (sonst zeigte z. B.
der 7-Tage-Blick tagsüber fälschlich „weniger“). Der heutige Stand steht getrennt in today und in der Kopfleiste
(„Heute bisher: 42 Aufrufe“). Der Satz „direkt kontaktiert“ zählt nur Telefon, E-Mail und WhatsApp (F2).
2. Oberfläche
2.1 Aufbau der Seite (Zustand „aktiv“)
[ Kopfleiste: ● Website-Name · „Analyse aktiv · Heute bisher: 42 Aufrufe · Stand: 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
devModestatt „Analyse aktiv“ gelb: „Im Dev-Modus wird nicht gemessen.“ „Stand“ = Ladezeitpunkt der Daten. Bei mehreren Websites ein Auswahlfeld in der Leiste (ersetzt das bisherigefn-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 ausseries(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, FassungavvVersion), Karte „Unterlagen“ (Links AVV und Leistungsbeschreibung auslegal), 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
firstDayim 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):
- 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 undvisitors >= 30: „Von den Besuchern mit Zustimmung hat noch niemand über die Website angerufen oder geschrieben.“ (Kontakt-Aktionen zählen nur bei Zustimmung.) - Wochentag (mindestens 7 Tage mit Daten im Zeitraum und
pv >= 70): Ø Aufrufe je Wochentag über die Tage abfirstDay; 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). - Herkunft (erster Eintrag in
refsmit Host, Anteil ≥ 20 % der Herkunfts-Summe): „Die meisten, die über einen Link kamen, kommen von Google (880).“ - Gerät (
moderd≥ 55 % der Geräte-Summe): „66 % der Aufrufe kommen vom Handy.“ / „… vom Computer.“ - 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.completeundprevious.totals.pv >= 20. Sonst kein Chip; Kacheln zeigen „Vergleich ab TT.MM.“ mit TT.MM. =firstDay+ 2 × range Tage; ohnefirstDaygar nichts. Fehltpreviousganz (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 >= 100undcur >= 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 ▲▼).
- Zählwerte: Prozent (ganzzahlig, „▲ 14 %“), wenn
- 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
.anmit eigenen CSS-Variablen; außerhalb ändert sich nichts. Akzent--an-acc: #2dd4bf, stark#5eead4, weichrgba(45,212,191,.1); weniger#fda4af; Kartenrgba(14,16,20,.92), Randrgba(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.jslädt/analyse.jsfür die Ansichtanalyse.public/fn-util.js(neu):esc,safeUrl,request– bisher lokal infunktionen.js, jetzt von beiden Modulen importiert.public/analyse-view.js: bekommt die reinen Funktionensummarize,delta,fmtDuration,contactRate,compareFrom,refName,weekdayStatssowieanalyseOf,analyseState,pickAnalyseProject(bisher infunktionen.js, von der Kachel und der Analyse gebraucht).public/analyse.css(neu, indashboard.htmleingebunden): alle.an-Regeln; die alten.an-*-Regeln infunktionen.cssentfallen, 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):previousmit richtigen Tagen und Summen;completefalse/true je nachfirstDay;firstDayausdailyund Rohdaten;seriesmit visitors/contacts/avgSec;avgSec/avgScrollnull ohne en; Vorzeitraum über eine Zeitumstellung (Ende Oktober) lückenlos und ohne Überlappung; nichts doppelt (verdichtet + live). Vertragstesttest/analyticsContract.test.jsum die neuen Felder ergänzen. - Portal, rein (
test/analyseInsights.test.js, neu):summarizefü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.jsundanalyseView.test.jsaufanalyse.js/fn-util.jsumstellen; alle bisherigen Text-Prüfungen (Einschalten, Ausschalten) laufen unverändert weiter.analyseRoutes.test.js:activatedAtin 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; keinepageerror; keine waagrechte Scrollleiste. Screenshots nach/root/reports/analyse-ueberarbeitung-2026-10-04/screens/. - Gesamte Suiten:
npm test(Portal) undanalytics: npm testgrün; Node 20 (Portal) und Node 24 (Collector).
5. Livegang
- Nutzer pusht
feature/analyse-uebersichtnachmain(mein Push nachmainist im Auto-Modus gesperrt). deploy.shstartet Portal und – weilanalytics/geändert – den Collector neu. Keine Schema-Änderung, keine.env-Änderung.- Prüfung durch mich: Collector
/a/health, Stats-Abruf einer echten aktiven Website im Container (neue Felder vorhanden, alte unverändert), Portal-Bundle enthältanalyse.js, Ansicht lädt ohne Fehler. - 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.