feat: Google Consent Mode v2 + Developer Portal cookie banner docs

Phase A: Google Consent Mode v2 in cookie-banner-embed.ts
- gtag('consent', 'default', {...denied}) before banner loads
- gtag('consent', 'update', {...}) after user decision
- Automatic mapping: statistics→analytics_storage, marketing→ad_storage

Phase B: 5 Developer Portal pages at /sdk/consent/cookie-banner/
- Overview page with 4 cards
- Integration Guide: 3-step setup, script-tag, categories
- Google Consent Mode: automatic integration, parameter mapping
- Script Blocking: type=text/plain pattern, GA/FB/Hotjar examples
- Compliance Checklist: 12 points, 9 automatic

Sidebar navigation extended with Cookie-Banner section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-02 17:13:34 +02:00
parent dccd9d09e5
commit cb2d503e84
7 changed files with 504 additions and 0 deletions
@@ -325,6 +325,29 @@ function generateJS(config: CookieBannerConfig): string {
const CATEGORIES = ${JSON.stringify(categoryIds)}; const CATEGORIES = ${JSON.stringify(categoryIds)};
const REQUIRED_CATEGORIES = ${JSON.stringify(requiredCategories)}; const REQUIRED_CATEGORIES = ${JSON.stringify(requiredCategories)};
// Google Consent Mode v2 — PFLICHT seit Maerz 2024 fuer Google Services in EEA
// Sets default consent state to "denied" BEFORE any Google tags fire
if (typeof gtag === 'function') {
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
functionality_storage: 'granted',
security_storage: 'granted',
});
}
function updateGoogleConsentMode(consent) {
if (typeof gtag !== 'function') return;
gtag('consent', 'update', {
analytics_storage: consent.statistics ? 'granted' : 'denied',
ad_storage: consent.marketing ? 'granted' : 'denied',
ad_user_data: consent.marketing ? 'granted' : 'denied',
ad_personalization: consent.marketing ? 'granted' : 'denied',
});
}
function getConsent() { function getConsent() {
const cookie = document.cookie.split('; ').find(row => row.startsWith(COOKIE_NAME + '=')); const cookie = document.cookie.split('; ').find(row => row.startsWith(COOKIE_NAME + '='));
if (!cookie) return null; if (!cookie) return null;
@@ -342,6 +365,7 @@ function generateJS(config: CookieBannerConfig): string {
';expires=' + date.toUTCString() + ';expires=' + date.toUTCString() +
';path=/;SameSite=Lax'; ';path=/;SameSite=Lax';
window.dispatchEvent(new CustomEvent('cookieConsentUpdated', { detail: consent })); window.dispatchEvent(new CustomEvent('cookieConsentUpdated', { detail: consent }));
updateGoogleConsentMode(consent);
} }
function hasConsent(category) { function hasConsent(category) {
@@ -0,0 +1,87 @@
'use client'
import React from 'react'
import { SDKDocsSidebar } from '@/components/SDKDocsSidebar'
const CHECKS = [
{ id: 1, text: "Impressum-Link im oder hinter dem Banner erreichbar", ref: "§5 TMG", auto: true },
{ id: 2, text: "Link zur Datenschutzerklaerung im Banner", ref: "Art. 13 DSGVO", auto: true },
{ id: 3, text: "Keine 'Zustimmung zur DSE' — nur 'zur Kenntnis genommen'", ref: "Art. 13 DSGVO", auto: true },
{ id: 4, text: "Ablehnen-Button sichtbar und gleichwertig", ref: "§25 TDDDG, EDPB", auto: true },
{ id: 5, text: "Keine vorausgewaehlten Checkboxen (nur 'Notwendig')", ref: "EuGH Planet49", auto: true },
{ id: 6, text: "Buttons gleich gross (kein Dark Pattern)", ref: "EDPB Guidelines", auto: true },
{ id: 7, text: "Cookie-Einstellungen erneut erreichbar (Widerruf)", ref: "Art. 7(3) DSGVO", auto: true },
{ id: 8, text: "Scripts blockiert bis Consent erteilt", ref: "§25 TDDDG", auto: true },
{ id: 9, text: "Google Consent Mode v2 integriert (wenn Google Services)", ref: "Google EEA Policy", auto: true },
{ id: 10, text: "Consent revisionssicher gespeichert (Server-Side)", ref: "Art. 7(1) DSGVO", auto: false },
{ id: 11, text: "Barrierefreiheit (WCAG 2.2 Level AA)", ref: "European Accessibility Act", auto: false },
{ id: 12, text: "Mehrsprachigkeit (min. DE + EN)", ref: "Best Practice", auto: false },
]
export default function ChecklistPage() {
return (
<div className="flex min-h-screen bg-white">
<SDKDocsSidebar />
<main className="flex-1 ml-64 p-8 max-w-4xl">
<div className="prose prose-gray max-w-none">
<h1>Compliance Checklist</h1>
<p className="text-lg text-gray-600">
12 Punkte die Ihr Cookie-Banner erfuellen muss. Der BreakPilot Banner
erfuellt 9 davon automatisch.
</p>
<div className="bg-green-50 border border-green-200 rounded-xl p-4 my-6">
<h4 className="text-green-900 mt-0">Automatische Pruefung verfuegbar</h4>
<p className="text-green-700 text-sm mb-0">
Nutzen Sie den <a href="/sdk/agent" className="text-green-800 font-medium">Compliance Agent</a>
Cookie-Test Tab um Ihren Banner automatisch gegen alle 8 Playwright-Checks zu pruefen.
</p>
</div>
<div className="not-prose">
<div className="space-y-3">
{CHECKS.map(check => (
<div key={check.id} className="flex items-start gap-3 p-4 rounded-lg border border-gray-200 bg-white">
<span className={`mt-0.5 w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold ${
check.auto ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700'
}`}>
{check.auto ? '✓' : '!'}
</span>
<div className="flex-1">
<p className="text-sm font-medium text-gray-900">{check.text}</p>
<div className="flex items-center gap-2 mt-1">
<span className="text-xs text-gray-500">{check.ref}</span>
{check.auto && (
<span className="text-[10px] px-2 py-0.5 rounded-full bg-green-100 text-green-700 font-medium">
Automatisch
</span>
)}
</div>
</div>
</div>
))}
</div>
</div>
<h2 className="mt-8">Legende</h2>
<ul>
<li><span className="text-green-600 font-bold"> Automatisch</span> vom BreakPilot Banner automatisch erfuellt</li>
<li><span className="text-yellow-600 font-bold">! Manuell</span> erfordert zusaetzliche Konfiguration/Pruefung</li>
</ul>
<h2>Selbst-Test</h2>
<p>
Nach der Integration koennen Sie Ihren Banner automatisch pruefen lassen:
</p>
<ol>
<li>Oeffnen Sie den <a href="/sdk/agent">Compliance Agent</a></li>
<li>Waehlen Sie den Tab &quot;Cookie-Test&quot;</li>
<li>Geben Sie Ihre Website-URL ein</li>
<li>Der Agent prueft mit einem echten Browser (Playwright/Chromium)</li>
<li>Sie erhalten einen detaillierten Report mit allen 8 Checks</li>
</ol>
</div>
</main>
</div>
)
}
@@ -0,0 +1,96 @@
'use client'
import React from 'react'
import { SDKDocsSidebar } from '@/components/SDKDocsSidebar'
export default function GoogleConsentModePage() {
return (
<div className="flex min-h-screen bg-white">
<SDKDocsSidebar />
<main className="flex-1 ml-64 p-8 max-w-4xl">
<div className="prose prose-gray max-w-none">
<h1>Google Consent Mode v2</h1>
<p className="text-lg text-gray-600">
Seit Maerz 2024 ist Google Consent Mode v2 Pflicht fuer alle Websites
die Google Analytics oder Google Ads im EWR nutzen.
</p>
<div className="bg-red-50 border border-red-200 rounded-xl p-4 my-6">
<h4 className="text-red-900 mt-0">Pflicht seit Maerz 2024</h4>
<p className="text-red-700 text-sm mb-0">
Ohne Google Consent Mode v2 verlieren Sie Messdaten von EWR-Besuchern.
Google Tags funktionieren moeglicherweise nicht mehr korrekt.
</p>
</div>
<h2>Was ist Google Consent Mode?</h2>
<p>
Google Consent Mode passt das Verhalten von Google Tags (Analytics, Ads)
automatisch an die Consent-Entscheidung des Nutzers an. Statt Tags komplett
zu blockieren, sendet Consent Mode anonymisierte Pings die Google fuer
Modellierung und Conversion-Tracking nutzen kann.
</p>
<h2>Automatische Integration</h2>
<p>
Der BreakPilot Cookie-Banner integriert Google Consent Mode v2 <strong>automatisch</strong>.
Sie muessen nichts zusaetzlich konfigurieren.
</p>
<h3>Was passiert im Hintergrund</h3>
<p><strong>Beim Laden der Seite (vor Banner):</strong></p>
<pre className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
{`// Automatisch vom Banner gesetzt — alles "denied"
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
functionality_storage: 'granted',
security_storage: 'granted',
});`}
</pre>
<p><strong>Nach Consent-Entscheidung des Nutzers:</strong></p>
<pre className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
{`// Automatisch nach Klick auf "Akzeptieren" oder "Auswahl speichern"
gtag('consent', 'update', {
analytics_storage: 'granted', // Wenn "Statistik" akzeptiert
ad_storage: 'granted', // Wenn "Marketing" akzeptiert
ad_user_data: 'granted',
ad_personalization: 'granted',
});`}
</pre>
<h2>Consent-Parameter Mapping</h2>
<table className="min-w-full">
<thead>
<tr>
<th>Google Parameter</th>
<th>Banner-Kategorie</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr><td><code>analytics_storage</code></td><td>Statistik</td><td>Google Analytics Cookies</td></tr>
<tr><td><code>ad_storage</code></td><td>Marketing</td><td>Werbe-Cookies (Google Ads)</td></tr>
<tr><td><code>ad_user_data</code></td><td>Marketing</td><td>Nutzerdaten fuer Werbung</td></tr>
<tr><td><code>ad_personalization</code></td><td>Marketing</td><td>Personalisierte Werbung</td></tr>
<tr><td><code>functionality_storage</code></td><td>Notwendig</td><td>Immer granted</td></tr>
<tr><td><code>security_storage</code></td><td>Notwendig</td><td>Immer granted</td></tr>
</tbody>
</table>
<h2>Vorteile</h2>
<ul>
<li>Kein Datenverlust Google modelliert fehlende Daten</li>
<li>DSGVO-konform Tags feuern nur nach Consent</li>
<li>Automatisch keine manuelle gtag()-Konfiguration noetig</li>
<li>EWR-konform erfuellt Google-Anforderungen seit Maerz 2024</li>
</ul>
</div>
</main>
</div>
)
}
@@ -0,0 +1,103 @@
'use client'
import React from 'react'
import { SDKDocsSidebar } from '@/components/SDKDocsSidebar'
export default function IntegrationGuidePage() {
return (
<div className="flex min-h-screen bg-white">
<SDKDocsSidebar />
<main className="flex-1 ml-64 p-8 max-w-4xl">
<div className="prose prose-gray max-w-none">
<h1>Cookie-Banner Integration Guide</h1>
<p className="text-lg text-gray-600">
Integrieren Sie den BreakPilot Cookie-Banner in 3 Schritten auf Ihrer Website.
DSGVO-konform, mit Script-Blocking und Google Consent Mode v2.
</p>
<div className="bg-violet-50 border border-violet-200 rounded-xl p-4 my-6">
<h4 className="text-violet-900 mt-0">Unser Banner ist rechtlich geprueft</h4>
<p className="text-violet-700 text-sm mb-0">
Der Banner erfuellt automatisch alle 8 Compliance-Checks die unser Agent prueft:
Impressum-Link, DSE-Link, gleichwertige Buttons, keine vorausgewaehlten Checkboxen,
Settings-Reopen, kein Dark Pattern, Google Consent Mode v2.
</p>
</div>
<h2>Schritt 1: Script-Tag einbinden</h2>
<p>Fuegen Sie eine einzige Zeile in den <code>&lt;head&gt;</code> Ihrer Website ein:</p>
<pre className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
{`<!-- BreakPilot Cookie-Banner -->
<script src="https://sdk.breakpilot.ai/cookie-banner.js"
data-tenant="IHRE-TENANT-ID"
data-language="de">
</script>`}
</pre>
<h2>Schritt 2: Tracking-Scripts blockieren</h2>
<p>
Aendern Sie den <code>type</code> Ihrer Tracking-Scripts von
<code>text/javascript</code> zu <code>text/plain</code> und fuegen Sie
das <code>data-cookie-category</code> Attribut hinzu:
</p>
<pre className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
{`<!-- VORHER (laedt sofort — DSGVO-Verstoss!): -->
<script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"></script>
<!-- NACHHER (laedt erst nach Consent): -->
<script type="text/plain"
data-cookie-category="statistics"
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX">
</script>`}
</pre>
<h2>Schritt 3: Kategorien zuordnen</h2>
<table className="min-w-full">
<thead>
<tr>
<th>Kategorie</th>
<th>data-cookie-category</th>
<th>Beispiele</th>
</tr>
</thead>
<tbody>
<tr><td>Notwendig</td><td><code>necessary</code></td><td>Session-Cookies, CSRF-Token</td></tr>
<tr><td>Statistik</td><td><code>statistics</code></td><td>Google Analytics, Matomo, Hotjar</td></tr>
<tr><td>Marketing</td><td><code>marketing</code></td><td>Facebook Pixel, Google Ads, LinkedIn</td></tr>
<tr><td>Funktional</td><td><code>functional</code></td><td>Chat-Widget, Spracheinstellung</td></tr>
</tbody>
</table>
<h2>Schritt 4: Fertig automatische Features</h2>
<ul>
<li><strong>Google Consent Mode v2</strong> automatisch integriert, keine zusaetzliche Konfiguration</li>
<li><strong>Impressum + DSE Links</strong> im Banner enthalten</li>
<li><strong>Settings-Reopen</strong> schwebendes &quot;Cookie-Einstellungen&quot; Icon</li>
<li><strong>Gleichwertige Buttons</strong> Akzeptieren und Ablehnen gleich gross</li>
<li><strong>Server-seitige Speicherung</strong> Consent wird revisionssicher gespeichert</li>
</ul>
<h2>API-Integration (optional)</h2>
<p>Fuer programmatische Kontrolle:</p>
<pre className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
{`// Consent-Status abfragen
if (window.CookieConsent.hasConsent('statistics')) {
// Analytics-Code ausfuehren
}
// Banner erneut oeffnen
window.CookieConsent.show();
// Consent programmatisch setzen
window.CookieConsent.saveConsent({
necessary: true,
statistics: false,
marketing: false,
functional: true,
});`}
</pre>
</div>
</main>
</div>
)
}
@@ -0,0 +1,65 @@
'use client'
import React from 'react'
import Link from 'next/link'
import { SDKDocsSidebar } from '@/components/SDKDocsSidebar'
import { FileCode, Shield, Zap, CheckCircle } from 'lucide-react'
const PAGES = [
{
title: 'Integration Guide',
href: '/sdk/consent/cookie-banner/integration',
icon: <FileCode className="w-6 h-6" />,
desc: 'Banner in 3 Schritten auf Ihrer Website einbinden. Script-Tag, Script-Blocking, fertig.',
},
{
title: 'Google Consent Mode v2',
href: '/sdk/consent/cookie-banner/google-consent-mode',
icon: <Zap className="w-6 h-6" />,
desc: 'Pflicht seit Maerz 2024 fuer Google Services in EEA. Automatisch integriert.',
},
{
title: 'Script Blocking',
href: '/sdk/consent/cookie-banner/script-blocking',
icon: <Shield className="w-6 h-6" />,
desc: 'Tracking-Scripts erst nach Consent laden. Code-Beispiele fuer GA, FB, Hotjar.',
},
{
title: 'Compliance Checklist',
href: '/sdk/consent/cookie-banner/checklist',
icon: <CheckCircle className="w-6 h-6" />,
desc: '12 Punkte die Ihr Banner erfuellen muss. 9 davon automatisch.',
},
]
export default function CookieBannerOverviewPage() {
return (
<div className="flex min-h-screen bg-white">
<SDKDocsSidebar />
<main className="flex-1 ml-64 p-8 max-w-4xl">
<h1 className="text-3xl font-bold text-gray-900">Cookie-Banner</h1>
<p className="text-lg text-gray-600 mt-2 mb-8">
Rechtlich korrekter Cookie-Banner fuer Ihre Website DSGVO-konform,
mit Script-Blocking, Google Consent Mode v2 und automatischer Compliance-Pruefung.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{PAGES.map(page => (
<Link key={page.href} href={page.href}
className="block p-6 rounded-xl border border-gray-200 hover:border-violet-300 hover:shadow-md transition-all group">
<div className="flex items-start gap-4">
<div className="p-2 rounded-lg bg-violet-50 text-violet-600 group-hover:bg-violet-100">
{page.icon}
</div>
<div>
<h3 className="font-semibold text-gray-900 group-hover:text-violet-700">{page.title}</h3>
<p className="text-sm text-gray-500 mt-1">{page.desc}</p>
</div>
</div>
</Link>
))}
</div>
</main>
</div>
)
}
@@ -0,0 +1,118 @@
'use client'
import React from 'react'
import { SDKDocsSidebar } from '@/components/SDKDocsSidebar'
export default function ScriptBlockingPage() {
return (
<div className="flex min-h-screen bg-white">
<SDKDocsSidebar />
<main className="flex-1 ml-64 p-8 max-w-4xl">
<div className="prose prose-gray max-w-none">
<h1>Script Blocking</h1>
<p className="text-lg text-gray-600">
Tracking-Scripts duerfen erst nach Einwilligung des Nutzers laden (§25 TDDDG).
So blockieren Sie Scripts korrekt mit dem BreakPilot Cookie-Banner.
</p>
<h2>Das Problem</h2>
<p>
Wird ein Tracking-Script normal eingebunden, laedt es <strong>sofort</strong>
bevor der Nutzer ueberhaupt den Cookie-Banner sieht. Das ist ein Verstoss
gegen §25 TDDDG und wird von unserem Compliance Agent als <strong>HIGH</strong>
Finding gemeldet.
</p>
<h2>Die Loesung: type=&quot;text/plain&quot;</h2>
<p>
Aendern Sie den <code>type</code> von <code>text/javascript</code> zu
<code>text/plain</code>. Der Browser ignoriert das Script bis unser Banner
es nach Consent aktiviert.
</p>
<h3>Google Analytics</h3>
<pre className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
{`<!-- Blockiert bis "Statistik" akzeptiert wird -->
<script type="text/plain"
data-cookie-category="statistics"
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX">
</script>
<script type="text/plain" data-cookie-category="statistics">
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXX');
</script>`}
</pre>
<h3>Facebook / Meta Pixel</h3>
<pre className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
{`<!-- Blockiert bis "Marketing" akzeptiert wird -->
<script type="text/plain" data-cookie-category="marketing">
!function(f,b,e,v,n,t,s){/*...Facebook Pixel Code...*/}
fbq('init', 'IHRE_PIXEL_ID');
fbq('track', 'PageView');
</script>`}
</pre>
<h3>Hotjar</h3>
<pre className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
{`<!-- Blockiert bis "Statistik" akzeptiert wird -->
<script type="text/plain" data-cookie-category="statistics">
(function(h,o,t,j,a,r){/*...Hotjar Code...*/})(window,document,
'https://static.hotjar.com/c/hotjar-','js?sv=');
</script>`}
</pre>
<h3>Google Maps / YouTube Embeds</h3>
<pre className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
{`<!-- Blockiert bis "Funktional" akzeptiert wird -->
<iframe type="text/plain"
data-cookie-category="functional"
data-src="https://www.google.com/maps/embed?pb=...">
</iframe>
<!-- YouTube mit Platzhalter -->
<div data-cookie-category="marketing"
data-cookie-placeholder="Bitte akzeptieren Sie Marketing-Cookies um dieses Video zu sehen.">
<iframe data-src="https://www.youtube-nocookie.com/embed/VIDEO_ID"></iframe>
</div>`}
</pre>
<h2>Kategorie-Referenz</h2>
<table className="min-w-full">
<thead>
<tr>
<th>data-cookie-category</th>
<th>Dienste</th>
<th>Consent erforderlich</th>
</tr>
</thead>
<tbody>
<tr><td><code>necessary</code></td><td>Session, CSRF, CMP</td><td>Nein (immer aktiv)</td></tr>
<tr><td><code>statistics</code></td><td>GA, Matomo, Hotjar, Clarity</td><td>Ja</td></tr>
<tr><td><code>marketing</code></td><td>FB Pixel, Google Ads, LinkedIn, TikTok</td><td>Ja</td></tr>
<tr><td><code>functional</code></td><td>Chat, Maps, Spracheinstellungen</td><td>Ja</td></tr>
</tbody>
</table>
<h2>Programmatische Abfrage</h2>
<pre className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
{`// Pruefen ob Kategorie akzeptiert wurde
if (window.CookieConsent.hasConsent('statistics')) {
// Analytics initialisieren
}
// Auf Consent-Aenderung reagieren
window.addEventListener('cookieConsentUpdated', (e) => {
const consent = e.detail;
if (consent.marketing) {
// Marketing-Scripts aktivieren
}
});`}
</pre>
</div>
</main>
</div>
)
}
@@ -52,6 +52,17 @@ const navigation: NavItem[] = [
{ title: 'Flutter', href: '/sdk/consent/mobile/flutter' }, { title: 'Flutter', href: '/sdk/consent/mobile/flutter' },
], ],
}, },
{
title: 'Cookie-Banner',
href: '/sdk/consent/cookie-banner',
icon: <Shield className="w-4 h-4" />,
children: [
{ title: 'Integration Guide', href: '/sdk/consent/cookie-banner/integration' },
{ title: 'Google Consent Mode', href: '/sdk/consent/cookie-banner/google-consent-mode' },
{ title: 'Script Blocking', href: '/sdk/consent/cookie-banner/script-blocking' },
{ title: 'Compliance Checklist', href: '/sdk/consent/cookie-banner/checklist' },
],
},
{ {
title: 'Sicherheit', title: 'Sicherheit',
href: '/sdk/consent/security', href: '/sdk/consent/security',