From 7104f97feb348c1360e52be5800e82c6e79f3907 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 09:47:28 +0000 Subject: [PATCH] Shop Review B I-2, I-3: Gewaehrleistungskasten vor der Zusammenfassung, AGB-Hinweis ueber dem Bestellknopf Die amtliche Gewaehrleistungsgrafik steht in hervorgehobenem Kasten mit Ueberschrift vor der Zusammenfassung der Kasse (hoechstens 420 px breit, sofort geladen, Vergroessern oeffnet sie in einem mit Esc und Knopf schliessbaren Dialog). Direkt ueber "Zahlungspflichtig bestellen" steht "Es gelten unsere AGB. Informationen zu Ihrem Widerrufsrecht finden Sie in der Widerrufsbelehrung." mit Links auf beide Texte. Co-Authored-By: Claude Opus 5.5 --- public/embed/shop.js | 71 ++++++++++++++++++++++++++++-------- test/shopEmbedStatic.test.js | 34 ++++++++++++++++- 2 files changed, 88 insertions(+), 17 deletions(-) diff --git a/public/embed/shop.js b/public/embed/shop.js index 115bf59..e5e3d43 100644 --- a/public/embed/shop.js +++ b/public/embed/shop.js @@ -113,8 +113,13 @@ '.wks-summen .wks-gesamt{font-weight:700;font-size:1.1rem;border-top:1px solid var(--wks-i-rand);padding-top:.35rem}', '.wks-zusammen{margin:0 0 1rem;display:grid;grid-template-columns:max-content 1fr;gap:.35rem 1rem}', '.wks-zusammen dt{font-weight:600}.wks-zusammen dd{margin:0;overflow-wrap:anywhere;white-space:pre-line}', - '.wks-gewaehr{margin:.75rem 0}', - '.wks-gewaehr img{display:block;max-width:100%;height:auto;border:1px solid var(--wks-i-rand);border-radius:calc(var(--wks-i-radius) * .7)}', + '.wks-gewaehr{margin:1.25rem 0;padding:.9rem 1rem;border:2px solid var(--wks-i-akzent);border-radius:var(--wks-i-radius);background:var(--wks-i-weich)}', + '.wks-gewaehr h4{font-size:1rem;margin:0 0 .6rem}', + '.wks-gewaehr img{display:block;width:100%;max-width:420px;height:auto;margin:0 0 .6rem;border:1px solid var(--wks-i-rand);border-radius:calc(var(--wks-i-radius) * .7);background:#fff}', + '.wks-gewaehr>:last-child{margin-bottom:0}', + '.wks-agb-hinweis{margin:0 0 .75rem}', + 'dialog.wks-dialog.wks-dialog-gross{width:min(64rem,calc(100vw - 1.5rem))}', + '.wks-dialog-bild{display:block;max-width:100%;height:auto;margin:0 auto 1rem;background:#fff}', '.wks-links{display:flex;flex-wrap:wrap;gap:.25rem 1.25rem;margin:0 0 .75rem}', '.wks-box{margin:0 0 1rem;padding:.75rem 1rem;border-radius:var(--wks-i-radius);background:var(--wks-i-weich)}', '.wks-box h4{font-size:1rem;margin:0 0 .25rem}', @@ -645,9 +650,12 @@ dlg = el('dialog', 'wks-dialog') dlg.addEventListener('click', function (e) { if (e.target === dlg) dialogSchliessen() }) dlg.addEventListener('close', function () { if (dlgAusloeser && dlgAusloeser.focus) dlgAusloeser.focus() }) + // Esc schliesst auch dort, wo showModal fehlt (der Browser erledigt es sonst selbst) + dlg.addEventListener('keydown', function (e) { if (e.key === 'Escape' || e.key === 'Esc') { e.preventDefault(); dialogSchliessen() } }) root.appendChild(dlg) } dlgAusloeser = ausloeser || null + dlg.className = 'wks-dialog' + (bloecke.some(function (b) { return !!b.bild }) ? ' wks-dialog-gross' : '') dlg.textContent = '' var innen = el('div', 'wks-dialog-innen') var zu = knopf('wks-schliessen', '×', dialogSchliessen) @@ -661,6 +669,14 @@ text.setAttribute('role', 'region') text.setAttribute('aria-label', ueberschrift) bloecke.forEach(function (b) { + if (b.bild) { + var gross = el('img', 'wks-dialog-bild') + gross.src = b.bild + gross.alt = nichtLeer(b.text) ? b.text : '' + gross.decoding = 'async' + text.appendChild(gross) + return + } if (!nichtLeer(b.text)) return if (b.titel) text.appendChild(el('h4', 'wks-untertitel', b.titel)) text.appendChild(absaetze(b.text, '')) @@ -705,6 +721,31 @@ dialogOeffnen('Versand und Zahlung', bloecke, ausloeser) } + // Hinweis auf das gesetzliche Gewaehrleistungsrecht (Art. 246a § 1 Abs. 1 S. 1 Nr. 11 EGBGB): amtliche Grafik in hervorgehobenem + // Kasten vor der Zusammenfassung der Kasse, sofort geladen, mit dem woertlichen Text als Alternativtext und einer Vergroesserung + function gewaehrKasten () { + var g = gewaehr() + if (!g) return null + var kasten = el('section', 'wks-gewaehr') + kasten.setAttribute('aria-label', 'Ihre gesetzlichen Gewährleistungsrechte') + kasten.appendChild(el('h4', '', 'Ihre gesetzlichen Gewährleistungsrechte')) + var bildUrl = httpsUrl(g.bild) + if (bildUrl) { + var img = el('img') + img.src = bildUrl + img.alt = g.text + img.loading = 'eager' + img.decoding = 'async' + kasten.appendChild(img) + kasten.appendChild(knopf('wks-btn wks-btn-sek', 'Vergrößern', function (e) { + dialogOeffnen('Ihre gesetzlichen Gewährleistungsrechte', [{ bild: bildUrl, text: g.text }], e.currentTarget) + })) + } else { + kasten.appendChild(absaetze(g.text, 'wks-text')) + } + return kasten + } + // ------------------------------------------------------------ Formularhelfer function honigfeld () { @@ -1258,6 +1299,10 @@ landSichtbar() landSel.addEventListener('change', function () { S.land = landSel.value; pruefe() }) + // Gewaehrleistungshinweis: hervorgehoben vor der Zusammenfassung (wird nicht mit der Zusammenfassung neu aufgebaut) + var gkasten = gewaehrKasten() + if (gkasten) form.appendChild(gkasten) + // Pflichtinformationen: stehen unmittelbar ueber dem Knopf und kommen zum grossen Teil vom Server (/pruefen) var pflicht = el('div', 'wks-pflicht') form.appendChild(pflicht) @@ -1265,6 +1310,14 @@ form.appendChild(honigfeld()) var fehlerBox = el('div', 'wks-fehlerbox') form.appendChild(fehlerBox) + // Einbeziehung der AGB (§ 305 Abs. 2 BGB): ausdruecklicher Hinweis unmittelbar ueber dem Knopf, beide Texte oeffnen sich im Dialog + var agbHinweis = el('p', 'wks-agb-hinweis') + agbHinweis.appendChild(document.createTextNode('Es gelten unsere ')) + agbHinweis.appendChild(knopf('wks-link', 'AGB', function (e) { zeigeAgb(e.currentTarget) })) + agbHinweis.appendChild(document.createTextNode('. Informationen zu Ihrem Widerrufsrecht finden Sie in der ')) + agbHinweis.appendChild(knopf('wks-link', 'Widerrufsbelehrung', function (e) { zeigeBelehrung(e.currentTarget) })) + agbHinweis.appendChild(document.createTextNode('.')) + form.appendChild(agbHinweis) var senden = el('button', 'wks-btn', 'Zahlungspflichtig bestellen') senden.type = 'submit' senden.disabled = true @@ -1333,20 +1386,6 @@ links.appendChild(knopf('wks-link', 'Versand & Zahlung lesen', function (e) { zeigeVersand(e.currentTarget) })) pflicht.appendChild(links) - var g = gewaehr() - var bildUrl = g ? httpsUrl(g.bild) : '' - var gbox = el('div', 'wks-gewaehr') - if (bildUrl) { - var img = el('img') - img.src = bildUrl - img.alt = g.text - img.loading = 'lazy' - img.decoding = 'async' - gbox.appendChild(img) - } else if (g) { - gbox.appendChild(absaetze(g.text, 'wks-text')) - } - pflicht.appendChild(gbox) pflicht.appendChild(el('p', '', 'Name, Lieferadresse und Zahlung geben Sie im nächsten Schritt bei unserem Zahlungsdienstleister Stripe ein.')) senden.disabled = S.sendet } diff --git a/test/shopEmbedStatic.test.js b/test/shopEmbedStatic.test.js index 09ed939..bb1e917 100644 --- a/test/shopEmbedStatic.test.js +++ b/test/shopEmbedStatic.test.js @@ -88,7 +88,7 @@ test('Pflichttexte: Knopf der Kasse, Widerrufsfunktion, Gewaehrleistungsbild, St 'Der Shop ist gerade geschlossen.', 'Shop-Technik: WEBklar', 'Lieferung nach: ', 'Abholung möglich', 'Zahlung: ', "'AGB'", "'Widerrufsbelehrung'", "'Versand & Zahlung'", "'Impressum'", "'Datenschutz'", 'zzgl. Versand', 'Vielen Dank für Ihre Bestellung', 'bei uns eingegangen. Eine Eingangsbestätigung haben wir Ihnen per E-Mail geschickt.', - 'rel = \'noopener noreferrer\'', 'img.alt = g.text', "img.loading = 'lazy'", 'gewaehrleistung', + 'rel = \'noopener noreferrer\'', 'img.alt = g.text', "img.loading = 'eager'", 'gewaehrleistung', 'wk_bestellung', 'wk_t', 'wk_abbruch', 'replaceState', 'kontakt_url', 'formToken', 'rueckUrl: location.href', 'nurWiderruf', 'data-webklar-shop', 'data-api', 'data-ansicht', "'ansicht'", 'aria-live', 'aria-pressed', 'tabIndex = -1', 'showModal', ]) { @@ -100,6 +100,38 @@ test('Pflichttexte: Knopf der Kasse, Widerrufsfunktion, Gewaehrleistungsbild, St assert.ok(OHNE_KOMMENTARE.includes("knopf('wks-btn wks-btn-sek', 'Vertrag widerrufen'"), 'Widerruf im Fuss ist ein Knopf') }) +test('I-2: Gewährleistungsgrafik steht hervorgehoben vor der Zusammenfassung, höchstens 420 px, sofort geladen, mit Vergrößern-Dialog', () => { + assert.ok(OHNE_KOMMENTARE.includes("'Ihre gesetzlichen Gewährleistungsrechte'"), 'Überschrift') + assert.match(OHNE_KOMMENTARE, /\.wks-gewaehr img\{[^']*width:100%;max-width:420px/) + assert.match(OHNE_KOMMENTARE, /\.wks-gewaehr\{[^']*border:2px solid/) + const kastenCode = OHNE_KOMMENTARE.slice(OHNE_KOMMENTARE.indexOf('function gewaehrKasten'), OHNE_KOMMENTARE.indexOf('function honigfeld')) + assert.ok(kastenCode.includes("img.alt = g.text") && !kastenCode.includes('lazy'), 'Pflichtbild nicht verzögert laden, Alt-Text = voller Text') + assert.ok(OHNE_KOMMENTARE.includes("img.loading = 'eager'")) + assert.ok(OHNE_KOMMENTARE.includes("'Vergrößern'"), 'Knopf') + assert.match(OHNE_KOMMENTARE, /dialogOeffnen\('Ihre gesetzlichen Gewährleistungsrechte', \[\{ bild: bildUrl, text: g\.text \}\]/) + assert.match(OHNE_KOMMENTARE, /e\.key === 'Escape'/, 'Esc schließt') + assert.ok(OHNE_KOMMENTARE.includes('knopf(\'wks-schliessen\', \'×\', dialogSchliessen)'), 'Schließen-Knopf') + // Reihenfolge in der Kasse: Kasten -> Zusammenfassung -> AGB-Hinweis -> Knopf + const i = (t) => OHNE_KOMMENTARE.indexOf(t) + const kasten = i('form.appendChild(gkasten)') + const pflicht = i('form.appendChild(pflicht)') + const hinweis = i('form.appendChild(agbHinweis)') + const knopf = i("var senden = el('button', 'wks-btn', 'Zahlungspflichtig bestellen')") + assert.ok(kasten > 0 && kasten < pflicht && pflicht < hinweis && hinweis < knopf, 'Reihenfolge') + // nicht mehr in der Zusammenfassung (die wird bei jeder Änderung neu aufgebaut) + const fuelle = OHNE_KOMMENTARE.slice(i('function fuellePflicht'), i('function landName')) + assert.ok(!fuelle.includes('gewaehr'), 'Gewährleistung nicht in fuellePflicht') +}) + +test('I-3: ausdrücklicher AGB-Hinweis direkt über dem Bestellknopf, AGB und Widerrufsbelehrung öffnen die Texte', () => { + const stelle = OHNE_KOMMENTARE.slice(OHNE_KOMMENTARE.indexOf("var agbHinweis = el('p'"), OHNE_KOMMENTARE.indexOf("var senden = el('button'")) + assert.ok(stelle.includes("createTextNode('Es gelten unsere ')")) + assert.ok(stelle.includes("knopf('wks-link', 'AGB', function (e) { zeigeAgb(e.currentTarget) })")) + assert.ok(stelle.includes("createTextNode('. Informationen zu Ihrem Widerrufsrecht finden Sie in der ')")) + assert.ok(stelle.includes("knopf('wks-link', 'Widerrufsbelehrung', function (e) { zeigeBelehrung(e.currentTarget) })")) + assert.ok(stelle.includes("createTextNode('.')")) +}) + test('Weiterleitung zu Stripe nur zu einer https-Adresse (httpsUrl), Links der Rechtstexte nur als https', () => { assert.match(OHNE_KOMMENTARE, /var ziel = d \? httpsUrl\(d\.url\) : ''/) assert.match(OHNE_KOMMENTARE, /location\.href = ziel/)