Barrierefreiheit (BFSG/WCAG 2.1 AA): Skip-Link, aria-live, Labels, Fokus-Sichtbarkeit, Überschriften-Hierarchie
Technisches Accessibility-Audit ergab sechs behebbare Punkte, alle jetzt gefixt: - Skip-to-Content-Link ganz oben auf jeder Seite (WCAG 2.4.1) - Fehler-/Statusmeldungen (Checkout, Login, Rezension, Gate-/Verwaltungs-Code) jetzt mit role="alert"/aria-live, damit Screenreader sie automatisch ansagen (WCAG 4.1.3) - Warenkorb-Icon hatte fälschlich das "Konto"-Label geerbt, sagt jetzt korrekt "Warenkorb" (WCAG 4.1.2/2.4.4), dafür t.nav.cart in allen 4 Sprachen neu ergänzt - Sichtbarer Fokus-Ring beim Mengenfeld auf der Produktseite wiederhergestellt (WCAG 2.4.7) - Placeholder-only Formularfelder (Gate-Zugangscode, Verwaltungs-Suche/Filter/Tracking) haben jetzt echte Labels (sichtbar per sr-only-Klasse oder aria-label) statt nur Platzhaltertext (WCAG 1.3.1/3.3.2) - Übersprungene Überschriften-Ebenen (h1→h3 ohne h2) in Checkout, Warenkorb, Kontakt, Konto korrigiert (WCAG 1.3.1) Reine Accessibility-Nachrüstung, keine funktionale Änderung. Kleinstunternehmer-Ausnahme (§3 Abs.3 BFSG) greift hier vermutlich, aber vorsorglich sauber umgesetzt. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b70bad3e41
commit
b944bcf724
+13
-1
@@ -21,6 +21,17 @@
|
|||||||
--ease: cubic-bezier(0.22, 1, 0.36, 1);
|
--ease: cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
}
|
}
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
|
/* Für Screenreader vorhanden, visuell aber unsichtbar — ersetzt bei den Formularfeldern dieser
|
||||||
|
Seite den (allein nicht ausreichenden) Placeholder-Text durch ein echtes <label>. */
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px; height: 1px;
|
||||||
|
padding: 0; margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
html, body { height: 100%; }
|
html, body { height: 100%; }
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -179,10 +190,11 @@
|
|||||||
<h1>Van's DIY & Bastelbedarf</h1>
|
<h1>Van's DIY & Bastelbedarf</h1>
|
||||||
<p>Diese Seite ist gerade im Aufbau und noch nicht für die Öffentlichkeit freigegeben.<br />Zugangscode eingeben, um weiterzukommen.</p>
|
<p>Diese Seite ist gerade im Aufbau und noch nicht für die Öffentlichkeit freigegeben.<br />Zugangscode eingeben, um weiterzukommen.</p>
|
||||||
<form id="gate-form">
|
<form id="gate-form">
|
||||||
|
<label class="sr-only" for="gate-code">Zugangscode</label>
|
||||||
<input type="text" id="gate-code" placeholder="Zugangscode" autocomplete="off" autocapitalize="off" spellcheck="false" autofocus />
|
<input type="text" id="gate-code" placeholder="Zugangscode" autocomplete="off" autocapitalize="off" spellcheck="false" autofocus />
|
||||||
<button type="submit">Weiter</button>
|
<button type="submit">Weiter</button>
|
||||||
</form>
|
</form>
|
||||||
<p class="gate-fehler" id="gate-fehler"></p>
|
<p class="gate-fehler" id="gate-fehler" role="alert" aria-live="assertive"></p>
|
||||||
<p class="gate-logo">🧶 Handgemacht mit Liebe</p>
|
<p class="gate-logo">🧶 Handgemacht mit Liebe</p>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -21,6 +21,17 @@
|
|||||||
--ease: cubic-bezier(0.22, 1, 0.36, 1);
|
--ease: cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
}
|
}
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
|
/* Für Screenreader vorhanden, visuell aber unsichtbar — ersetzt bei den Formularfeldern dieser
|
||||||
|
Seite den (allein nicht ausreichenden) Placeholder-Text durch ein echtes <label>. */
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px; height: 1px;
|
||||||
|
padding: 0; margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
html, body { height: 100%; }
|
html, body { height: 100%; }
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -133,10 +144,11 @@
|
|||||||
<p>Diese Seite ist zusätzlich mit einem eigenen, festen Code geschützt, den nur ihr beide
|
<p>Diese Seite ist zusätzlich mit einem eigenen, festen Code geschützt, den nur ihr beide
|
||||||
kennt. Der Code bleibt immer gleich — ihr müsst ihn aber einmal pro Tag neu eingeben.</p>
|
kennt. Der Code bleibt immer gleich — ihr müsst ihn aber einmal pro Tag neu eingeben.</p>
|
||||||
<form id="vw-form">
|
<form id="vw-form">
|
||||||
|
<label class="sr-only" for="vw-code">Verwaltungs-Code</label>
|
||||||
<input type="text" id="vw-code" placeholder="Verwaltungs-Code" autocomplete="off" autocapitalize="off" spellcheck="false" autofocus />
|
<input type="text" id="vw-code" placeholder="Verwaltungs-Code" autocomplete="off" autocapitalize="off" spellcheck="false" autofocus />
|
||||||
<button type="submit">Weiter</button>
|
<button type="submit">Weiter</button>
|
||||||
</form>
|
</form>
|
||||||
<p class="gate-fehler" id="vw-fehler"></p>
|
<p class="gate-fehler" id="vw-fehler" role="alert" aria-live="assertive"></p>
|
||||||
<p class="gate-zurueck"><a href="/">← Zurück zur Startseite</a></p>
|
<p class="gate-zurueck"><a href="/">← Zurück zur Startseite</a></p>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
+2
-1
@@ -7,7 +7,7 @@ import type { Locale } from "./config";
|
|||||||
|
|
||||||
export const ui = {
|
export const ui = {
|
||||||
de: {
|
de: {
|
||||||
nav: { shop: "Shop", sale: "Sale", creative: "Über die Kreative", faq: "FAQ", contact: "Kontakt", account: "Mein Konto", login: "Anmelden" },
|
nav: { shop: "Shop", sale: "Sale", creative: "Über die Kreative", faq: "FAQ", contact: "Kontakt", account: "Mein Konto", login: "Anmelden", cart: "Warenkorb" },
|
||||||
accountMenu: { myAccount: "Mein Konto", logout: "Abmelden" },
|
accountMenu: { myAccount: "Mein Konto", logout: "Abmelden" },
|
||||||
topbar: "Versandkostenfrei ab 75 € (DE) / 85 € (AT, LU) / 100 € (CH)",
|
topbar: "Versandkostenfrei ab 75 € (DE) / 85 € (AT, LU) / 100 € (CH)",
|
||||||
footer: {
|
footer: {
|
||||||
@@ -30,6 +30,7 @@ export const ui = {
|
|||||||
quantity: "Anzahl", photoSoon: "Produktfoto folgt", portraitSoon: "Porträtfoto von VanVan folgt",
|
quantity: "Anzahl", photoSoon: "Produktfoto folgt", portraitSoon: "Porträtfoto von VanVan folgt",
|
||||||
prevPhoto: "Vorheriges Foto", nextPhoto: "Nächstes Foto",
|
prevPhoto: "Vorheriges Foto", nextPhoto: "Nächstes Foto",
|
||||||
handmadeBadge: "🩵 Handgemacht",
|
handmadeBadge: "🩵 Handgemacht",
|
||||||
|
skipToContent: "Zum Inhalt springen",
|
||||||
},
|
},
|
||||||
product: {
|
product: {
|
||||||
articleNo: "Artikelnummer", material: "Material", size: "Größe", colors: "Farben",
|
articleNo: "Artikelnummer", material: "Material", size: "Größe", colors: "Farben",
|
||||||
|
|||||||
@@ -66,6 +66,13 @@ const p = (href: string) => `${localePrefix(lang)}${href}`;
|
|||||||
<ClientRouter fallback="swap" />
|
<ClientRouter fallback="swap" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
{/* Skip-Link (WCAG 2.1 SC 2.4.1 "Bypass Blocks"): für Maus-/Touch-Nutzer:innen unsichtbar,
|
||||||
|
erscheint aber als allererstes fokussierbares Element sichtbar oben links, sobald man mit
|
||||||
|
Tab durch die Seite geht — erspart Tastatur-/Screenreader-Nutzer:innen das Durchtabben durch
|
||||||
|
Topbar, komplette Kopfnavigation, Sprachumschalter, Konto-Menü und zweite Navigationsleiste,
|
||||||
|
bevor der eigentliche Seiteninhalt (<main id="main-content">) erreichbar ist. */}
|
||||||
|
<a href="#main-content" class="skip-link">{t.common.skipToContent}</a>
|
||||||
|
|
||||||
<div class="topbar">🩵 {t.topbar}</div>
|
<div class="topbar">🩵 {t.topbar}</div>
|
||||||
|
|
||||||
<header class="site-header">
|
<header class="site-header">
|
||||||
@@ -115,7 +122,7 @@ const p = (href: string) => `${localePrefix(lang)}${href}`;
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href={p("/warenkorb/")} class="icon-link cart-link" aria-label={t.nav.account}>
|
<a href={p("/warenkorb/")} class="icon-link cart-link" aria-label={t.nav.cart}>
|
||||||
🧺 <span class="cart-count" id="cart-count">0</span>
|
🧺 <span class="cart-count" id="cart-count">0</span>
|
||||||
</a>
|
</a>
|
||||||
<button class="hamburger" id="hamburger" aria-label="Menü" aria-expanded="false">☰</button>
|
<button class="hamburger" id="hamburger" aria-label="Menü" aria-expanded="false">☰</button>
|
||||||
@@ -141,7 +148,7 @@ const p = (href: string) => `${localePrefix(lang)}${href}`;
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<main>
|
<main id="main-content" tabindex="-1">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@@ -595,6 +602,27 @@ const p = (href: string) => `${localePrefix(lang)}${href}`;
|
|||||||
body { font-family: var(--font-body); }
|
body { font-family: var(--font-body); }
|
||||||
h1, h2, h3, h4 { font-family: var(--font-head-override, var(--font-head)); }
|
h1, h2, h3, h4 { font-family: var(--font-head-override, var(--font-head)); }
|
||||||
|
|
||||||
|
/* Skip-Link: liegt normalerweise außerhalb des sichtbaren Bereichs (kein Layout-Sprung für
|
||||||
|
Maus-Nutzer:innen), springt aber beim Fokussieren (Tab-Taste) sofort gut sichtbar oben links
|
||||||
|
ins Bild — Standardmuster für WCAG 2.4.1. */
|
||||||
|
.skip-link {
|
||||||
|
position: absolute;
|
||||||
|
top: -3rem;
|
||||||
|
left: 0.6rem;
|
||||||
|
z-index: 100;
|
||||||
|
background: var(--c-accent);
|
||||||
|
color: #08181c;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0.7rem 1.1rem;
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
transition: top 0.15s var(--ease);
|
||||||
|
}
|
||||||
|
.skip-link:focus {
|
||||||
|
top: 0;
|
||||||
|
outline: 2px solid var(--c-text);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.topbar {
|
.topbar {
|
||||||
background: linear-gradient(90deg, var(--c-purple), var(--c-purple-deep));
|
background: linear-gradient(90deg, var(--c-purple), var(--c-purple-deep));
|
||||||
color: var(--c-text);
|
color: var(--c-text);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const t = useTranslations(lang);
|
|||||||
<form class="frm card" id="checkout-form">
|
<form class="frm card" id="checkout-form">
|
||||||
<div class="checkout-split">
|
<div class="checkout-split">
|
||||||
<div class="checkout-split-col">
|
<div class="checkout-split-col">
|
||||||
<h3>{t.checkout.step2}</h3>
|
<h2>{t.checkout.step2}</h2>
|
||||||
{/* Nur noch zwei Zahlungsarten (auf ausdrücklichen Wunsch, siehe Recherche zu Rechts-/
|
{/* Nur noch zwei Zahlungsarten (auf ausdrücklichen Wunsch, siehe Recherche zu Rechts-/
|
||||||
Kostenlage): Klarna raus (würde eine eigene Händlerprüfung + Bonitätsprüfungs-
|
Kostenlage): Klarna raus (würde eine eigene Händlerprüfung + Bonitätsprüfungs-
|
||||||
Pflichten voraussetzen, siehe EU-Verbraucherkreditrichtlinie) und keine eigene
|
Pflichten voraussetzen, siehe EU-Verbraucherkreditrichtlinie) und keine eigene
|
||||||
@@ -58,7 +58,7 @@ const t = useTranslations(lang);
|
|||||||
<div class="pay-panel-inline">
|
<div class="pay-panel-inline">
|
||||||
<p>{t.checkout.payPalHint}</p>
|
<p>{t.checkout.payPalHint}</p>
|
||||||
<div id="paypal-button-container"></div>
|
<div id="paypal-button-container"></div>
|
||||||
<p class="pay-connect-status" id="paypal-error" style="display:none; color: var(--c-sale);"></p>
|
<p class="pay-connect-status" id="paypal-error" role="alert" aria-live="assertive" style="display:none; color: var(--c-sale);"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pay-card" style="--brand-color:#7fa8c9; --brand-bg:#3b5a76;">
|
<div class="pay-card" style="--brand-color:#7fa8c9; --brand-bg:#3b5a76;">
|
||||||
@@ -79,7 +79,7 @@ const t = useTranslations(lang);
|
|||||||
</div>
|
</div>
|
||||||
<div class="checkout-split-divider" aria-hidden="true"><span>🧵</span></div>
|
<div class="checkout-split-divider" aria-hidden="true"><span>🧵</span></div>
|
||||||
<div class="checkout-split-col">
|
<div class="checkout-split-col">
|
||||||
<h3>{t.checkout.step1}</h3>
|
<h2>{t.checkout.step1}</h2>
|
||||||
<div><label for="email">{t.checkout.email}</label><input id="email" type="email" required /></div>
|
<div><label for="email">{t.checkout.email}</label><input id="email" type="email" required /></div>
|
||||||
<div class="grid grid-2">
|
<div class="grid grid-2">
|
||||||
<div><label for="vorname">{t.checkout.firstName}</label><input id="vorname" type="text" required /></div>
|
<div><label for="vorname">{t.checkout.firstName}</label><input id="vorname" type="text" required /></div>
|
||||||
@@ -101,7 +101,7 @@ const t = useTranslations(lang);
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="divider" />
|
<hr class="divider" />
|
||||||
<h3>{t.checkout.step3}</h3>
|
<h2>{t.checkout.step3}</h2>
|
||||||
<div id="checkout-summary" class="checkout-summary"></div>
|
<div id="checkout-summary" class="checkout-summary"></div>
|
||||||
<p class="small">{t.common.vatNote}</p>
|
<p class="small">{t.common.vatNote}</p>
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ const t = useTranslations(lang);
|
|||||||
<input type="text" id="coupon-input" placeholder={t.cart.couponPlaceholder} />
|
<input type="text" id="coupon-input" placeholder={t.cart.couponPlaceholder} />
|
||||||
<button type="button" id="coupon-apply" class="btn btn-outline btn-sm">{t.cart.couponApply}</button>
|
<button type="button" id="coupon-apply" class="btn btn-outline btn-sm">{t.cart.couponApply}</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="small" id="coupon-status"></p>
|
<p class="small" id="coupon-status" role="status" aria-live="polite"></p>
|
||||||
</div>
|
</div>
|
||||||
<hr class="divider stitched" />
|
<hr class="divider stitched" />
|
||||||
<div class="coupon-row">
|
<div class="coupon-row">
|
||||||
@@ -140,7 +140,7 @@ const t = useTranslations(lang);
|
|||||||
<input type="text" id="partner-code-input" placeholder={t.cart.partnerCodePlaceholder} />
|
<input type="text" id="partner-code-input" placeholder={t.cart.partnerCodePlaceholder} />
|
||||||
<button type="button" id="partner-code-apply" class="btn btn-outline btn-sm">{t.cart.partnerCodeApply}</button>
|
<button type="button" id="partner-code-apply" class="btn btn-outline btn-sm">{t.cart.partnerCodeApply}</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="small" id="partner-code-status"></p>
|
<p class="small" id="partner-code-status" role="status" aria-live="polite"></p>
|
||||||
</div>
|
</div>
|
||||||
<hr class="divider stitched" />
|
<hr class="divider stitched" />
|
||||||
<h3>🚚 {t.checkout.shippingHintTitle}</h3>
|
<h3>🚚 {t.checkout.shippingHintTitle}</h3>
|
||||||
@@ -403,6 +403,8 @@ import { registriereAbgeschlosseneBestellung, treuebonusEinloesen } from "../../
|
|||||||
const submitBtn = document.getElementById("order-submit-btn");
|
const submitBtn = document.getElementById("order-submit-btn");
|
||||||
const orderErrorEl = document.createElement("p");
|
const orderErrorEl = document.createElement("p");
|
||||||
orderErrorEl.className = "small";
|
orderErrorEl.className = "small";
|
||||||
|
orderErrorEl.setAttribute("role", "alert");
|
||||||
|
orderErrorEl.setAttribute("aria-live", "assertive");
|
||||||
orderErrorEl.style.color = "var(--c-sale)";
|
orderErrorEl.style.color = "var(--c-sale)";
|
||||||
orderErrorEl.style.display = "none";
|
orderErrorEl.style.display = "none";
|
||||||
submitBtn.insertAdjacentElement("beforebegin", orderErrorEl);
|
submitBtn.insertAdjacentElement("beforebegin", orderErrorEl);
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ const t = useTranslations(lang);
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="card contact-highlight">
|
<div class="card contact-highlight">
|
||||||
<h3><span aria-hidden="true">💌</span> {t.contact.directTitle}</h3>
|
<h2><span aria-hidden="true">💌</span> {t.contact.directTitle}</h2>
|
||||||
<p class="small">E-Mail: <a href="mailto:[email protected]">[email protected]</a> <span class="small">{t.contact.emailPlaceholderNote}</span></p>
|
<p class="small">E-Mail: <a href="mailto:[email protected]">[email protected]</a> <span class="small">{t.contact.emailPlaceholderNote}</span></p>
|
||||||
<p class="small">{t.contact.orderNote}</p>
|
<p class="small">{t.contact.orderNote}</p>
|
||||||
<h3><span aria-hidden="true">⏱️</span> {t.contact.responseTitle}</h3>
|
<h2><span aria-hidden="true">⏱️</span> {t.contact.responseTitle}</h2>
|
||||||
<p class="small">{t.contact.responseNote}</p>
|
<p class="small">{t.contact.responseNote}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const t = useTranslations(lang);
|
|||||||
<section class="section-tight">
|
<section class="section-tight">
|
||||||
<div class="container grid grid-2 account-grid">
|
<div class="container grid grid-2 account-grid">
|
||||||
<form class="frm card" id="login-form">
|
<form class="frm card" id="login-form">
|
||||||
<h3>{t.account.loginTitle}</h3>
|
<h2>{t.account.loginTitle}</h2>
|
||||||
|
|
||||||
{/* Echtes Google-/PayPal-Login (OAuth 2.0 + PKCE, server-seitig geprüft — siehe
|
{/* Echtes Google-/PayPal-Login (OAuth 2.0 + PKCE, server-seitig geprüft — siehe
|
||||||
functions/api/auth/ + functions/_shared/oauth.js). Ganz normale Links, kein JS
|
functions/api/auth/ + functions/_shared/oauth.js). Ganz normale Links, kein JS
|
||||||
@@ -36,7 +36,7 @@ const t = useTranslations(lang);
|
|||||||
</span>
|
</span>
|
||||||
{t.account.socialPaypal}
|
{t.account.socialPaypal}
|
||||||
</a>
|
</a>
|
||||||
<p class="small social-note" id="social-note" style="display:none;"></p>
|
<p class="small social-note" id="social-note" role="alert" aria-live="assertive" style="display:none;"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="social-divider">{t.account.orDivider}</div>
|
<div class="social-divider">{t.account.orDivider}</div>
|
||||||
@@ -66,14 +66,14 @@ const t = useTranslations(lang);
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-primary btn-block" type="submit">{t.account.loginButton}</button>
|
<button class="btn btn-primary btn-block" type="submit">{t.account.loginButton}</button>
|
||||||
<p class="small" id="login-error" style="display:none; color: var(--c-sale);">{t.account.loginError}</p>
|
<p class="small" id="login-error" role="alert" aria-live="assertive" style="display:none; color: var(--c-sale);">{t.account.loginError}</p>
|
||||||
<a class="small" href="#">{t.account.forgotPassword}</a>
|
<a class="small" href="#">{t.account.forgotPassword}</a>
|
||||||
|
|
||||||
<p class="small login-consent">{t.account.consentPrefix} <a href="/datenschutz/">{t.account.consentLink}</a>{t.account.consentSuffix}</p>
|
<p class="small login-consent">{t.account.consentPrefix} <a href="/datenschutz/">{t.account.consentLink}</a>{t.account.consentSuffix}</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>{t.account.newTitle}</h3>
|
<h2>{t.account.newTitle}</h2>
|
||||||
<p class="small">{t.account.newText}</p>
|
<p class="small">{t.account.newText}</p>
|
||||||
<a class="btn btn-outline btn-block" href="/shop/">{t.account.guestButton}</a>
|
<a class="btn btn-outline btn-block" href="/shop/">{t.account.guestButton}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ const felder: [string, string | undefined][] = [
|
|||||||
<textarea id="review-text" rows="3"></textarea>
|
<textarea id="review-text" rows="3"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary" type="submit">{t.product.submitReview}</button>
|
<button class="btn btn-primary" type="submit">{t.product.submitReview}</button>
|
||||||
<p class="small" id="review-error" style="display:none; color: var(--c-sale);">{t.product.reviewNeedsRating}</p>
|
<p class="small" id="review-error" role="alert" aria-live="assertive" style="display:none; color: var(--c-sale);">{t.product.reviewNeedsRating}</p>
|
||||||
<p class="small" id="review-confirm" role="status" style="display:none; color: var(--c-accent);">{t.product.reviewSaved}</p>
|
<p class="small" id="review-confirm" role="status" style="display:none; color: var(--c-accent);">{t.product.reviewSaved}</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -248,7 +248,9 @@ const lang: Locale = "de";
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="verwaltung-toolbar">
|
<div class="verwaltung-toolbar">
|
||||||
|
<label class="sr-only" for="verwaltung-search">Suche nach Bestellnummer, Name oder E-Mail</label>
|
||||||
<input type="search" id="verwaltung-search" class="verwaltung-input" placeholder="🔍 Suche nach Bestellnummer, Name oder E-Mail …" />
|
<input type="search" id="verwaltung-search" class="verwaltung-input" placeholder="🔍 Suche nach Bestellnummer, Name oder E-Mail …" />
|
||||||
|
<label class="sr-only" for="verwaltung-zeitraum">Zeitraum filtern</label>
|
||||||
<select id="verwaltung-zeitraum" class="verwaltung-input verwaltung-select">
|
<select id="verwaltung-zeitraum" class="verwaltung-input verwaltung-select">
|
||||||
<option value="alle">Alle Zeiträume</option>
|
<option value="alle">Alle Zeiträume</option>
|
||||||
<option value="heute">Heute</option>
|
<option value="heute">Heute</option>
|
||||||
@@ -256,6 +258,7 @@ const lang: Locale = "de";
|
|||||||
<option value="monat">Dieser Monat</option>
|
<option value="monat">Dieser Monat</option>
|
||||||
<option value="30tage">Letzte 30 Tage</option>
|
<option value="30tage">Letzte 30 Tage</option>
|
||||||
</select>
|
</select>
|
||||||
|
<label class="sr-only" for="verwaltung-sort">Sortierung</label>
|
||||||
<select id="verwaltung-sort" class="verwaltung-input verwaltung-select">
|
<select id="verwaltung-sort" class="verwaltung-input verwaltung-select">
|
||||||
<option value="neu">Neueste zuerst</option>
|
<option value="neu">Neueste zuerst</option>
|
||||||
<option value="alt">Älteste zuerst</option>
|
<option value="alt">Älteste zuerst</option>
|
||||||
@@ -531,7 +534,7 @@ const lang: Locale = "de";
|
|||||||
<div class="order-card-foot verwaltung-order-foot">
|
<div class="order-card-foot verwaltung-order-foot">
|
||||||
<span class="order-total">Summe: <strong>${formatPrice(Number(b.summe), lang)}</strong></span>
|
<span class="order-total">Summe: <strong>${formatPrice(Number(b.summe), lang)}</strong></span>
|
||||||
<div class="verwaltung-tracking-edit">
|
<div class="verwaltung-tracking-edit">
|
||||||
<input type="text" class="verwaltung-input verwaltung-tracking-input" data-order-id="${b.id}" placeholder="Tracking-Nummer" value="${escapeHtml(b.tracking || "")}" />
|
<input type="text" class="verwaltung-input verwaltung-tracking-input" data-order-id="${b.id}" placeholder="Tracking-Nummer" aria-label="Tracking-Nummer für Bestellung ${escapeHtml(b.bestellnummer || `#${b.id}`)}" value="${escapeHtml(b.tracking || "")}" />
|
||||||
<button type="button" class="btn btn-outline btn-xs verwaltung-tracking-save" data-order-id="${b.id}">💾</button>
|
<button type="button" class="btn btn-outline btn-xs verwaltung-tracking-save" data-order-id="${b.id}">💾</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="verwaltung-status-edit">
|
<div class="verwaltung-status-edit">
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const t = useTranslations(lang);
|
|||||||
<div id="cart-content" class="cart-layout" style="display:none;">
|
<div id="cart-content" class="cart-layout" style="display:none;">
|
||||||
<div class="card cart-items" id="cart-items"></div>
|
<div class="card cart-items" id="cart-items"></div>
|
||||||
<aside class="card cart-summary">
|
<aside class="card cart-summary">
|
||||||
<h3>🧾 {t.cart.subtotal}</h3>
|
<h2>🧾 {t.cart.subtotal}</h2>
|
||||||
<div id="summary-items" class="checkout-summary"></div>
|
<div id="summary-items" class="checkout-summary"></div>
|
||||||
<div class="summary-row"><span>{t.cart.subtotal}</span><span id="sum-subtotal">0,00 €</span></div>
|
<div class="summary-row"><span>{t.cart.subtotal}</span><span id="sum-subtotal">0,00 €</span></div>
|
||||||
<div class="summary-row discount-row" id="discount-row" style="display:none;"><span>{t.cart.discount}</span><span id="sum-discount">-0,00 €</span></div>
|
<div class="summary-row discount-row" id="discount-row" style="display:none;"><span>{t.cart.discount}</span><span id="sum-discount">-0,00 €</span></div>
|
||||||
|
|||||||
+21
-1
@@ -42,6 +42,20 @@
|
|||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
html { scroll-behavior: smooth; }
|
html { scroll-behavior: smooth; }
|
||||||
|
|
||||||
|
/* Für Screenreader vorhanden, visuell aber unsichtbar — für Formularfelder, die aus Platz-/
|
||||||
|
Layoutgründen keinen sichtbaren <label>-Text bekommen (z.B. Icon-/Placeholder-only Suchfelder
|
||||||
|
in der Verwaltung), damit sie trotzdem einen echten zugänglichen Namen haben (WCAG 1.3.1/3.3.2)
|
||||||
|
statt sich allein auf den (beim Tippen verschwindenden) Placeholder zu verlassen. */
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px; height: 1px;
|
||||||
|
padding: 0; margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: var(--c-black);
|
background: var(--c-black);
|
||||||
@@ -2043,7 +2057,13 @@ a:focus-visible, button:focus-visible {
|
|||||||
}
|
}
|
||||||
.qty-stepper input::-webkit-outer-spin-button,
|
.qty-stepper input::-webkit-outer-spin-button,
|
||||||
.qty-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
.qty-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
||||||
.qty-stepper input:focus { outline: none; }
|
/* Kein echtes outline (würde durch das runde .qty-stepper-Gehäuse eckig herausragen), aber
|
||||||
|
deswegen NICHT ersatzlos entfernt (WCAG 2.4.7 "Focus Visible") — stattdessen ein innen
|
||||||
|
liegender Ring per box-shadow, der sich sauber an die abgerundete Form anschmiegt. */
|
||||||
|
.qty-stepper input:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: inset 0 0 0 2px var(--c-accent);
|
||||||
|
}
|
||||||
.specs { margin-top: 1.5rem; }
|
.specs { margin-top: 1.5rem; }
|
||||||
.specs th { width: 40%; color: var(--c-text-muted); font-weight: 500; font-family: var(--font-body); }
|
.specs th { width: 40%; color: var(--c-text-muted); font-weight: 500; font-family: var(--font-body); }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user