Relaunch Phase 1: Feinschliff nach Browser- und Code-Prüfung

- Kopfleiste voll deckend, nichts scheint beim Scrollen durch
- Leistungen: Überschrift bleibt links stehen, während die Liste vorbeizieht
- langer Handy-Screenshot unten beschnitten (kein schwarzer Streifen am Ende)
- Titel, Beschreibung, og/twitter nach Spezifikation 6.5; canonical je Route
- Über uns ohne alte Versprechen, nur belegte Aussagen
- Schrittzeile neutral benannt, Tippflächen 44 px, Navigation als eigenes Modul

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-01 15:33:27 +00:00
1 parent 1c8cfa5c1c
commit 6e8ba2cddb
18 files changed
+98 -110

No files matched your search

+6 -6
View File
@@ -25,18 +25,18 @@
</script> </script>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WEBklar – Webagentur für KMU | Digitalisierung &amp; Automatisierung</title> <title>WEBklar – Websites, Shops und Apps für kleine Betriebe</title>
<meta <meta
name="description" name="description"
content="WEBklar digitalisiert KMU: Website, Prozesse und Systeme aus einer Hand. Kostenlose Potenzialanalyse – Antwort spätestens am nächsten Werktag." content="WEBklar baut Websites, Online-Shops und Web-Apps. Im Kundenportal sehen Sie jeden Schritt, mit Live-Vorschau Ihrer Website."
/> />
<meta name="author" content="WEBklar" /> <meta name="author" content="WEBklar" />
<link rel="canonical" href="https://webklar.com/" /> <link rel="canonical" href="https://webklar.com/" />
<meta property="og:title" content="WEBklar – Webagentur für KMU" /> <meta property="og:title" content="WEBklar – Websites, Shops und Apps für kleine Betriebe" />
<meta <meta
property="og:description" property="og:description"
content="Website, Automatisierung und vernetzte Prozesse für wachsende Unternehmen – ohne Mehraufwand im Alltag." content="WEBklar baut Websites, Online-Shops und Web-Apps. Im Kundenportal sehen Sie jeden Schritt, mit Live-Vorschau Ihrer Website."
/> />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="https://webklar.com/" /> <meta property="og:url" content="https://webklar.com/" />
@@ -45,10 +45,10 @@
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@WEBklar" /> <meta name="twitter:site" content="@WEBklar" />
<meta name="twitter:title" content="WEBklar – Webagentur für KMU" /> <meta name="twitter:title" content="WEBklar – Websites, Shops und Apps für kleine Betriebe" />
<meta <meta
name="twitter:description" name="twitter:description"
content="Digitalisierung für KMU: Website, Prozesse und Systeme. Kostenlose Potenzialanalyse." content="WEBklar baut Websites, Online-Shops und Web-Apps. Im Kundenportal sehen Sie jeden Schritt, mit Live-Vorschau Ihrer Website."
/> />
<meta name="twitter:image" content="https://webklar.com/og-image.png" /> <meta name="twitter:image" content="https://webklar.com/og-image.png" />
</head> </head>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

