Klaro polish: remove redundant toggle-all text, responsive button rows, real per-service descriptions

- Empties Klaro's own "Mit diesem Schalter..." boilerplate under the
  modal's per-service toggle-all switch — redundant next to a plainly
  labeled switch.
- .cn-ok/.cn-buttons/.cm-buttons now explicit flex-wrap rows (side by
  side until they genuinely don't fit, then wrap) instead of relying
  on Klaro's own fragile inline-block flow.
- Every tracking-codes provider gets a real description in the modal's
  service list (was title-only) — what it does and, where publicly
  documented and stable, the actual cookie names/lifetimes (_ga/
  _ga_<container-id> for GA4, _fbp/fr for Facebook Pixel). GTM/Maps
  descriptions are deliberately non-specific about exact cookies —
  neither has a fixed, publicly enumerable list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-08-01 22:04:13 +00:00
parent ac6c3d6b71
commit 7ef6d2652b
2 changed files with 48 additions and 2 deletions
+13
View File
@@ -73,6 +73,19 @@ function KlaroTheme() {
.klaro .cm-btn:hover { opacity: 0.88; }
.klaro .cm-btn:active { transform: scale(0.97); }
.klaro .cm-btn:focus-visible { outline: 2px solid #f6a701 !important; outline-offset: 2px; }
/* Side-by-side by default (accept/decline in the notice, the
modal's own bottom action row), only wrapping to a stacked
layout once they genuinely don't fit — flex-wrap does this
naturally at any width, no fixed breakpoint needed. Klaro's own
.cn-ok/.cn-buttons rely on inline-block flow for the same
result, which is fragile; this is the explicit version. */
.klaro .cn-ok, .klaro .cn-buttons, .klaro .cm-buttons {
display: flex !important;
flex-wrap: wrap !important;
gap: 10px !important;
width: auto !important;
}
.klaro .cn-buttons .cm-btn, .klaro .cm-buttons .cm-btn { width: auto !important; margin: 0 !important; }
/* The "Karte laden?" placeholder ContextualConsentNotice renders in
place of a gated embed (GoogleMapsEmbed.tsx) — fills that embed's
own aspect-ratio box, so this needs to look like a real card
+35 -2
View File
@@ -56,14 +56,40 @@ function providerTitle(code: TrackingCode): string {
}
}
// Shown per service in the modal's expanded list — without this, Klaro
// only ever shows the bare provider title (see providerTitle above),
// which is thin for real TTDSG transparency ("what does this actually
// do") and reads as less trustworthy than a CMP that explains itself.
// Generic per-provider text, not per-row — a second Google Analytics row
// would get the identical description, which is fine since the "what
// GA does" fact doesn't change between rows, only which property it's
// wired to (already visible via the row's own `name` if an admin sets a
// distinguishing one).
function providerDescription(code: TrackingCode): string {
switch (code.provider) {
case "google-analytics":
return "Erfasst anonymisierte Nutzungsstatistiken (z. B. welche Seiten wie oft besucht werden), damit wir verstehen, was bei euch ankommt. Setzt die Cookies _ga (Laufzeit 2 Jahre, unterscheidet Besucher:innen) und _ga_<Container-ID> (2 Jahre, Sitzungsstatus).";
case "facebook-pixel":
return "Misst, ob ein Besuch von hier zu einer Anzeige bei Facebook/Instagram passt, und hilft uns, Werbung zielgerichteter zu schalten. Setzt den Cookie _fbp (3 Monate, eigene Domain); ist ein Facebook-Konto im selben Browser aktiv, setzt facebook.com zusätzlich den Cookie fr (3 Monate).";
case "google-tag-manager":
return "Lädt weitere Mess-/Marketing-Skripte je nach Konfiguration nach, ohne dass wir dafür jedes Mal den Code der Seite ändern müssen. Setzt selbst keine eigenen Cookies — welche Cookies letztlich gesetzt werden, hängt von den darüber geladenen Diensten ab (siehe deren eigene Einträge hier).";
case "google-maps":
return "Bindet eine interaktive Karte von Google ein. Beim Laden überträgt dein Browser deine IP-Adresse an Google und Google setzt eigene Cookies (u. a. NID), deren genauer Umfang außerhalb unserer Kontrolle liegt.";
default:
return "Ein zusätzliches Skript, das diese Seite einbindet.";
}
}
// `onAccept` is threaded in rather than importing loadTrackingCode.ts
// directly here — this module only builds a plain config object (no DOM
// access), kept separate from the actual script-injection side effect so
// it stays trivially testable/reusable if the loading mechanism ever
// changes.
export function buildKlaroConfig(codes: TrackingCode[], onAccept: (code: TrackingCode) => void): KlaroConfig {
const serviceTranslations: Record<string, { title: string }> = {};
for (const code of codes) serviceTranslations[serviceName(code)] = { title: providerTitle(code) };
const serviceTranslations: Record<string, { title: string; description: string }> = {};
for (const code of codes) {
serviceTranslations[serviceName(code)] = { title: providerTitle(code), description: providerDescription(code) };
}
return {
version: 1,
@@ -129,6 +155,13 @@ export function buildKlaroConfig(codes: TrackingCode[], onAccept: (code: Trackin
marketing: "Marketing",
content: "Inhalte",
},
// Klaro's own bundled German copy for the per-service "alle
// umschalten" toggle in the modal — the explanatory line reads as
// redundant boilerplate next to a plainly-labeled switch, and the
// toggle's own text color made it barely legible anyway (see
// KlaroTheme's own comment). Title stays (Klaro's own default is
// fine), only the description is emptied.
service: { disableAll: { description: "" } },
...serviceTranslations,
},
},