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:
1 parent
1c8cfa5c1c
commit
6e8ba2cddb
18 files changed
+98
-110
No files matched your search
+6
-6
@@ -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 & 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 |
@@ -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]
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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" },
|
||||||
|
];
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.",
|
||||||
|
|||||||
@@ -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)", () => {
|
||||||
|
|||||||
Reference in new issue
Block a user