Login-Seite professionalisiert: Social-Login-Optionen, Passwort-Toggle, DSGVO-Hinweis

Nach Recherche zu üblichen Anmelde-Standards bei deutschen Online-Shops:
Ergänzt Social-Login-Buttons für Google, Apple und PayPal (PayPal explizit
wegen seiner Marktdominanz bei Zahlungen in DE/DACH), da Social Login
Registrierung/Login deutlich beschleunigt und laut Studien die Conversion
spürbar erhöht. Die Buttons sind noch nicht funktional (kein OAuth ohne
eigenes Backend) — ein Klick zeigt stattdessen ehrlich einen Phase-2-
Hinweis, statt eine Anmeldung vorzutäuschen.

Zusätzlich: Passwort-Sichtbarkeits-Toggle (Augen-Icon, eine der
wirkungsvollsten Login-UX-Verbesserungen laut UX-Recherche), "Angemeldet
bleiben"-Checkbox, sowie ein DSGVO-konformer Hinweis mit Link zur
Datenschutzerklärung unterhalb des Formulars. Gastbestellung ("Neu hier?")
war als gleichwertige Alternative schon vorhanden und bleibt bestehen —
Gastbestellung gilt laut EDPB-Empfehlung als Standard neben der freiwilligen
Registrierung.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
qcigano
2026-08-03 04:33:12 +02:00
co-authored by Claude Sonnet 5
parent cc3e84bd98
commit ab3bf675b9
6 changed files with 393 additions and 8 deletions
+28
View File
@@ -208,6 +208,13 @@ export const ui = {
newTitle: "Neu hier?", newText: "Lege ein Konto an, um Bestellhistorie, Wunschliste und Sendungsverfolgung an einem Ort zu haben oder bestelle einfach als Gast, ganz ohne Konto.", newTitle: "Neu hier?", newText: "Lege ein Konto an, um Bestellhistorie, Wunschliste und Sendungsverfolgung an einem Ort zu haben oder bestelle einfach als Gast, ganz ohne Konto.",
guestButton: "Als Gast weiter einkaufen", guestButton: "Als Gast weiter einkaufen",
loginError: "Bitte gib eine E-Mail-Adresse ein.", loginError: "Bitte gib eine E-Mail-Adresse ein.",
socialHeading: "Schneller anmelden mit",
socialGoogle: "Mit Google anmelden", socialApple: "Mit Apple anmelden", socialPaypal: "Mit PayPal anmelden",
socialComingSoon: "Kommt in Phase 2: Sobald die echte Konto-Anbindung steht, kannst du dich hier direkt anmelden — ganz ohne neues Passwort.",
orDivider: "oder mit E-Mail",
showPassword: "Passwort anzeigen", hidePassword: "Passwort verbergen",
rememberMe: "Angemeldet bleiben",
consentPrefix: "Mit der Anmeldung akzeptierst du unsere", consentLink: "Datenschutzerklärung", consentSuffix: ".",
}, },
accountDash: { accountDash: {
eyebrow: "Mein Konto", greeting: (name: string) => `Hallo ${name} 🩵`, lead: "Schön, dass du wieder da bist. Hier findest du alle deine Bestellungen, deine Wunschliste und deine Daten auf einen Blick.", eyebrow: "Mein Konto", greeting: (name: string) => `Hallo ${name} 🩵`, lead: "Schön, dass du wieder da bist. Hier findest du alle deine Bestellungen, deine Wunschliste und deine Daten auf einen Blick.",
@@ -429,6 +436,13 @@ export const ui = {
newTitle: "New here?", newText: "Create an account to keep order history, wishlist and tracking in one place or simply check out as a guest, no account needed.", newTitle: "New here?", newText: "Create an account to keep order history, wishlist and tracking in one place or simply check out as a guest, no account needed.",
guestButton: "Continue shopping as guest", guestButton: "Continue shopping as guest",
loginError: "Please enter an email address.", loginError: "Please enter an email address.",
socialHeading: "Sign in faster with",
socialGoogle: "Sign in with Google", socialApple: "Sign in with Apple", socialPaypal: "Sign in with PayPal",
socialComingSoon: "Coming in Phase 2: once the real account backend is live, you'll be able to sign in here directly — no new password needed.",
orDivider: "or with email",
showPassword: "Show password", hidePassword: "Hide password",
rememberMe: "Keep me signed in",
consentPrefix: "By signing in you accept our", consentLink: "privacy policy", consentSuffix: ".",
}, },
accountDash: { accountDash: {
eyebrow: "My Account", greeting: (name: string) => `Hello ${name} 🩵`, lead: "Good to see you again. Here you'll find all your orders, your wishlist and your details at a glance.", eyebrow: "My Account", greeting: (name: string) => `Hello ${name} 🩵`, lead: "Good to see you again. Here you'll find all your orders, your wishlist and your details at a glance.",
@@ -650,6 +664,13 @@ export const ui = {
newTitle: "Neu da?", newText: "Leg es Konto a, für Bstellhistorie, Wunschlischte und Sändigsverfolgig a einem Ort z'ha oder bstell eifach als Gast, ganz ohni Konto.", newTitle: "Neu da?", newText: "Leg es Konto a, für Bstellhistorie, Wunschlischte und Sändigsverfolgig a einem Ort z'ha oder bstell eifach als Gast, ganz ohni Konto.",
guestButton: "Als Gast wyter yichaufe", guestButton: "Als Gast wyter yichaufe",
loginError: "Bitte gib e E-Mail-Adrässe i.", loginError: "Bitte gib e E-Mail-Adrässe i.",
socialHeading: "Schnäller aamälde mit",
socialGoogle: "Mit Google aamälde", socialApple: "Mit Apple aamälde", socialPaypal: "Mit PayPal aamälde",
socialComingSoon: "Chunnt i Phase 2: Sobald d'echti Konto-Aabindig staht, chasch di da direkt aamälde — ganz ohni nöis Passwort.",
orDivider: "oder mit E-Mail",
showPassword: "Passwort azeige", hidePassword: "Passwort verstecke",
rememberMe: "Aagmäldet bliebe",
consentPrefix: "Mit dr Aamäldig akzeptiersch üsi", consentLink: "Datenschutzerklärig", consentSuffix: ".",
}, },
accountDash: { accountDash: {
eyebrow: "Mis Konto", greeting: (name: string) => `Hoi ${name} 🩵`, lead: "Schön, bisch wieder da. Da findsch alli dini Bstellige, dini Wunschlischte und dini Date uf ein Blick.", eyebrow: "Mis Konto", greeting: (name: string) => `Hoi ${name} 🩵`, lead: "Schön, bisch wieder da. Da findsch alli dini Bstellige, dini Wunschlischte und dini Date uf ein Blick.",
@@ -871,6 +892,13 @@ export const ui = {
newTitle: "Nouveau ici ?", newText: "Créez un compte pour retrouver l'historique des commandes, la liste de souhaits et le suivi au même endroit ou commandez simplement en tant qu'invité, sans compte.", newTitle: "Nouveau ici ?", newText: "Créez un compte pour retrouver l'historique des commandes, la liste de souhaits et le suivi au même endroit ou commandez simplement en tant qu'invité, sans compte.",
guestButton: "Continuer mes achats en tant qu'invité", guestButton: "Continuer mes achats en tant qu'invité",
loginError: "Veuillez saisir une adresse e-mail.", loginError: "Veuillez saisir une adresse e-mail.",
socialHeading: "Connexion plus rapide avec",
socialGoogle: "Se connecter avec Google", socialApple: "Se connecter avec Apple", socialPaypal: "Se connecter avec PayPal",
socialComingSoon: "Disponible en phase 2 : dès que la vraie connexion au compte sera en place, vous pourrez vous connecter directement ici — sans nouveau mot de passe.",
orDivider: "ou avec e-mail",
showPassword: "Afficher le mot de passe", hidePassword: "Masquer le mot de passe",
rememberMe: "Rester connecté(e)",
consentPrefix: "En vous connectant, vous acceptez notre", consentLink: "politique de confidentialité", consentSuffix: ".",
}, },
accountDash: { accountDash: {
eyebrow: "Mon compte", greeting: (name: string) => `Bonjour ${name} 🩵`, lead: "Ravie de vous revoir. Retrouvez ici toutes vos commandes, votre liste de souhaits et vos données en un coup d'œil.", eyebrow: "Mon compte", greeting: (name: string) => `Bonjour ${name} 🩵`, lead: "Ravie de vous revoir. Retrouvez ici toutes vos commandes, votre liste de souhaits et vos données en un coup d'œil.",
+75 -2
View File
@@ -19,11 +19,60 @@ const t = useTranslations(lang);
<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> <h3>{t.account.loginTitle}</h3>
<div><label for="login-email">{t.account.email}</label><input id="login-email" type="email" required /></div>
<div><label for="login-pw">{t.account.password}</label><input id="login-pw" type="password" /></div> {/* Social-Login: in DE/DACH übliche Alternativen zu E-Mail/Passwort — Google und Apple
als verbreitetste Anbieter, PayPal zusätzlich wegen seiner Marktdominanz bei
Zahlungen in Deutschland (siehe Recherche). Ohne eigenes Backend noch nicht
funktional; ein Klick zeigt ehrlich den Phase-2-Hinweis statt eine Anmeldung
vorzutäuschen. */}
<div class="social-login">
<p class="small" style="margin:0 0 -0.2rem;">{t.account.socialHeading}</p>
<button type="button" class="btn-social btn-social-google">
<svg width="18" height="18" viewBox="0 0 48 48" aria-hidden="true"><path fill="#FFC107" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"/><path fill="#FF3D00" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"/><path fill="#4CAF50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"/><path fill="#1976D2" d="M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"/></svg>
{t.account.socialGoogle}
</button>
<button type="button" class="btn-social btn-social-apple">
<svg width="15" height="18" viewBox="0 0 814 1000" fill="currentColor" aria-hidden="true"><path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-163.9-39.5c-76.4 0-103.5 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/></svg>
{t.account.socialApple}
</button>
<button type="button" class="btn-social btn-social-paypal">
<span class="social-icon-badge" aria-hidden="true">P</span>
{t.account.socialPaypal}
</button>
<p class="small social-note" id="social-note">{t.account.socialComingSoon}</p>
</div>
<div class="social-divider">{t.account.orDivider}</div>
<div><label for="login-email">{t.account.email}</label><input id="login-email" type="email" required autocomplete="email" /></div>
<div>
<label for="login-pw">{t.account.password}</label>
<div class="pw-field">
<input id="login-pw" type="password" autocomplete="current-password" />
<button
type="button"
class="pw-toggle"
id="login-pw-toggle"
data-show-label={t.account.showPassword}
data-hide-label={t.account.hidePassword}
aria-label={t.account.showPassword}
aria-pressed="false"
>
<svg id="login-pw-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z"></path><circle cx="12" cy="12" r="3"></circle></svg>
</button>
</div>
</div>
<div class="checkbox-row">
<input id="login-remember" type="checkbox" />
<label for="login-remember">{t.account.rememberMe}</label>
</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" 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>
</form> </form>
<div class="card"> <div class="card">
@@ -51,4 +100,28 @@ const t = useTranslations(lang);
login(email); login(email);
window.location.href = "/ch/konto/angemeldet/"; window.location.href = "/ch/konto/angemeldet/";
}); });
// Passwort-Sichtbarkeits-Toggle — rein clientseitig, braucht kein Backend.
const pwInput = document.getElementById("login-pw") as HTMLInputElement | null;
const pwToggle = document.getElementById("login-pw-toggle") as HTMLButtonElement | null;
const EYE_OPEN = `<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z"></path><circle cx="12" cy="12" r="3"></circle>`;
const EYE_CLOSED = `<path d="M3 3l18 18"></path><path d="M10.6 10.6a2 2 0 0 0 2.8 2.8"></path><path d="M9.9 4.24A9.9 9.9 0 0 1 12 4c7 0 11 7 11 7a13.2 13.2 0 0 1-3.4 4.06M6.1 6.1C3.5 7.9 1 12 1 12s4 7 11 7a9.9 9.9 0 0 0 4.9-1.24"></path>`;
pwToggle?.addEventListener("click", () => {
if (!pwInput) return;
const showing = pwInput.type === "text";
pwInput.type = showing ? "password" : "text";
pwToggle.setAttribute("aria-pressed", String(!showing));
pwToggle.setAttribute("aria-label", showing ? (pwToggle.dataset.showLabel ?? "") : (pwToggle.dataset.hideLabel ?? ""));
const icon = document.getElementById("login-pw-icon");
if (icon) icon.innerHTML = showing ? EYE_OPEN : EYE_CLOSED;
});
// Social-Login-Buttons: kein echtes OAuth ohne Backend — Klick zeigt stattdessen ehrlich
// den Phase-2-Hinweis (siehe .social-note oben im Markup).
const socialNote = document.getElementById("social-note");
document.querySelectorAll(".btn-social").forEach((btn) => {
btn.addEventListener("click", () => {
if (socialNote) socialNote.style.display = "block";
});
});
</script> </script>
+75 -2
View File
@@ -19,11 +19,60 @@ const t = useTranslations(lang);
<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> <h3>{t.account.loginTitle}</h3>
<div><label for="login-email">{t.account.email}</label><input id="login-email" type="email" required /></div>
<div><label for="login-pw">{t.account.password}</label><input id="login-pw" type="password" /></div> {/* Social-Login: in DE/DACH übliche Alternativen zu E-Mail/Passwort — Google und Apple
als verbreitetste Anbieter, PayPal zusätzlich wegen seiner Marktdominanz bei
Zahlungen in Deutschland (siehe Recherche). Ohne eigenes Backend noch nicht
funktional; ein Klick zeigt ehrlich den Phase-2-Hinweis statt eine Anmeldung
vorzutäuschen. */}
<div class="social-login">
<p class="small" style="margin:0 0 -0.2rem;">{t.account.socialHeading}</p>
<button type="button" class="btn-social btn-social-google">
<svg width="18" height="18" viewBox="0 0 48 48" aria-hidden="true"><path fill="#FFC107" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"/><path fill="#FF3D00" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"/><path fill="#4CAF50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"/><path fill="#1976D2" d="M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"/></svg>
{t.account.socialGoogle}
</button>
<button type="button" class="btn-social btn-social-apple">
<svg width="15" height="18" viewBox="0 0 814 1000" fill="currentColor" aria-hidden="true"><path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-163.9-39.5c-76.4 0-103.5 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/></svg>
{t.account.socialApple}
</button>
<button type="button" class="btn-social btn-social-paypal">
<span class="social-icon-badge" aria-hidden="true">P</span>
{t.account.socialPaypal}
</button>
<p class="small social-note" id="social-note">{t.account.socialComingSoon}</p>
</div>
<div class="social-divider">{t.account.orDivider}</div>
<div><label for="login-email">{t.account.email}</label><input id="login-email" type="email" required autocomplete="email" /></div>
<div>
<label for="login-pw">{t.account.password}</label>
<div class="pw-field">
<input id="login-pw" type="password" autocomplete="current-password" />
<button
type="button"
class="pw-toggle"
id="login-pw-toggle"
data-show-label={t.account.showPassword}
data-hide-label={t.account.hidePassword}
aria-label={t.account.showPassword}
aria-pressed="false"
>
<svg id="login-pw-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z"></path><circle cx="12" cy="12" r="3"></circle></svg>
</button>
</div>
</div>
<div class="checkbox-row">
<input id="login-remember" type="checkbox" />
<label for="login-remember">{t.account.rememberMe}</label>
</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" 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>
</form> </form>
<div class="card"> <div class="card">
@@ -51,4 +100,28 @@ const t = useTranslations(lang);
login(email); login(email);
window.location.href = "/en/konto/angemeldet/"; window.location.href = "/en/konto/angemeldet/";
}); });
// Passwort-Sichtbarkeits-Toggle — rein clientseitig, braucht kein Backend.
const pwInput = document.getElementById("login-pw") as HTMLInputElement | null;
const pwToggle = document.getElementById("login-pw-toggle") as HTMLButtonElement | null;
const EYE_OPEN = `<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z"></path><circle cx="12" cy="12" r="3"></circle>`;
const EYE_CLOSED = `<path d="M3 3l18 18"></path><path d="M10.6 10.6a2 2 0 0 0 2.8 2.8"></path><path d="M9.9 4.24A9.9 9.9 0 0 1 12 4c7 0 11 7 11 7a13.2 13.2 0 0 1-3.4 4.06M6.1 6.1C3.5 7.9 1 12 1 12s4 7 11 7a9.9 9.9 0 0 0 4.9-1.24"></path>`;
pwToggle?.addEventListener("click", () => {
if (!pwInput) return;
const showing = pwInput.type === "text";
pwInput.type = showing ? "password" : "text";
pwToggle.setAttribute("aria-pressed", String(!showing));
pwToggle.setAttribute("aria-label", showing ? (pwToggle.dataset.showLabel ?? "") : (pwToggle.dataset.hideLabel ?? ""));
const icon = document.getElementById("login-pw-icon");
if (icon) icon.innerHTML = showing ? EYE_OPEN : EYE_CLOSED;
});
// Social-Login-Buttons: kein echtes OAuth ohne Backend — Klick zeigt stattdessen ehrlich
// den Phase-2-Hinweis (siehe .social-note oben im Markup).
const socialNote = document.getElementById("social-note");
document.querySelectorAll(".btn-social").forEach((btn) => {
btn.addEventListener("click", () => {
if (socialNote) socialNote.style.display = "block";
});
});
</script> </script>
+75 -2
View File
@@ -19,11 +19,60 @@ const t = useTranslations(lang);
<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> <h3>{t.account.loginTitle}</h3>
<div><label for="login-email">{t.account.email}</label><input id="login-email" type="email" required /></div>
<div><label for="login-pw">{t.account.password}</label><input id="login-pw" type="password" /></div> {/* Social-Login: in DE/DACH übliche Alternativen zu E-Mail/Passwort — Google und Apple
als verbreitetste Anbieter, PayPal zusätzlich wegen seiner Marktdominanz bei
Zahlungen in Deutschland (siehe Recherche). Ohne eigenes Backend noch nicht
funktional; ein Klick zeigt ehrlich den Phase-2-Hinweis statt eine Anmeldung
vorzutäuschen. */}
<div class="social-login">
<p class="small" style="margin:0 0 -0.2rem;">{t.account.socialHeading}</p>
<button type="button" class="btn-social btn-social-google">
<svg width="18" height="18" viewBox="0 0 48 48" aria-hidden="true"><path fill="#FFC107" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"/><path fill="#FF3D00" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"/><path fill="#4CAF50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"/><path fill="#1976D2" d="M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"/></svg>
{t.account.socialGoogle}
</button>
<button type="button" class="btn-social btn-social-apple">
<svg width="15" height="18" viewBox="0 0 814 1000" fill="currentColor" aria-hidden="true"><path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-163.9-39.5c-76.4 0-103.5 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/></svg>
{t.account.socialApple}
</button>
<button type="button" class="btn-social btn-social-paypal">
<span class="social-icon-badge" aria-hidden="true">P</span>
{t.account.socialPaypal}
</button>
<p class="small social-note" id="social-note">{t.account.socialComingSoon}</p>
</div>
<div class="social-divider">{t.account.orDivider}</div>
<div><label for="login-email">{t.account.email}</label><input id="login-email" type="email" required autocomplete="email" /></div>
<div>
<label for="login-pw">{t.account.password}</label>
<div class="pw-field">
<input id="login-pw" type="password" autocomplete="current-password" />
<button
type="button"
class="pw-toggle"
id="login-pw-toggle"
data-show-label={t.account.showPassword}
data-hide-label={t.account.hidePassword}
aria-label={t.account.showPassword}
aria-pressed="false"
>
<svg id="login-pw-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z"></path><circle cx="12" cy="12" r="3"></circle></svg>
</button>
</div>
</div>
<div class="checkbox-row">
<input id="login-remember" type="checkbox" />
<label for="login-remember">{t.account.rememberMe}</label>
</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" 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>
</form> </form>
<div class="card"> <div class="card">
@@ -51,4 +100,28 @@ const t = useTranslations(lang);
login(email); login(email);
window.location.href = "/fr/konto/angemeldet/"; window.location.href = "/fr/konto/angemeldet/";
}); });
// Passwort-Sichtbarkeits-Toggle — rein clientseitig, braucht kein Backend.
const pwInput = document.getElementById("login-pw") as HTMLInputElement | null;
const pwToggle = document.getElementById("login-pw-toggle") as HTMLButtonElement | null;
const EYE_OPEN = `<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z"></path><circle cx="12" cy="12" r="3"></circle>`;
const EYE_CLOSED = `<path d="M3 3l18 18"></path><path d="M10.6 10.6a2 2 0 0 0 2.8 2.8"></path><path d="M9.9 4.24A9.9 9.9 0 0 1 12 4c7 0 11 7 11 7a13.2 13.2 0 0 1-3.4 4.06M6.1 6.1C3.5 7.9 1 12 1 12s4 7 11 7a9.9 9.9 0 0 0 4.9-1.24"></path>`;
pwToggle?.addEventListener("click", () => {
if (!pwInput) return;
const showing = pwInput.type === "text";
pwInput.type = showing ? "password" : "text";
pwToggle.setAttribute("aria-pressed", String(!showing));
pwToggle.setAttribute("aria-label", showing ? (pwToggle.dataset.showLabel ?? "") : (pwToggle.dataset.hideLabel ?? ""));
const icon = document.getElementById("login-pw-icon");
if (icon) icon.innerHTML = showing ? EYE_OPEN : EYE_CLOSED;
});
// Social-Login-Buttons: kein echtes OAuth ohne Backend — Klick zeigt stattdessen ehrlich
// den Phase-2-Hinweis (siehe .social-note oben im Markup).
const socialNote = document.getElementById("social-note");
document.querySelectorAll(".btn-social").forEach((btn) => {
btn.addEventListener("click", () => {
if (socialNote) socialNote.style.display = "block";
});
});
</script> </script>
+75 -2
View File
@@ -19,11 +19,60 @@ const t = useTranslations(lang);
<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> <h3>{t.account.loginTitle}</h3>
<div><label for="login-email">{t.account.email}</label><input id="login-email" type="email" required /></div>
<div><label for="login-pw">{t.account.password}</label><input id="login-pw" type="password" /></div> {/* Social-Login: in DE/DACH übliche Alternativen zu E-Mail/Passwort — Google und Apple
als verbreitetste Anbieter, PayPal zusätzlich wegen seiner Marktdominanz bei
Zahlungen in Deutschland (siehe Recherche). Ohne eigenes Backend noch nicht
funktional; ein Klick zeigt ehrlich den Phase-2-Hinweis statt eine Anmeldung
vorzutäuschen. */}
<div class="social-login">
<p class="small" style="margin:0 0 -0.2rem;">{t.account.socialHeading}</p>
<button type="button" class="btn-social btn-social-google">
<svg width="18" height="18" viewBox="0 0 48 48" aria-hidden="true"><path fill="#FFC107" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"/><path fill="#FF3D00" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"/><path fill="#4CAF50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"/><path fill="#1976D2" d="M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"/></svg>
{t.account.socialGoogle}
</button>
<button type="button" class="btn-social btn-social-apple">
<svg width="15" height="18" viewBox="0 0 814 1000" fill="currentColor" aria-hidden="true"><path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-163.9-39.5c-76.4 0-103.5 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/></svg>
{t.account.socialApple}
</button>
<button type="button" class="btn-social btn-social-paypal">
<span class="social-icon-badge" aria-hidden="true">P</span>
{t.account.socialPaypal}
</button>
<p class="small social-note" id="social-note">{t.account.socialComingSoon}</p>
</div>
<div class="social-divider">{t.account.orDivider}</div>
<div><label for="login-email">{t.account.email}</label><input id="login-email" type="email" required autocomplete="email" /></div>
<div>
<label for="login-pw">{t.account.password}</label>
<div class="pw-field">
<input id="login-pw" type="password" autocomplete="current-password" />
<button
type="button"
class="pw-toggle"
id="login-pw-toggle"
data-show-label={t.account.showPassword}
data-hide-label={t.account.hidePassword}
aria-label={t.account.showPassword}
aria-pressed="false"
>
<svg id="login-pw-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z"></path><circle cx="12" cy="12" r="3"></circle></svg>
</button>
</div>
</div>
<div class="checkbox-row">
<input id="login-remember" type="checkbox" />
<label for="login-remember">{t.account.rememberMe}</label>
</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" 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>
</form> </form>
<div class="card"> <div class="card">
@@ -51,4 +100,28 @@ const t = useTranslations(lang);
login(email); login(email);
window.location.href = "/konto/angemeldet/"; window.location.href = "/konto/angemeldet/";
}); });
// Passwort-Sichtbarkeits-Toggle — rein clientseitig, braucht kein Backend.
const pwInput = document.getElementById("login-pw") as HTMLInputElement | null;
const pwToggle = document.getElementById("login-pw-toggle") as HTMLButtonElement | null;
const EYE_OPEN = `<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z"></path><circle cx="12" cy="12" r="3"></circle>`;
const EYE_CLOSED = `<path d="M3 3l18 18"></path><path d="M10.6 10.6a2 2 0 0 0 2.8 2.8"></path><path d="M9.9 4.24A9.9 9.9 0 0 1 12 4c7 0 11 7 11 7a13.2 13.2 0 0 1-3.4 4.06M6.1 6.1C3.5 7.9 1 12 1 12s4 7 11 7a9.9 9.9 0 0 0 4.9-1.24"></path>`;
pwToggle?.addEventListener("click", () => {
if (!pwInput) return;
const showing = pwInput.type === "text";
pwInput.type = showing ? "password" : "text";
pwToggle.setAttribute("aria-pressed", String(!showing));
pwToggle.setAttribute("aria-label", showing ? (pwToggle.dataset.showLabel ?? "") : (pwToggle.dataset.hideLabel ?? ""));
const icon = document.getElementById("login-pw-icon");
if (icon) icon.innerHTML = showing ? EYE_OPEN : EYE_CLOSED;
});
// Social-Login-Buttons: kein echtes OAuth ohne Backend — Klick zeigt stattdessen ehrlich
// den Phase-2-Hinweis (siehe .social-note oben im Markup).
const socialNote = document.getElementById("social-note");
document.querySelectorAll(".btn-social").forEach((btn) => {
btn.addEventListener("click", () => {
if (socialNote) socialNote.style.display = "block";
});
});
</script> </script>
+65
View File
@@ -365,6 +365,71 @@ a:focus-visible, button:focus-visible {
.frm .checkbox-row { display: flex; gap: 0.6rem; align-items: flex-start; font-size: 0.88rem; color: var(--c-text-muted); } .frm .checkbox-row { display: flex; gap: 0.6rem; align-items: flex-start; font-size: 0.88rem; color: var(--c-text-muted); }
.frm .checkbox-row input { width: auto; margin-top: 0.2rem; } .frm .checkbox-row input { width: auto; margin-top: 0.2rem; }
/* ============================================================
Login: Social-Login-Buttons, Passwort-Sichtbarkeit, DSGVO-Hinweis
In Deutschland/DACH übliche Anmelde-Alternativen zur reinen E-Mail/Passwort-
Anmeldung (siehe Recherche: PayPal-Login ist wegen der PayPal-Marktdominanz
in DE besonders verbreitet, neben Google/Apple). Die Buttons sind bewusst
NICHT funktional (kein echtes OAuth ohne Backend) — ein Klick zeigt einen
ehrlichen Hinweis statt eine vorgetäuschte Anmeldung.
============================================================ */
.social-login { display: grid; gap: 0.6rem; }
.btn-social {
display: flex;
align-items: center;
justify-content: center;
gap: 0.6em;
width: 100%;
padding: 0.7em 1em;
border-radius: var(--radius-s);
border: 1px solid rgba(244, 241, 247, 0.16);
background: var(--c-anthracite);
color: var(--c-text);
font-family: var(--font-body);
font-size: 0.92rem;
font-weight: 600;
cursor: pointer;
transition: background 0.15s var(--ease), border-color 0.15s var(--ease), transform 0.1s var(--ease);
}
.btn-social:hover { background: var(--c-anthracite-light); border-color: rgba(244, 241, 247, 0.3); }
.btn-social:active { transform: scale(0.99); }
.btn-social svg { flex-shrink: 0; }
.btn-social-paypal .social-icon-badge {
display: inline-flex; align-items: center; justify-content: center;
width: 18px; height: 18px; border-radius: 50%;
background: #003087; color: #fff; font-family: var(--font-head); font-weight: 700; font-size: 0.72rem;
flex-shrink: 0;
}
.social-note {
display: none;
font-size: 0.82rem;
color: var(--c-text-muted);
background: color-mix(in srgb, var(--c-accent) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--c-accent) 25%, transparent);
border-radius: var(--radius-s);
padding: 0.6em 0.8em;
margin: -0.2rem 0 0;
}
.social-divider { display: flex; align-items: center; gap: 0.8em; color: var(--c-text-muted); font-size: 0.82rem; margin: 0.1rem 0; }
.social-divider::before, .social-divider::after { content: ""; flex: 1; height: 1px; background: rgba(244, 241, 247, 0.14); }
.pw-field { position: relative; }
.pw-field input { padding-right: 2.6em; }
.pw-toggle {
position: absolute;
top: 50%;
right: 0.5em;
transform: translateY(-50%);
background: none;
border: none;
color: var(--c-text-muted);
cursor: pointer;
padding: 0.3em;
display: flex;
border-radius: var(--radius-s);
}
.pw-toggle:hover { color: var(--c-text); }
.login-consent { color: var(--c-text-muted); margin-top: -0.3rem; }
/* ============================================================ /* ============================================================
Sprach-Umschalter + Konto-Button (Header) Sprach-Umschalter + Konto-Button (Header)
============================================================ */ ============================================================ */