From f0bced164be065394f226d8ed02656b037422967 Mon Sep 17 00:00:00 2001
From: root
Date: Sun, 4 Oct 2026 15:02:04 +0000
Subject: [PATCH] =?UTF-8?q?Kundenportal-B=C3=BChne:=20Gruppe=20=E2=80=9EWe?=
=?UTF-8?q?itere=20Funktionen=E2=80=9C,=20Unterpunkte=20als=20Stichpunkte?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Wunsch des Inhabers: Die drei Funktionen heißen als Gruppe „Weitere Funktionen“ und
beschreiben sich in drei Stichpunkten statt in einem Satz (Liste der Bühne und Karten
der Wisch-Reihe). Der Bedienhinweis der Heatmap („Zeigen Sie mit der Maus …“ bzw.
„Tippen Sie …“) steht jetzt im Hinweis unter dem MacBook und in der Karte.
Co-Authored-By: Claude Opus 5.5
---
.../specs/2026-10-04-funktionen-demos.md | 13 ++--
src/components/home/Kundenportal.tsx | 67 +++++++++++++++----
src/components/home/kundenportal.css | 49 +++++++++++++-
src/test/relaunch-kundenportal.test.tsx | 38 ++++++-----
4 files changed, 131 insertions(+), 36 deletions(-)
diff --git a/docs/superpowers/specs/2026-10-04-funktionen-demos.md b/docs/superpowers/specs/2026-10-04-funktionen-demos.md
index 101db5c..f1a849c 100644
--- a/docs/superpowers/specs/2026-10-04-funktionen-demos.md
+++ b/docs/superpowers/specs/2026-10-04-funktionen-demos.md
@@ -14,16 +14,19 @@ Dieses Dokument ergänzt `2026-10-01-relaunch-design.md` (Abschnitt 7.2 Kundenpo
## 1. Stationen der Bühne (Reihenfolge)
1. Live-Vorschau · 2. Fortschritt · 3. Chat · 4. Rechnungen (unverändert)
-5. **Terminplaner** · 6. **Website-Analyse** · 7. **News-Pinnwand** – Gruppe „Funktionen zum Dazubuchen“
+5. **Terminplaner** · 6. **Website-Analyse** · 7. **News-Pinnwand** – Gruppe „Weitere Funktionen“ (Inhaber 2026-10-04: so nennen, Unterpunkte nur in Stichpunkten)
8. Als App (unverändert, bleibt die letzte Station mit dem langen Screenshot)
Texte (Sie-Form, keine Erfolgsversprechen, keine Zahlen ohne Quelle):
-| Station | Titel | Unterschrift |
+| Station | Titel | Stichpunkte |
|---|---|---|
-| 5 | Terminplaner | Gäste buchen auf Ihrer Website – die Termine sehen Sie im Portal und auf Wunsch im Google Kalender. |
-| 6 | Website-Analyse | Sehen Sie, wohin Ihre Besucher klicken. Zeigen Sie mit der Maus auf die Seite. |
-| 7 | News-Pinnwand | Neuigkeiten mit Text und Foto, vom Handy direkt auf Ihre Website. |
+| 5 | Terminplaner | Gäste buchen direkt auf Ihrer Website · Termine und Anfragen im Portal · Kalender-Abo, z. B. Google Kalender |
+| 6 | Website-Analyse | Heatmap: wohin Besucher klicken · Aufrufe, Herkunft und Geräte · Im Wartungs-Abo enthalten |
+| 7 | News-Pinnwand | Text und Foto, vom Handy aus · Hängt direkt auf Ihrer Website · Laufzeit je Zettel wählbar |
+
+Bedienhinweis der Heatmap (nicht in den Stichpunkten, sondern im Hinweis unter dem MacBook bzw. in der Karte):
+„Zeigen Sie mit der Maus auf die Seite.“ (Maus) / „Tippen Sie auf eine Stelle der Seite.“ (Touch).
Wahrheit der Aussagen (geprüft im Portal-Code und in den Spezifikationen):
- Terminplaner: Buchung im Widget, Gast bestätigt per Link in der E-Mail, Betrieb bekommt eine Mail
diff --git a/src/components/home/Kundenportal.tsx b/src/components/home/Kundenportal.tsx
index 4e2a984..db94972 100644
--- a/src/components/home/Kundenportal.tsx
+++ b/src/components/home/Kundenportal.tsx
@@ -55,9 +55,12 @@ type DemoArt = "termin" | "heatmap" | "pinnwand";
type Funktion = {
id: string;
titel: string;
- unterschrift: string;
- /** Abweichende Unterschrift in der Wisch-Reihe (dort gibt es keine Maus). */
- unterschriftHandy?: string;
+ /** Ein Satz unter dem Titel (Portal-Stationen); die Funktionen haben stattdessen Stichpunkte. */
+ unterschrift?: string;
+ /** Stichpunkte unter dem Titel (die drei Funktionen, Wunsch des Inhabers 2026-10-04). */
+ stichpunkte?: string[];
+ /** Bedienhinweis zur Live-Demo, je nachdem, ob es eine Maus gibt (nur Website-Analyse). */
+ bedienhinweis?: { maus: string; tippen: string };
/** Desktop-Bild; Demo-Stationen zeigen stattdessen die Website, "Als App" hat keines (dort blendet das Fenster aus). */
desktop?: { name: string; alt: string };
handy: { name: string; alt: string };
@@ -116,7 +119,7 @@ const FUNKTIONEN: Funktion[] = [
{
id: "terminplaner",
titel: "Terminplaner",
- unterschrift: "Gäste buchen auf Ihrer Website – die Termine sehen Sie im Portal und auf Wunsch im Google Kalender.",
+ stichpunkte: ["Gäste buchen direkt auf Ihrer Website", "Termine und Anfragen im Portal", "Kalender-Abo, z. B. Google Kalender"],
demo: "termin",
adresse: "salon-schnittstelle.example",
beschreibung:
@@ -131,8 +134,8 @@ const FUNKTIONEN: Funktion[] = [
{
id: "website-analyse",
titel: "Website-Analyse",
- unterschrift: "Sehen Sie, wohin Ihre Besucher klicken. Zeigen Sie mit der Maus auf die Seite.",
- unterschriftHandy: "Sehen Sie, wohin Ihre Besucher klicken. Tippen Sie auf eine Stelle der Seite.",
+ stichpunkte: ["Heatmap: wohin Besucher klicken", "Aufrufe, Herkunft und Geräte", "Im Wartungs-Abo enthalten"],
+ bedienhinweis: { maus: "Zeigen Sie mit der Maus auf die Seite.", tippen: "Tippen Sie auf eine Stelle der Seite." },
demo: "heatmap",
adresse: "boulderhalle-ueberhang.example",
beschreibung:
@@ -145,7 +148,7 @@ const FUNKTIONEN: Funktion[] = [
{
id: "news-pinnwand",
titel: "News-Pinnwand",
- unterschrift: "Neuigkeiten mit Text und Foto, vom Handy direkt auf Ihre Website.",
+ stichpunkte: ["Text und Foto, vom Handy aus", "Hängt direkt auf Ihrer Website", "Laufzeit je Zettel wählbar"],
demo: "pinnwand",
adresse: "musterbach.example",
beschreibung: "Beispiel-Website einer Ortsgemeinde mit einer Pinnwand, an der ein neuer Zettel aufgehängt wird",
@@ -171,7 +174,7 @@ const LETZTER = FUNKTIONEN.length - 1;
/** Stationen vor "Als App": normale Schritte und längere Demo-Schritte (bestimmen die Scrollstrecke der Hülle). */
const ANZAHL_DEMO = FUNKTIONEN.filter((f) => f.demo).length;
const ANZAHL_NORMAL = LETZTER - ANZAHL_DEMO;
-const GRUPPE_TITEL = "Funktionen zum Dazubuchen";
+const GRUPPE_TITEL = "Weitere Funktionen";
// ── Demos (eigener Chunk, erst geladen, wenn die Bühne die Station fast erreicht) ──────────────────────
@@ -214,6 +217,36 @@ const INERT = (parseInt(version, 10) >= 19 ? { inert: true } : { inert: "" }) as
const beispielText = (f: Funktion) => `Erfundenes Beispiel · ${f.adresse}`;
+/**
+ * Unterschrift einer Station: ein Satz oder Stichpunkte. In der Liste steckt sie in einem
);
if (!f.demo) return
{eintrag}
;
@@ -484,7 +518,11 @@ const Buehne = () => {
data-an={f.demo === aktivDemo ? "ja" : "nein"}
aria-hidden={f.demo !== aktivDemo || undefined}
>
- {beispielText(f)}
+ {/* Website-Analyse: statt der Adresse (steht in der Safari-Leiste) der Bedienhinweis, damit es eine Zeile bleibt;
+ ohne Maus (Tablet quer) heißt es „Tippen Sie“ statt „Zeigen Sie mit der Maus“ */}
+ {f.bedienhinweis
+ ? `Erfundenes Beispiel · ${fein ? f.bedienhinweis.maus : f.bedienhinweis.tippen}`
+ : beispielText(f)}
);
})}
diff --git a/src/components/home/kundenportal.css b/src/components/home/kundenportal.css
index edc7c39..5ea81b8 100644
--- a/src/components/home/kundenportal.css
+++ b/src/components/home/kundenportal.css
@@ -114,6 +114,22 @@
.kp-eintrag[aria-current="step"] .kp-eintrag__titel {
color: var(--fg);
}
+/* Stichpunkte der drei Funktionen: je Punkt eine Zeile mit kleinem runden Punkt davor */
+.kp-stichpunkt {
+ position: relative;
+ display: block;
+ padding-left: 0.95em;
+}
+.kp-stichpunkt::before {
+ content: "";
+ position: absolute;
+ left: 0.15em;
+ top: calc(0.7em - 2px);
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: currentColor;
+}
/* kleiner roter Punkt als Marker der aktiven Funktion (hängt im Rand links vom Text) */
.kp-eintrag__punkt {
position: absolute;
@@ -178,7 +194,7 @@
}
}
-/* Gruppe "Funktionen zum Dazubuchen": die drei Demo-Stationen mit kleiner Überschrift darüber */
+/* Gruppe "Weitere Funktionen": die drei Demo-Stationen mit kleiner Überschrift darüber */
.kp-gruppe[data-erste="ja"] {
margin-top: 10px;
}
@@ -375,6 +391,37 @@
.kp-karte__text {
max-width: 26ch;
}
+/* Stichpunkte in den Karten: linksbündige Liste, als Block in der Kartenmitte */
+.kp-karte__punkte {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 2px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ text-align: left;
+}
+.kp-karte__punkte li {
+ position: relative;
+ padding-left: 0.95em;
+}
+.kp-karte__punkte li::before {
+ content: "";
+ position: absolute;
+ left: 0.15em;
+ top: calc(0.7em - 2px);
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: currentColor;
+}
+.kp-karte__bedienen {
+ margin: 0.35rem 0 0;
+ font-size: var(--fs-small);
+ font-weight: 600;
+ color: var(--fg);
+}
/* Demo-Karten: die Website läuft im Handy, darunter Hinweis aufs erfundene Beispiel (Heatmap: zusätzlich die Diagnose) */
.kp-karte__geraet {
diff --git a/src/test/relaunch-kundenportal.test.tsx b/src/test/relaunch-kundenportal.test.tsx
index f277966..88a9568 100644
--- a/src/test/relaunch-kundenportal.test.tsx
+++ b/src/test/relaunch-kundenportal.test.tsx
@@ -11,14 +11,15 @@ const WURZEL = `${process.cwd()}/`;
const BILDORDNER = join(WURZEL, "public/relaunch/portal");
const TITEL = ["Live-Vorschau", "Fortschritt", "Chat", "Rechnungen", "Terminplaner", "Website-Analyse", "News-Pinnwand", "Als App"];
-const UNTERSCHRIFTEN = [
+/** Ein Satz je Portal-Station, Stichpunkte bei den drei Funktionen (Wunsch des Inhabers 2026-10-04). */
+const UNTERSCHRIFTEN: (string | string[])[] = [
"Ihre Website, noch während wir daran bauen.",
"Was wir zuletzt erledigt haben – in einfachen Sätzen.",
"Frage, Foto oder Sprachnachricht direkt ans Team.",
"Ansehen, bezahlen, herunterladen.",
- "Gäste buchen auf Ihrer Website – die Termine sehen Sie im Portal und auf Wunsch im Google Kalender.",
- "Sehen Sie, wohin Ihre Besucher klicken. Zeigen Sie mit der Maus auf die Seite.",
- "Neuigkeiten mit Text und Foto, vom Handy direkt auf Ihre Website.",
+ ["Gäste buchen direkt auf Ihrer Website", "Termine und Anfragen im Portal", "Kalender-Abo, z. B. Google Kalender"],
+ ["Heatmap: wohin Besucher klicken", "Aufrufe, Herkunft und Geräte", "Im Wartungs-Abo enthalten"],
+ ["Text und Foto, vom Handy aus", "Hängt direkt auf Ihrer Website", "Laufzeit je Zettel wählbar"],
"Aufs Handy installieren, fertig.",
];
const SCHRITTE = [
@@ -90,9 +91,14 @@ describe("Kundenportal: Text und Reihenfolge", () => {
it("Handy: die acht Funktionen stehen in der richtigen Reihenfolge mit ihren Unterschriften", () => {
zeigeHandy();
expect(screen.getAllByRole("heading", { level: 3 }).map((h) => h.textContent)).toEqual(TITEL);
- // in der Wisch-Reihe gibt es keine Maus: die Analyse sagt "Tippen" statt "mit der Maus zeigen"
- const handyTexte = UNTERSCHRIFTEN.map((t) => (t.includes("mit der Maus") ? "Sehen Sie, wohin Ihre Besucher klicken. Tippen Sie auf eine Stelle der Seite." : t));
- for (const text of handyTexte) expect(screen.getByText(text)).toBeInTheDocument();
+ for (const text of UNTERSCHRIFTEN.flat()) expect(screen.getByText(text)).toBeInTheDocument();
+ // Stichpunkte stehen in den Karten als echte Liste
+ for (const punkte of UNTERSCHRIFTEN.filter(Array.isArray)) {
+ expect(screen.getByText(punkte[0]).closest("ul")).toHaveClass("kp-karte__punkte");
+ }
+ // in der Wisch-Reihe gibt es keine Maus: der Bedienhinweis der Analyse sagt "Tippen"
+ expect(screen.getByText("Tippen Sie auf eine Stelle der Seite.")).toBeInTheDocument();
+ expect(screen.queryByText(/mit der Maus/)).toBeNull();
});
it("Desktop: die Liste der Bühne hat dieselben acht Funktionen in derselben Reihenfolge, die drei Demos als Gruppe", () => {
@@ -102,22 +108,21 @@ describe("Kundenportal: Text und Reihenfolge", () => {
expect(eintraege).toHaveLength(8);
TITEL.forEach((titel, i) => {
expect(eintraege[i]).toHaveTextContent(new RegExp(`^${titel}`));
- expect(eintraege[i]).toHaveTextContent(UNTERSCHRIFTEN[i]);
+ for (const text of [UNTERSCHRIFTEN[i]].flat()) expect(eintraege[i]).toHaveTextContent(text);
});
// Gruppenüberschrift steht über dem ersten der drei Funktions-Einträge, die Gruppe umfasst genau diese drei
- const gruppe = screen.getByText("Funktionen zum Dazubuchen");
+ const gruppe = screen.getByText("Weitere Funktionen");
expect(gruppe.closest("li")).toContainElement(eintraege[4]);
expect(Array.from(liste.querySelectorAll("li.kp-gruppe"))).toHaveLength(3);
expect(eintraege[3].closest("li")).not.toHaveClass("kp-gruppe");
expect(eintraege[7].closest("li")).not.toHaveClass("kp-gruppe");
});
- it("Tablet quer (Bühne ohne Maus): die Unterschrift der Website-Analyse sagt „Tippen Sie“ statt „Zeigen Sie mit der Maus“", () => {
- zeigeTablet();
- const liste = screen.getByRole("list", { name: "Funktionen des Kundenportals" });
- const analyse = within(liste).getAllByRole("button")[5];
- expect(analyse).toHaveTextContent("Tippen Sie auf eine Stelle der Seite.");
- expect(analyse).not.toHaveTextContent("Maus");
+ it("Tablet quer (Bühne ohne Maus): der Bedienhinweis der Website-Analyse sagt „Tippen Sie“ statt „Zeigen Sie mit der Maus“", () => {
+ const { container } = zeigeTablet();
+ const hinweis = container.querySelectorAll(".kp-beispiel")[1];
+ expect(hinweis).toHaveTextContent("Tippen Sie auf eine Stelle der Seite.");
+ expect(hinweis).not.toHaveTextContent("Maus");
});
it.each([
@@ -396,7 +401,8 @@ describe("Kundenportal: Bühne wechselt mit dem IntersectionObserver", () => {
const hinweise = Array.from(container.querySelectorAll(".kp-beispiel"));
expect(hinweise.map((h) => h.textContent)).toEqual([
"Erfundenes Beispiel · salon-schnittstelle.example",
- "Erfundenes Beispiel · boulderhalle-ueberhang.example",
+ // mit Maus (diese Bühne meldet eine feine Zeigereingabe): Hinweis zum Zeigen
+ "Erfundenes Beispiel · Zeigen Sie mit der Maus auf die Seite.",
"Erfundenes Beispiel · musterbach.example",
]);
expect(hinweise.map((h) => h.getAttribute("data-an"))).toEqual(["nein", "ja", "nein"]);