+6 -1
View File
@@ -34,6 +34,9 @@ BUDGET_HANDY = 60 * KB
# Der lange Screenshot ist gut dreimal so hoch wie ein Handy-Bild (5428 statt 1688 px). Sein Budget # Der lange Screenshot ist gut dreimal so hoch wie ein Handy-Bild (5428 statt 1688 px). Sein Budget
# wächst mit der Höhe (je Bildschirmhöhe weiterhin unter 60 KB), sonst wäre der Text nur noch Matsch. # wächst mit der Höhe (je Bildschirmhöhe weiterhin unter 60 KB), sonst wäre der Text nur noch Matsch.
BUDGET_LANG = {390: 80 * KB, 780: 150 * KB} BUDGET_LANG = {390: 80 * KB, 780: 150 * KB}
# Unter der letzten Inhaltszeile (Zeile 5179) ist der Rohexport nur noch schwarz. Abgeschnitten wird
# 40 px darunter, sonst endet die Verschiebung im Handy-Rahmen auf einem leeren Streifen.
LANG_HOEHE = 5219
# Dateiname (ohne .webp) -> (Art, Zielbreiten) # Dateiname (ohne .webp) -> (Art, Zielbreiten)
BILDER = { BILDER = {
@@ -43,7 +46,7 @@ BILDER = {
"desktop-3b-chat-foto": ("desktop", (800, 1200, 1600)), "desktop-3b-chat-foto": ("desktop", (800, 1200, 1600)),
"desktop-4-rechnungen": ("desktop", (800, 1200, 1600)), "desktop-4-rechnungen": ("desktop", (800, 1200, 1600)),
"desktop-7-news-pinnwand": ("desktop", (800, 1200, 1600)), "desktop-7-news-pinnwand": ("desktop", (800, 1200, 1600)),
# Handy (Roh 780 x 1688, das lange Bild 780 x 5428) # Handy (Roh 780 x 1688, das lange Bild 780 x 5428, beschnitten auf 5219)
"phone-1-projekte": ("handy", (390, 780)), "phone-1-projekte": ("handy", (390, 780)),
"phone-3-projektdetails": ("handy", (390, 780)), "phone-3-projektdetails": ("handy", (390, 780)),
"phone-4b-chat-foto": ("handy", (390, 780)), "phone-4b-chat-foto": ("handy", (390, 780)),
@@ -81,6 +84,8 @@ def main() -> int:
fehler += 1 fehler += 1
continue continue
roh = Image.open(quelle).convert("RGB") roh = Image.open(quelle).convert("RGB")
if name.endswith("-lang"):
roh = roh.crop((0, 0, roh.width, LANG_HOEHE))
for breite in breiten: for breite in breiten:
if name.endswith("-lang"): if name.endswith("-lang"):
budget = BUDGET_LANG[breite] budget = BUDGET_LANG[breite]
+5 -5
View File
@@ -32,7 +32,7 @@ const desktopBild = (name: string, alt: string): RahmenBildDaten => ({
alt, alt,
}); });
/** Handy-Bild: Rohformat 780 x 1688 (das lange Bild 780 x 5428), ausgeliefert in 390 / 780 px. */ /** Handy-Bild: Rohformat 780 x 1688 (das lange Bild 780 x 5219), ausgeliefert in 390 / 780 px. */
const handyBild = (name: string, alt: string, sizes: string, hoehe = 1688): RahmenBildDaten => ({ const handyBild = (name: string, alt: string, sizes: string, hoehe = 1688): RahmenBildDaten => ({
src: `${BILDER}/${name}-780.webp`, src: `${BILDER}/${name}-780.webp`,
srcSet: srcSet(name, HANDY_BREITEN), srcSet: srcSet(name, HANDY_BREITEN),
@@ -291,7 +291,7 @@ const Buehne = () => {
> >
{f.lang ? ( {f.lang ? (
<div className="kp-lang"> <div className="kp-lang">
<RahmenBild bild={handyBild(f.handy.name, f.handy.alt, "(min-width: 1200px) 340px, 30vw", 5428)} /> <RahmenBild bild={handyBild(f.handy.name, f.handy.alt, "(min-width: 1200px) 340px, 30vw", 5219)} />
</div> </div>
) : ( ) : (
<RahmenBild bild={handyBild(f.handy.name, f.handy.alt, "(min-width: 1200px) 230px, 20vw")} /> <RahmenBild bild={handyBild(f.handy.name, f.handy.alt, "(min-width: 1200px) 230px, 20vw")} />
@@ -326,7 +326,7 @@ const LangeKarte = ({ f }: { f: Funktion }) => {
<div className="kp-karte" ref={ref} data-sichtbar={sichtbar ? "ja" : "nein"}> <div className="kp-karte" ref={ref} data-sichtbar={sichtbar ? "ja" : "nein"}>
<HandyRahmen className="kp-karte__handy"> <HandyRahmen className="kp-karte__handy">
<div className="kp-lang"> <div className="kp-lang">
<RahmenBild bild={handyBild(f.handy.name, f.handy.alt, "250px", 5428)} /> <RahmenBild bild={handyBild(f.handy.name, f.handy.alt, "250px", 5219)} />
</div> </div>
</HandyRahmen> </HandyRahmen>
<h3 className="wk-h3 kp-karte__titel">{f.titel}</h3> <h3 className="wk-h3 kp-karte__titel">{f.titel}</h3>
@@ -358,7 +358,7 @@ const HandyReihe = () => (
const Schrittzeile = ({ desktop }: { desktop: boolean }) => const Schrittzeile = ({ desktop }: { desktop: boolean }) =>
desktop ? ( desktop ? (
<div className="wk-container"> <div className="wk-container">
<ol className="kp-schritte wk-reveal" aria-label="So arbeiten wir"> <ol className="kp-schritte wk-reveal" aria-label="Ablauf in vier Schritten">
{SCHRITTE.map((s) => ( {SCHRITTE.map((s) => (
<li key={s.nr} className="kp-schritt"> <li key={s.nr} className="kp-schritt">
<span className="kp-schritt__nr wk-num">{s.nr}</span> <span className="kp-schritt__nr wk-num">{s.nr}</span>
@@ -369,7 +369,7 @@ const Schrittzeile = ({ desktop }: { desktop: boolean }) =>
</div> </div>
) : ( ) : (
<div className="kp-schritte-handy"> <div className="kp-schritte-handy">
<WischReihe ariaLabel="So arbeiten wir, vier Schritte, seitlich wischen" className="kp-schritte-reihe"> <WischReihe ariaLabel="Ablauf in vier Schritten, seitlich wischen" className="kp-schritte-reihe">
{SCHRITTE.map((s) => ( {SCHRITTE.map((s) => (
<div key={s.nr} className="kp-schritt-karte"> <div key={s.nr} className="kp-schritt-karte">
<span className="kp-schritt__nr wk-num">{s.nr}</span> <span className="kp-schritt__nr wk-num">{s.nr}</span>
+6 -1
View File
@@ -7,7 +7,12 @@
align-items: start; align-items: start;
} }
.wk-ueber-uns__link { .wk-ueber-uns__link {
margin-top: 1.5rem; margin-top: 1.25rem;
}
.wk-ueber-uns__link a {
display: inline-flex;
align-items: center;
min-height: 44px;
} }
@media (max-width: 899.98px) { @media (max-width: 899.98px) {
.wk-ueber-uns { .wk-ueber-uns {
+4 -4
View File
@@ -210,8 +210,8 @@
transform: translate(-35cqw, -16.75cqw) scale(1.5); transform: translate(-35cqw, -16.75cqw) scale(1.5);
} }
/* Der lange Screenshot: gleiche Breite wie der Schirm, 68,9 % seiner Höhe wandern aus dem Bild /* Der lange Screenshot: gleiche Breite wie der Schirm, 67,66 % seiner Höhe wandern aus dem Bild
((5428 - 1688) / 5428). Die Verschiebung hängt am Scrollfortschritt (--kp-q), kein eigener Scrollbereich. */ ((5219 - 1688) / 5219). Die Verschiebung hängt am Scrollfortschritt (--kp-q), kein eigener Scrollbereich. */
.kp-lang { .kp-lang {
position: absolute; position: absolute;
top: 0; top: 0;
@@ -233,7 +233,7 @@
transition: transform var(--dur-enter) var(--ease); transition: transform var(--dur-enter) var(--ease);
} }
.kp-buehne .kp-lang { .kp-buehne .kp-lang {
transform: translateY(calc(var(--kp-q, 0) * -68.9%)); transform: translateY(calc(var(--kp-q, 0) * -67.66%));
} }
/* die große Ebene nur im letzten Schritt auf die Grafikkarte legen */ /* die große Ebene nur im letzten Schritt auf die Grafikkarte legen */
.kp-buehne[data-app="ja"] .kp-lang { .kp-buehne[data-app="ja"] .kp-lang {
@@ -276,7 +276,7 @@
transform: translateY(0); transform: translateY(0);
} }
100% { 100% {
transform: translateY(-68.9%); transform: translateY(-67.66%);
} }
} }
+3
View File
@@ -68,6 +68,9 @@
.wk-leistungen__kopf { .wk-leistungen__kopf {
grid-column: 1; grid-column: 1;
grid-row: 1 / span 2; grid-row: 1 / span 2;
position: sticky;
top: calc(var(--header-h) + 3rem);
align-self: start;
} }
.wk-leistungen__liste { .wk-leistungen__liste {
grid-column: 2; grid-column: 2;
+1 -10
View File
@@ -3,18 +3,9 @@ import { Link, useLocation } from "react-router-dom";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { Moon, Sun } from "lucide-react"; import { Moon, Sun } from "lucide-react";
import Logo from "@/components/Logo"; import Logo from "@/components/Logo";
import { projekte } from "@/data/projekte"; import { NAVIGATION } from "./navigation";
import "./site.css"; import "./site.css";
/** Navigation der Kopfleiste (Spezifikation 6.1). „Projekte“ erscheint erst mit freigegebenen Referenzen. */
export const NAVIGATION = [
{ label: "Kundenportal", to: "/#kundenportal" },
{ label: "Was wir bauen", to: "/#leistungen" },
...(projekte.length > 0 ? [{ label: "Projekte", to: "/#projekte" }] : []),
{ label: "Preise", to: "/#preise" },
{ label: "Über uns", to: "/#ueber-uns" },
];
/** Hell/Dunkel umschalten. Vor dem ersten Rendern ist das Thema unbekannt, daher kein Symbolwechsel beim Laden. */ /** Hell/Dunkel umschalten. Vor dem ersten Rendern ist das Thema unbekannt, daher kein Symbolwechsel beim Laden. */
const ThemaSchalter = ({ mitText = false }: { mitText?: boolean }) => { const ThemaSchalter = ({ mitText = false }: { mitText?: boolean }) => {
const { resolvedTheme, setTheme } = useTheme(); const { resolvedTheme, setTheme } = useTheme();
+10
View File
@@ -0,0 +1,10 @@
import { projekte } from "@/data/projekte";
/** Navigation der Kopfleiste (Spezifikation 6.1). „Projekte“ erscheint erst mit freigegebenen Referenzen. */
export const NAVIGATION = [
{ label: "Kundenportal", to: "/#kundenportal" },
{ label: "Was wir bauen", to: "/#leistungen" },
...(projekte.length > 0 ? [{ label: "Projekte", to: "/#projekte" }] : []),
{ label: "Preise", to: "/#preise" },
{ label: "Über uns", to: "/#ueber-uns" },
];
+3 -2
View File
@@ -4,7 +4,7 @@
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 50; z-index: 50;
background: color-mix(in srgb, var(--bg) 94%, transparent); background: var(--bg);
border-bottom: 1px solid transparent; border-bottom: 1px solid transparent;
transition: border-color var(--dur-state) var(--ease); transition: border-color var(--dur-state) var(--ease);
} }
@@ -233,7 +233,8 @@
color: var(--fg); color: var(--fg);
font-weight: 600; font-weight: 600;
font-size: 1rem; font-size: 1rem;
margin-bottom: 0.75rem; min-height: 44px;
margin-bottom: 0.25rem;
} }
.wk-footer__marke svg { .wk-footer__marke svg {
border-radius: 6px; border-radius: 6px;
+17 -2
View File
@@ -1,9 +1,23 @@
import { useEffect } from "react"; import { useEffect } from "react";
const SITE_NAME = "WEBklar"; const SITE_NAME = "WEBklar";
const DEFAULT_TITLE = `${SITE_NAME} – Webagentur für KMU`; const DEFAULT_TITLE = `${SITE_NAME} – Websites, Shops und Apps für kleine Betriebe`;
const DEFAULT_DESCRIPTION = const DEFAULT_DESCRIPTION =
"WEBklar digitalisiert KMU: Website, Prozesse und Systeme aus einer Hand. Kostenlose Potenzialanalyse – Antwort spätestens am nächsten Werktag."; "WEBklar baut Websites, Online-Shops und Web-Apps. Im Kundenportal sehen Sie jeden Schritt, mit Live-Vorschau Ihrer Website.";
const SITE_URL = "https://webklar.com";
/** Kanonische Adresse je Route, damit Unterseiten nicht als Kopie der Startseite gelten. */
function setCanonical(pathname: string): void {
const href = `${SITE_URL}${pathname === "/" ? "/" : pathname.replace(/\/$/, "")}`;
let el = document.querySelector('link[rel="canonical"]');
if (!el) {
el = document.createElement("link");
el.setAttribute("rel", "canonical");
document.head.appendChild(el);
}
el.setAttribute("href", href);
setMetaTag("property", "og:url", href);
}
function setMetaTag( function setMetaTag(
attribute: "name" | "property", attribute: "name" | "property",
@@ -33,6 +47,7 @@ export function usePageMeta(title: string, description?: string): void {
setMetaTag("property", "og:description", desc); setMetaTag("property", "og:description", desc);
setMetaTag("name", "twitter:title", fullTitle); setMetaTag("name", "twitter:title", fullTitle);
setMetaTag("name", "twitter:description", desc); setMetaTag("name", "twitter:description", desc);
setCanonical(window.location.pathname);
return () => { return () => {
document.title = DEFAULT_TITLE; document.title = DEFAULT_TITLE;
+23 -40
View File
@@ -2,29 +2,34 @@ import { Link } from "react-router-dom";
import { usePageMeta } from "@/hooks/use-page-meta"; import { usePageMeta } from "@/hooks/use-page-meta";
import "./unterseiten.css"; import "./unterseiten.css";
/** „Der Unterschied“: nummerierte Textzeilen statt Karten (Phase 2 schreibt die ganze Seite neu). */ /** Was man bei uns sicher bekommt (Spezifikation 6.4, nur belegte Aussagen; Team mit Namen und Fotos erst nach O4). */
const UNTERSCHIED = [ const SO_ARBEITEN_WIR_ZUSAMMEN = [
{ {
nr: "01", nr: "01",
titel: "Alles aus einer Hand", titel: "Direkt mit dem Team",
text: "Website, Automation und Schnittstellen aus einer Hand – ein Ansprechpartner statt fünf Agenturen.", text: "Ihre Fragen gehen an die Leute, die an Ihrem Projekt arbeiten – im Chat des Kundenportals, per E-Mail oder am Telefon.",
}, },
{ {
nr: "02", nr: "02",
titel: "Systeme statt Inseln", titel: "Alles im Kundenportal",
text: "CRM, Formulare und Website tauschen Daten – ohne manuelles Nachpflegen.", text: "Fortschritt, Live-Vorschau Ihrer Website, Chat und Rechnungen finden Sie an einem Ort unter project.webklar.com.",
}, },
{ {
nr: "03", nr: "03",
titel: "Betreuung im Abo", titel: "Preis vorher",
text: "Nach dem Go-live kümmern wir uns im Wartungs-Abo um Updates und kleine Änderungen.", text: "Sie bekommen ein Angebot mit Preis, bevor wir anfangen. Weitere Arbeit gibt es nur mit Ihrer Freigabe.",
},
{
nr: "04",
titel: "Server in Deutschland",
text: "Ihre Website und das Kundenportal laufen auf unserem Server im Rechenzentrum von Hetzner in Falkenstein, Sachsen.",
}, },
]; ];
const About = () => { const About = () => {
usePageMeta( usePageMeta(
"Über uns", "Über uns",
"WEBklar hilft KMU beim digitalen Wachstum: Systeme statt Chaos, ein Partner für Website, Prozesse und Automatisierung." "WEBklar ist ein kleines Team aus Winnweiler in der Pfalz. Wir bauen Websites, Online-Shops und Web-Apps für kleine Betriebe.",
); );
return ( return (
@@ -33,52 +38,30 @@ const About = () => {
<div className="wk-container"> <div className="wk-container">
<div className="wk-seite__kopf"> <div className="wk-seite__kopf">
<span className="wk-eyebrow">Über WEBklar</span> <span className="wk-eyebrow">Über WEBklar</span>
<h1 className="wk-seite__titel">Digitalisierung, die im Alltag spürbar wird.</h1> <h1 className="wk-seite__titel">Wer hinter WEBklar steckt.</h1>
</div> </div>
<div className="wk-ueber__text"> <div className="wk-ueber__text">
<p> <p>
WEBklar ist die Webagentur für Unternehmen, die wachsen wollen – ohne Wir sind ein kleines Team aus Winnweiler in der Pfalz. Sie sprechen bei uns direkt mit den Leuten, die Ihre
dass Geschäftsführung und Team unter Tool-Chaos und manuellen Abläufen leiden. Website bauen.
</p> </p>
<p> <p>
Wir verbinden <strong>Website, Prozesse und Automatisierung</strong> zu einem System. Kein Wir bauen <strong>Websites, Online-Shops, Web-Apps und Automatisierungen</strong> für kleine Betriebe. Was
isoliertes Redesign, kein weiteres Plugin – sondern eine Lösung, die Anfragen, Daten und Abläufe wir gerade tun, sehen Sie jederzeit in Ihrem Kundenportal.
zusammenführt.
</p> </p>
</div> </div>
<div className="wk-ueber__aussage">
<p>Unser Anspruch: weniger Reibung.</p>
<p>Mehr planbares Wachstum.</p>
</div>
<p className="wk-ueber__schluss">
Starten Sie mit einem kostenlosen Gespräch – wir zeigen Ihnen die nächsten Schritte.
</p>
<div className="wk-seite__aktionen">
<Link to="/kontakt" className="wk-btn wk-btn--primary">
Projekt besprechen
</Link>
<Link to="/" className="wk-btn wk-btn--secondary">
Zur Startseite
</Link>
</div>
</div> </div>
</div> </div>
{/* Der Unterschied */} <section className="wk-section wk-section--alt" aria-labelledby="zusammenarbeit-titel">
<section className="wk-section wk-section--alt" aria-labelledby="unterschied-titel">
<div className="wk-container"> <div className="wk-container">
<span className="wk-eyebrow">Der Unterschied</span> <h2 id="zusammenarbeit-titel" className="wk-h2">
<h2 id="unterschied-titel" className="wk-h2"> So arbeiten wir zusammen.
Warum Unternehmen zu uns wechseln.
</h2> </h2>
<ol className="wk-unterschied" role="list"> <ol className="wk-unterschied" role="list">
{UNTERSCHIED.map((punkt) => ( {SO_ARBEITEN_WIR_ZUSAMMEN.map((punkt) => (
<li key={punkt.nr}> <li key={punkt.nr}>
<p className="wk-unterschied__nr" aria-hidden> <p className="wk-unterschied__nr" aria-hidden>
{punkt.nr} {punkt.nr}
+1 -26
View File
@@ -400,7 +400,7 @@
} }
} }
/* ── Über uns (Phase 2 schreibt die Seite neu) ─────────────────────────── */ /* ── Über uns ─────────────────────────────────────────────────────────── */
.wk-ueber__text { .wk-ueber__text {
max-width: var(--measure-text); max-width: var(--measure-text);
@@ -416,31 +416,6 @@
color: var(--fg); color: var(--fg);
} }
.wk-ueber__aussage {
margin-top: clamp(40px, 6vw, 72px);
max-width: 24ch;
font-size: clamp(1.75rem, 1.35rem + 1.6vw, 2.75rem);
line-height: 1.12;
font-weight: 600;
letter-spacing: -0.015em;
text-wrap: balance;
}
.wk-ueber__aussage p:first-child {
color: var(--fg);
}
.wk-ueber__aussage p:last-child {
color: var(--fg-muted);
}
.wk-ueber__schluss {
max-width: var(--measure-text);
margin-top: clamp(32px, 5vw, 56px);
font-size: var(--fs-lead);
line-height: 1.38;
font-weight: 600;
text-wrap: pretty;
}
/* Nummerierte Textzeilen statt Karten */ /* Nummerierte Textzeilen statt Karten */
.wk-unterschied { .wk-unterschied {
margin-top: clamp(32px, 5vw, 56px); margin-top: clamp(32px, 5vw, 56px);
+3 -3
View File
@@ -130,7 +130,7 @@ describe("Kundenportal: Bilder (Spezifikation 9.5)", () => {
expect(bild.getAttribute("height")).toBe("1688"); expect(bild.getAttribute("height")).toBe("1688");
} }
expect(bilder[5].getAttribute("src")).toContain("phone-2-projekte-lang"); expect(bilder[5].getAttribute("src")).toContain("phone-2-projekte-lang");
expect(bilder[5].getAttribute("height")).toBe("5428"); expect(bilder[5].getAttribute("height")).toBe("5219");
}); });
it("Desktop: zu Beginn hängen nur die ersten beiden Schritte ihre Bilder ein (spart Daten)", () => { it("Desktop: zu Beginn hängen nur die ersten beiden Schritte ihre Bilder ein (spart Daten)", () => {
@@ -180,7 +180,7 @@ describe("Kundenportal: Bilder (Spezifikation 9.5)", () => {
describe("Kundenportal: Schrittzeile", () => { describe("Kundenportal: Schrittzeile", () => {
it("Desktop: eine nummerierte Liste mit vier Schritten", () => { it("Desktop: eine nummerierte Liste mit vier Schritten", () => {
zeigeDesktop(); zeigeDesktop();
const liste = screen.getByRole("list", { name: /So arbeiten wir/ }); const liste = screen.getByRole("list", { name: /Ablauf in vier Schritten/ });
const punkte = within(liste).getAllByRole("listitem"); const punkte = within(liste).getAllByRole("listitem");
expect(punkte).toHaveLength(4); expect(punkte).toHaveLength(4);
punkte.forEach((punkt, i) => { punkte.forEach((punkt, i) => {
@@ -190,7 +190,7 @@ describe("Kundenportal: Schrittzeile", () => {
it("Handy: vier kleine Karten in einer Wisch-Reihe", () => { it("Handy: vier kleine Karten in einer Wisch-Reihe", () => {
zeigeHandy(); zeigeHandy();
const reihe = screen.getByRole("list", { name: /So arbeiten wir/ }); const reihe = screen.getByRole("list", { name: /Ablauf in vier Schritten/ });
const karten = within(reihe).getAllByRole("listitem"); const karten = within(reihe).getAllByRole("listitem");
expect(karten).toHaveLength(4); expect(karten).toHaveLength(4);
karten.forEach((karte, i) => { karten.forEach((karte, i) => {
+1 -1
View File
@@ -4,7 +4,7 @@ import { MemoryRouter } from "react-router-dom";
import { readFileSync } from "node:fs"; import { readFileSync } from "node:fs";
import { join } from "node:path"; import { join } from "node:path";
import { AppRoutes } from "@/routes"; import { AppRoutes } from "@/routes";
import { NAVIGATION } from "@/components/site/SiteHeader"; import { NAVIGATION } from "@/components/site/navigation";
import { projekte } from "@/data/projekte"; import { projekte } from "@/data/projekte";
// Netzwerk gibt es in jsdom nicht: das Kontaktformular-Backend wird durch eine Attrappe ersetzt. // Netzwerk gibt es in jsdom nicht: das Kontaktformular-Backend wird durch eine Attrappe ersetzt.
+2 -2
View File
@@ -74,7 +74,7 @@ describe("Leistungen (Spezifikation 7.3)", () => {
expect(h2).toHaveTextContent("Was wir für Sie bauen."); expect(h2).toHaveTextContent("Was wir für Sie bauen.");
expect(abschnitt).toHaveAttribute("aria-labelledby", h2.id); expect(abschnitt).toHaveAttribute("aria-labelledby", h2.id);
// die Kopfleiste verlinkt genau diesen Anker // die Kopfleiste verlinkt genau diesen Anker
expect(lies("src/components/site/SiteHeader.tsx")).toContain("/#leistungen"); expect(lies("src/components/site/navigation.ts")).toContain("/#leistungen");
}); });
it("zeigt die vier Einträge in der richtigen Reihenfolge, auf dem Handy als Wisch-Reihe", () => { it("zeigt die vier Einträge in der richtigen Reihenfolge, auf dem Handy als Wisch-Reihe", () => {
@@ -163,7 +163,7 @@ describe("Preise (Spezifikation 7.5)", () => {
const h2 = within(abschnitt).getByRole("heading", { level: 2 }); const h2 = within(abschnitt).getByRole("heading", { level: 2 });
expect(h2).toHaveTextContent("Was es kostet. Vorher."); expect(h2).toHaveTextContent("Was es kostet. Vorher.");
expect(abschnitt).toHaveAttribute("aria-labelledby", h2.id); expect(abschnitt).toHaveAttribute("aria-labelledby", h2.id);
expect(lies("src/components/site/SiteHeader.tsx")).toContain("/#preise"); expect(lies("src/components/site/navigation.ts")).toContain("/#preise");
expect(norm(abschnitt.querySelector(".wk-lead")?.textContent)).toBe( expect(norm(abschnitt.querySelector(".wk-lead")?.textContent)).toBe(
"Für Ihr Projekt bekommen Sie vorher ein Angebot mit Preis. Für den Betrieb danach gibt es zwei Abos. " + "Für Ihr Projekt bekommen Sie vorher ein Angebot mit Preis. Für den Betrieb danach gibt es zwei Abos. " +
"Alle Preise sind Endpreise; als Kleinunternehmer (§ 19 UStG) berechnen wir keine Umsatzsteuer.", "Alle Preise sind Endpreise; als Kleinunternehmer (§ 19 UStG) berechnen wir keine Umsatzsteuer.",
+7 -7
View File
@@ -281,13 +281,13 @@ describe("B5 Erfolgsversprechen entschärft", () => {
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
it("Über uns: Karte »Betreuung im Abo« statt »Langfristige Partnerschaft«", () => { it("Über uns: keine Erfolgsversprechen mehr, nur belegte Aussagen (Relaunch, Spezifikation 6.4)", () => {
zeige(<About />); const { container } = zeige(<About />);
expect(screen.getByRole("heading", { level: 3, name: "Betreuung im Abo" })).toBeInTheDocument(); expect(screen.getByRole("heading", { level: 1, name: "Wer hinter WEBklar steckt." })).toBeInTheDocument();
expect( expect(screen.getByRole("heading", { level: 3, name: "Server in Deutschland" })).toBeInTheDocument();
screen.getByText("Nach dem Go-live kümmern wir uns im Wartungs-Abo um Updates und kleine Änderungen."), expect(container).not.toHaveTextContent(
).toBeInTheDocument(); /Langfristige Partnerschaft|planbares Wachstum|Warum Unternehmen zu uns wechseln|CRM|Tool-Chaos/,
expect(screen.queryByText("Langfristige Partnerschaft")).toBeNull(); );
}); });
it("Über uns: ohne Glüh-Rahmen und hochzählende Zahlen, ohne »Potenzialanalyse« (Spezifikation 8.5)", () => { it("Über uns: ohne Glüh-Rahmen und hochzählende Zahlen, ohne »Potenzialanalyse« (Spezifikation 8.5)", () => {