diff --git a/app/components/KlaroConsentManager.tsx b/app/components/KlaroConsentManager.tsx index fc523da..78a0b21 100644 --- a/app/components/KlaroConsentManager.tsx +++ b/app/components/KlaroConsentManager.tsx @@ -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 diff --git a/app/lib/klaroConfig.ts b/app/lib/klaroConfig.ts index 84e08c0..a9eb18b 100644 --- a/app/lib/klaroConfig.ts +++ b/app/lib/klaroConfig.ts @@ -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_ (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 = {}; - for (const code of codes) serviceTranslations[serviceName(code)] = { title: providerTitle(code) }; + const serviceTranslations: Record = {}; + 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, }, },