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
@@ -32,7 +32,7 @@ const desktopBild = (name: string, alt: string): RahmenBildDaten => ({
|
||||
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 => ({
|
||||
src: `${BILDER}/${name}-780.webp`,
|
||||
srcSet: srcSet(name, HANDY_BREITEN),
|
||||
@@ -291,7 +291,7 @@ const Buehne = () => {
|
||||
>
|
||||
{f.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>
|
||||
) : (
|
||||
<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"}>
|
||||
<HandyRahmen className="kp-karte__handy">
|
||||
<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>
|
||||
</HandyRahmen>
|
||||
<h3 className="wk-h3 kp-karte__titel">{f.titel}</h3>
|
||||
@@ -358,7 +358,7 @@ const HandyReihe = () => (
|
||||
const Schrittzeile = ({ desktop }: { desktop: boolean }) =>
|
||||
desktop ? (
|
||||
<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) => (
|
||||
<li key={s.nr} className="kp-schritt">
|
||||
<span className="kp-schritt__nr wk-num">{s.nr}</span>
|
||||
@@ -369,7 +369,7 @@ const Schrittzeile = ({ desktop }: { desktop: boolean }) =>
|
||||
</div>
|
||||
) : (
|
||||
<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) => (
|
||||
<div key={s.nr} className="kp-schritt-karte">
|
||||
<span className="kp-schritt__nr wk-num">{s.nr}</span>
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
align-items: start;
|
||||
}
|
||||
.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) {
|
||||
.wk-ueber-uns {
|
||||
|
||||
@@ -210,8 +210,8 @@
|
||||
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
|
||||
((5428 - 1688) / 5428). Die Verschiebung hängt am Scrollfortschritt (--kp-q), kein eigener Scrollbereich. */
|
||||
/* Der lange Screenshot: gleiche Breite wie der Schirm, 67,66 % seiner Höhe wandern aus dem Bild
|
||||
((5219 - 1688) / 5219). Die Verschiebung hängt am Scrollfortschritt (--kp-q), kein eigener Scrollbereich. */
|
||||
.kp-lang {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -233,7 +233,7 @@
|
||||
transition: transform var(--dur-enter) var(--ease);
|
||||
}
|
||||
.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 */
|
||||
.kp-buehne[data-app="ja"] .kp-lang {
|
||||
@@ -276,7 +276,7 @@
|
||||
transform: translateY(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-68.9%);
|
||||
transform: translateY(-67.66%);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,9 @@
|
||||
.wk-leistungen__kopf {
|
||||
grid-column: 1;
|
||||
grid-row: 1 / span 2;
|
||||
position: sticky;
|
||||
top: calc(var(--header-h) + 3rem);
|
||||
align-self: start;
|
||||
}
|
||||
.wk-leistungen__liste {
|
||||
grid-column: 2;
|
||||
|
||||
@@ -3,18 +3,9 @@ import { Link, useLocation } from "react-router-dom";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import Logo from "@/components/Logo";
|
||||
import { projekte } from "@/data/projekte";
|
||||
import { NAVIGATION } from "./navigation";
|
||||
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. */
|
||||
const ThemaSchalter = ({ mitText = false }: { mitText?: boolean }) => {
|
||||
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;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
background: color-mix(in srgb, var(--bg) 94%, transparent);
|
||||
background: var(--bg);
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: border-color var(--dur-state) var(--ease);
|
||||
}
|
||||
@@ -233,7 +233,8 @@
|
||||
color: var(--fg);
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
min-height: 44px;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.wk-footer__marke svg {
|
||||
border-radius: 6px;
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
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 =
|
||||
"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(
|
||||
attribute: "name" | "property",
|
||||
@@ -33,6 +47,7 @@ export function usePageMeta(title: string, description?: string): void {
|
||||
setMetaTag("property", "og:description", desc);
|
||||
setMetaTag("name", "twitter:title", fullTitle);
|
||||
setMetaTag("name", "twitter:description", desc);
|
||||
setCanonical(window.location.pathname);
|
||||
|
||||
return () => {
|
||||
document.title = DEFAULT_TITLE;
|
||||
|
||||
+23
-40
@@ -2,29 +2,34 @@ import { Link } from "react-router-dom";
|
||||
import { usePageMeta } from "@/hooks/use-page-meta";
|
||||
import "./unterseiten.css";
|
||||
|
||||
/** „Der Unterschied“: nummerierte Textzeilen statt Karten (Phase 2 schreibt die ganze Seite neu). */
|
||||
const UNTERSCHIED = [
|
||||
/** Was man bei uns sicher bekommt (Spezifikation 6.4, nur belegte Aussagen; Team mit Namen und Fotos erst nach O4). */
|
||||
const SO_ARBEITEN_WIR_ZUSAMMEN = [
|
||||
{
|
||||
nr: "01",
|
||||
titel: "Alles aus einer Hand",
|
||||
text: "Website, Automation und Schnittstellen aus einer Hand – ein Ansprechpartner statt fünf Agenturen.",
|
||||
titel: "Direkt mit dem Team",
|
||||
text: "Ihre Fragen gehen an die Leute, die an Ihrem Projekt arbeiten – im Chat des Kundenportals, per E-Mail oder am Telefon.",
|
||||
},
|
||||
{
|
||||
nr: "02",
|
||||
titel: "Systeme statt Inseln",
|
||||
text: "CRM, Formulare und Website tauschen Daten – ohne manuelles Nachpflegen.",
|
||||
titel: "Alles im Kundenportal",
|
||||
text: "Fortschritt, Live-Vorschau Ihrer Website, Chat und Rechnungen finden Sie an einem Ort unter project.webklar.com.",
|
||||
},
|
||||
{
|
||||
nr: "03",
|
||||
titel: "Betreuung im Abo",
|
||||
text: "Nach dem Go-live kümmern wir uns im Wartungs-Abo um Updates und kleine Änderungen.",
|
||||
titel: "Preis vorher",
|
||||
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 = () => {
|
||||
usePageMeta(
|
||||
"Ü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 (
|
||||
@@ -33,52 +38,30 @@ const About = () => {
|
||||
<div className="wk-container">
|
||||
<div className="wk-seite__kopf">
|
||||
<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 className="wk-ueber__text">
|
||||
<p>
|
||||
WEBklar ist die Webagentur für Unternehmen, die wachsen wollen – ohne
|
||||
dass Geschäftsführung und Team unter Tool-Chaos und manuellen Abläufen leiden.
|
||||
Wir sind ein kleines Team aus Winnweiler in der Pfalz. Sie sprechen bei uns direkt mit den Leuten, die Ihre
|
||||
Website bauen.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Wir verbinden <strong>Website, Prozesse und Automatisierung</strong> zu einem System. Kein
|
||||
isoliertes Redesign, kein weiteres Plugin – sondern eine Lösung, die Anfragen, Daten und Abläufe
|
||||
zusammenführt.
|
||||
Wir bauen <strong>Websites, Online-Shops, Web-Apps und Automatisierungen</strong> für kleine Betriebe. Was
|
||||
wir gerade tun, sehen Sie jederzeit in Ihrem Kundenportal.
|
||||
</p>
|
||||
</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>
|
||||
|
||||
{/* Der Unterschied */}
|
||||
<section className="wk-section wk-section--alt" aria-labelledby="unterschied-titel">
|
||||
<section className="wk-section wk-section--alt" aria-labelledby="zusammenarbeit-titel">
|
||||
<div className="wk-container">
|
||||
<span className="wk-eyebrow">Der Unterschied</span>
|
||||
<h2 id="unterschied-titel" className="wk-h2">
|
||||
Warum Unternehmen zu uns wechseln.
|
||||
<h2 id="zusammenarbeit-titel" className="wk-h2">
|
||||
So arbeiten wir zusammen.
|
||||
</h2>
|
||||
|
||||
<ol className="wk-unterschied" role="list">
|
||||
{UNTERSCHIED.map((punkt) => (
|
||||
{SO_ARBEITEN_WIR_ZUSAMMEN.map((punkt) => (
|
||||
<li key={punkt.nr}>
|
||||
<p className="wk-unterschied__nr" aria-hidden>
|
||||
{punkt.nr}
|
||||
|
||||
@@ -400,7 +400,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Über uns (Phase 2 schreibt die Seite neu) ─────────────────────────── */
|
||||
/* ── Über uns ─────────────────────────────────────────────────────────── */
|
||||
|
||||
.wk-ueber__text {
|
||||
max-width: var(--measure-text);
|
||||
@@ -416,31 +416,6 @@
|
||||
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 */
|
||||
.wk-unterschied {
|
||||
margin-top: clamp(32px, 5vw, 56px);
|
||||
|
||||
@@ -130,7 +130,7 @@ describe("Kundenportal: Bilder (Spezifikation 9.5)", () => {
|
||||
expect(bild.getAttribute("height")).toBe("1688");
|
||||
}
|
||||
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)", () => {
|
||||
@@ -180,7 +180,7 @@ describe("Kundenportal: Bilder (Spezifikation 9.5)", () => {
|
||||
describe("Kundenportal: Schrittzeile", () => {
|
||||
it("Desktop: eine nummerierte Liste mit vier Schritten", () => {
|
||||
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");
|
||||
expect(punkte).toHaveLength(4);
|
||||
punkte.forEach((punkt, i) => {
|
||||
@@ -190,7 +190,7 @@ describe("Kundenportal: Schrittzeile", () => {
|
||||
|
||||
it("Handy: vier kleine Karten in einer Wisch-Reihe", () => {
|
||||
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");
|
||||
expect(karten).toHaveLength(4);
|
||||
karten.forEach((karte, i) => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { MemoryRouter } from "react-router-dom";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { AppRoutes } from "@/routes";
|
||||
import { NAVIGATION } from "@/components/site/SiteHeader";
|
||||
import { NAVIGATION } from "@/components/site/navigation";
|
||||
import { projekte } from "@/data/projekte";
|
||||
|
||||
// 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(abschnitt).toHaveAttribute("aria-labelledby", h2.id);
|
||||
// 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", () => {
|
||||
@@ -163,7 +163,7 @@ describe("Preise (Spezifikation 7.5)", () => {
|
||||
const h2 = within(abschnitt).getByRole("heading", { level: 2 });
|
||||
expect(h2).toHaveTextContent("Was es kostet. Vorher.");
|
||||
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(
|
||||
"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.",
|
||||
|
||||
@@ -281,13 +281,13 @@ describe("B5 Erfolgsversprechen entschärft", () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("Über uns: Karte »Betreuung im Abo« statt »Langfristige Partnerschaft«", () => {
|
||||
zeige(<About />);
|
||||
expect(screen.getByRole("heading", { level: 3, name: "Betreuung im Abo" })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("Nach dem Go-live kümmern wir uns im Wartungs-Abo um Updates und kleine Änderungen."),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText("Langfristige Partnerschaft")).toBeNull();
|
||||
it("Über uns: keine Erfolgsversprechen mehr, nur belegte Aussagen (Relaunch, Spezifikation 6.4)", () => {
|
||||
const { container } = zeige(<About />);
|
||||
expect(screen.getByRole("heading", { level: 1, name: "Wer hinter WEBklar steckt." })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { level: 3, name: "Server in Deutschland" })).toBeInTheDocument();
|
||||
expect(container).not.toHaveTextContent(
|
||||
/Langfristige Partnerschaft|planbares Wachstum|Warum Unternehmen zu uns wechseln|CRM|Tool-Chaos/,
|
||||
);
|
||||
});
|
||||
|
||||
it("Über uns: ohne Glüh-Rahmen und hochzählende Zahlen, ohne »Potenzialanalyse« (Spezifikation 8.5)", () => {
|
||||
|
||||
Reference in new issue
Block a user