feat(analytics): add Microsoft Clarity with consent-gated loading
Some checks failed
CI / tag (push) Has been cancelled
CI / test (push) Has been cancelled

Gate Clarity behind functional cookie consent (TTDSG § 25 + GDPR).
Script loads on page if consent already given, bootstraps immediately
on banner accept without reload. Privacy policy (EN + DE) updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-03-10 16:14:58 +01:00
parent abacaac3f5
commit 018eacb0f3
8 changed files with 45 additions and 10 deletions

View File

@@ -74,12 +74,13 @@ GSC_SERVICE_ACCOUNT_PATH=
GSC_SITE_URL=
BING_WEBMASTER_API_KEY=
BING_SITE_URL=
CLARITY_PROJECT_ID=ENC[AES256_GCM,data:PQ==,iv:GqQLR3UERBEGtqpZXAkZ8ETyVdj7+pk4YwuBPVxcjyE=,tag:1uuH2Gw3zE78Pugy6i6eDg==,type:str]
#ENC[AES256_GCM,data:ECsuDMQipS6YmFpSm1vqCsR2fUW2zN1Mg9VcUlw0roM=,iv:j+F6Akx2bklGMkFTux230YcZjMibA+Qp+qvgkGXl4Jw=,tag:7aO0wbmP/qB73wLgtiSJ2w==,type:comment]
GEONAMES_USERNAME=ENC[AES256_GCM,data:aSkVdLNrhiF6tlg=,iv:eemFGwDIv3EG/P3lVHGZj96MieIsr85e4xYmEIpZyfM=,tag:McpZMNOIO3FDkSebae2gOQ==,type:str]
CENSUS_API_KEY=ENC[AES256_GCM,data:qqG971573aGq9MiHI2xLlanKKFwjfcNNoMXtm8LNbyh0rMbQN2XukQ==,iv:az2i0ldH75nHGah4DeOxaXmDbVYqmC1c77ptZqFA9BI=,tag:zoDdKj9bR7fgIDo1/dEU2g==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxNWNmUzVNUGdWRnE0ZFpF\nM0JQZWZ3UDdEVzlwTmIxakxOZXBkT2x2ZlNrClRtV2M3S2daSGxUZmFDSWQ2Nmh4\neU51QndFcUxlSE00RFovOVJTcDZmUUUKLS0tIDcvL3hRMDRoMWZZSXljNzA3WG5o\nMWFic21MV0krMzlIaldBTVU0ZDdlTE0K7euGQtA+9lHNws+x7TMCArZamm9att96\nL8cXoUDWe5fNI5+M1bXReqVfNwPTwZsV6j/+ZtYKybklIzWz02Ex4A==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age1f5002gj4s78jju45jd28kuejtcfhn5cdujz885fl7z2p9ym68pnsgky87a
sops_lastmodified=2026-03-03T15:16:35Z
sops_mac=ENC[AES256_GCM,data:T0qph3KPd68Lo4hxd6ECP+wv87uwRFsAFZwnVyf/MXvuG7raraUW02RLox0xklVcKBJXk+9jM7ycQ1nuk95UIuu7uRU88g11RaAm67XaOsafgwDMrC17AjIlg0Vf0w64WAJBrQLaXhJlh/Gz45bXlz82F+XVnTW8fGCpHRZooMY=,iv:cDgMZX6FRVe9JqQXLN6OhO06Ysfg2AKP2hG0B/GeajU=,tag:vHavf9Hw2xqJrqM3vVUTjA==,type:str]
sops_lastmodified=2026-03-10T15:07:09Z
sops_mac=ENC[AES256_GCM,data:mYPhIGSZIN+nqFEQE5VmLGaoTOvxFQ7fXvOHWcYtjr+AL/Zmnt81bo8Icgja5IMQPplSWoBo4J/7N08kSHATuBDuvCxNrsJaqTzCriTwfXq0WFa5yvoce/Sd29JEDAN505L+mR1PovhfIPndTR/E1bLvcyTz2NuAq5VGSg6KcUU=,iv:6mrSOWqIOItVt7Dp6jNecvzLjaTw/qQMr5b28I/bZWU=,tag:bT6Zi0Mb5Ci0CZBqr9iB3g==,type:str]
sops_unencrypted_suffix=_unencrypted
sops_version=3.12.1

View File

@@ -50,6 +50,8 @@ GSC_SERVICE_ACCOUNT_PATH=ENC[AES256_GCM,data:Vki6yHk+gd4n,iv:rxzKvwrGnAkLcpS41EZ
GSC_SITE_URL=ENC[AES256_GCM,data:K0i1xRym+laMP6kgOMEfUyoAn2eNgQ==,iv:kyb+grzFq1e5CG/0NJRO3LkSXexOuCK07uJYApAdWsA=,tag:faljHqYjGTgrR/Zbh27/Yw==,type:str]
BING_WEBMASTER_API_KEY=ENC[AES256_GCM,data:kSQxJOpsYCuJ,iv:Kc4jJpOd64PATeBjidNHTwBr/bNnCeqsTrUqAAYM5Vs=,tag:4jBxqgpyomzMLwiC9XpfVQ==,type:str]
BING_SITE_URL=ENC[AES256_GCM,data:M33VI97DyxH8gRR3ZUXoXg4QrEv5og==,iv:GxZtwfbBVihUbp6XNQKzAalhO1GfQF1l1j1MeEIBCFQ=,tag:9njlBp4v684PeFl3HebyIg==,type:str]
INDEXNOW_KEY=ENC[AES256_GCM,data:3AJnmPOQJoKw525QR7jx6QBzV9kznUsWqHRmQjv1cU8=,iv:4XRmcPKrFE8S3GzsfNbxUdaUNaKc6z9T+ihUUwjZ8Y0=,tag:wERrhY9whJ9yTEgt8ewaMQ==,type:str]
CLARITY_PROJECT_ID=ENC[AES256_GCM,data:mLQ4vvtDFpZOpCg=,iv:S58K5Qf32EFlAuh8xkjo603wVpCOhNodZLJ4ZyaGF6c=,tag:LUQor6rIRc7unYCyytSgSg==,type:str]
#ENC[AES256_GCM,data:OTUMKNkRW0zrupNppXthwE1oieILhNjM+cjx5hFn69g=,iv:48ID2qtSe9ggD2X+G/iUqp3v2uwEc7fZw8lxHIvVXmk=,tag:okBn0Npk1K9dDOFWA/AB1A==,type:comment]
GEONAMES_USERNAME=ENC[AES256_GCM,data:UXd/S2TzXPiGmLY=,iv:OMURM5E6SFEsaqroUlH76DEnr7C/ujNk9UQnbWT0hK4=,tag:VsjjS12QDbudiEhdAQ/OCQ==,type:str]
CENSUS_API_KEY=ENC[AES256_GCM,data:9RbKlxSD17LqIuuNXaOKSgZ8LnFh9Wbze3XHgpctfV/1TqBMZTIedQ==,iv:WwsmR3HLUEcgUpLliGRaUPhGM9vFNPMGXSAQQ6+9UVc=,tag:R4EMNy5MxxvK0UTaCL0umA==,type:str]
@@ -64,7 +66,7 @@ sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb2
sops_age__list_1__map_recipient=age1wjepykv3glvsrtegu25tevg7vyn3ngpl607u3yjc9ucay04s045s796msw
sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFeHhaOURNZnRVMEwxNThu\nUjF4Q0kwUXhTUE1QSzZJbmpubnh3RnpQTmdvCjRmWWxpNkxFUmVGb3NRbnlydW5O\nWEg3ZXJQTU4vcndzS2pUQXY3Q0ttYjAKLS0tIE9IRFJ1c2ZxbGVHa2xTL0swbGN1\nTzgwMThPUDRFTWhuZHJjZUYxOTZrU00KY62qrNBCUQYxwcLMXFEnLkwncxq3BPJB\nKm4NzeHBU87XmPWVrgrKuf+PH1mxJlBsl7Hev8xBTy7l6feiZjLIvQ==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_2__map_recipient=age1c783ym2q5x9tv7py5d28uc4k44aguudjn03g97l9nzs00dd9tsrqum8h4d
sops_lastmodified=2026-03-05T15:55:19Z
sops_mac=ENC[AES256_GCM,data:orLypjurBTYmk3um0bDQV3wFxj1pjCsjOf2D+AZyoIYY88MeY8BjK8mg8BWhmJYlGWqHH1FCpoJS+2SECv2Bvgejqvx/C/HSysA8et5CArM/p/MBbcupLAKOD8bTXorKMRDYPkWpK/snkPToxIZZd7dNj/zSU+OhRp5qLGCHkvM=,iv:eBn93z4DSk8UPHgP/Jf/Kz+3KwoKIQ9Et72pbLFcLP8=,tag:79kzPIKp0rtHGhH1CkXqwg==,type:str]
sops_lastmodified=2026-03-10T15:05:54Z
sops_mac=ENC[AES256_GCM,data:85sRBn6/gjXZFgyZlFk2RyMQGYK/e6rVC879F7/APj0xeguY5q4ui4OaE7OpO+joRMoLbE+rCWjYEyTeToTTdCNJ30yLiwlTrKR+tnmegJ/8wUAdyJtI8KO6XxKZpAesbiKl+o4F38iBZMhuZ6iybQx1RGF8SzQRu+E3fUEIiKk=,iv:/uW53lMRNNk/a/bzPvWqwDzP0un5/1muBDvDLSRet58=,tag:56LC972g6HSipvwxfSepJg==,type:str]
sops_unencrypted_suffix=_unencrypted
sops_version=3.12.1

View File

@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
### Added
- **Microsoft Clarity integration** — consent-gated heatmaps and session recordings (project ID via `CLARITY_PROJECT_ID` env var). Script only loads when the user has accepted functional cookies; bootstraps immediately on consent without requiring a reload. Privacy policy (EN + DE) updated with Clarity disclosure: data collection, sub-processor, cookies (`_clck`, `_clsk`), and international transfers.
- **IndexNow integration** — push-notify Bing, Yandex, Seznam, and Naver when articles are published/unpublished/edited or suppliers are created. Bulk operations batch all URLs into a single request. Skips silently in dev (no key configured). Serves key verification file at `/{key}.txt`.
### Fixed

View File

@@ -73,6 +73,7 @@ class Config:
BING_WEBMASTER_API_KEY: str = os.getenv("BING_WEBMASTER_API_KEY", "")
BING_SITE_URL: str = os.getenv("BING_SITE_URL", "")
INDEXNOW_KEY: str = os.getenv("INDEXNOW_KEY", "")
CLARITY_PROJECT_ID: str = os.getenv("CLARITY_PROJECT_ID", "")
RESEND_API_KEY: str = os.getenv("RESEND_API_KEY", "")
EMAIL_FROM: str = _env("EMAIL_FROM", "hello@padelnomics.io")

View File

@@ -3,14 +3,14 @@
{% block title %}Datenschutzerklärung - {{ config.APP_NAME }}{% endblock %}
{% block head %}
<meta name="description" content="Datenschutzerklärung für Padelnomics. DSGVO-konform. Erfahren Sie, wie wir Ihre Daten erheben, verwenden und schützen. Wir nutzen Umami (cookielose Analyse), Paddle (Zahlungen) und Resend (E-Mail). Wir verkaufen Ihre Daten niemals.">
<meta name="description" content="Datenschutzerklärung für Padelnomics. DSGVO-konform. Erfahren Sie, wie wir Ihre Daten erheben, verwenden und schützen. Wir nutzen Umami (cookielose Analyse), Microsoft Clarity (Sitzungsaufzeichnungen, nur mit Einwilligung), Paddle (Zahlungen) und Resend (E-Mail). Wir verkaufen Ihre Daten niemals.">
{% endblock %}
{% block content %}
<main class="container-page py-12">
<div class="card max-w-3xl mx-auto">
<h1 class="text-2xl mb-1">Datenschutzerklärung</h1>
<p class="text-sm text-slate mb-8">Stand: Februar 2026 &mdash; <a href="{{ url_for('public.privacy', lang='en') }}" style="text-decoration:underline">Read in English</a></p>
<p class="text-sm text-slate mb-8">Stand: März 2026 &mdash; <a href="{{ url_for('public.privacy', lang='en') }}" style="text-decoration:underline">Read in English</a></p>
<div class="space-y-6 text-slate-dark leading-relaxed">
@@ -36,6 +36,7 @@
<p class="mt-3"><strong>Automatisch erhobene Daten:</strong></p>
<ul class="list-disc pl-6 mt-2 space-y-1">
<li>Aggregierte, anonymisierte Seitenaufruf-Daten über Umami (keine IP-Speicherung, kein siteübergreifendes Tracking)</li>
<li>Anonymisierte Interaktionsdaten (Klicks, Scrolltiefe, Sitzungsaufzeichnungen) über Microsoft Clarity — nur mit Ihrer Einwilligung</li>
<li>Session-Cookie zur Aufrechterhaltung der Anmeldung</li>
</ul>
<p class="mt-3"><strong>Beim Checkout erhobene Daten (durch Paddle, unseren Zahlungsdienstleister):</strong></p>
@@ -60,6 +61,7 @@
<li><strong>Umami</strong> (selbst gehostet auf unserer eigenen Infrastruktur) — cookielose, datenschutzfreundliche Webanalyse. Keine Übermittlung personenbezogener Daten an Dritte.</li>
<li><strong>Paddle</strong> (paddle.com, UK/USA) — Zahlungsabwicklung und Abonnementverwaltung. Paddle agiert als Merchant of Record. Siehe <a href="https://www.paddle.com/legal/privacy" target="_blank" rel="noopener" style="text-decoration:underline">Datenschutzerklärung von Paddle</a>.</li>
<li><strong>Resend</strong> (resend.com, USA) — Versand transaktionaler E-Mails (Magic Links, Belege). Die Übermittlung erfolgt auf Basis von Standardvertragsklauseln (SCC) der Europäischen Kommission. Siehe <a href="https://resend.com/legal/privacy-policy" target="_blank" rel="noopener" style="text-decoration:underline">Datenschutzerklärung von Resend</a>.</li>
<li><strong>Microsoft Clarity</strong> (clarity.microsoft.com, USA) — Heatmaps und Sitzungsaufzeichnungen zur Verbesserung der Nutzererfahrung. Nur aktiv mit Ihrer Einwilligung (funktionale Cookies). Die Übermittlung erfolgt auf Basis von Standardvertragsklauseln (SCC). Siehe <a href="https://privacy.microsoft.com/privacystatement" target="_blank" rel="noopener" style="text-decoration:underline">Datenschutzerklärung von Microsoft</a>.</li>
</ul>
</section>
@@ -87,6 +89,8 @@
<p class="mt-3 font-semibold text-sm">Funktional (erfordert Einwilligung)</p>
<ul class="list-disc pl-6 mt-1 space-y-1">
<li><strong>ab_*</strong> — Weist Ihnen eine A/B-Testvariante zu, um unsere Website zu verbessern. Läuft nach 30 Tagen ab. Wird nur gesetzt, wenn Sie funktionalen Cookies zugestimmt haben.</li>
<li><strong>_clck</strong> — Microsoft Clarity Nutzerkennung. Gültig 12 Monate. Wird nur gesetzt, wenn Sie funktionalen Cookies zugestimmt haben.</li>
<li><strong>_clsk</strong> — Microsoft Clarity Sitzungskennung. Gültig bis zum Ende der Sitzung. Wird nur gesetzt, wenn Sie funktionalen Cookies zugestimmt haben.</li>
</ul>
<p class="mt-3 font-semibold text-sm">Zahlung (nur beim Checkout)</p>
@@ -115,7 +119,7 @@
<section>
<h2 class="text-lg mb-2">8. Internationale Datenübermittlung</h2>
<p>Resend verarbeitet Daten in den USA. Die Übermittlung erfolgt auf Basis von Standardvertragsklauseln (SCC) der Europäischen Kommission. Paddle unterliegt dem UK-DSGVO mit Angemessenheitsbeschluss. Umami läuft auf unserer eigenen EU-Infrastruktur — keine Daten verlassen die EU.</p>
<p>Resend und Microsoft Clarity verarbeiten Daten in den USA. Die Übermittlung erfolgt auf Basis von Standardvertragsklauseln (SCC) der Europäischen Kommission. Paddle unterliegt dem UK-DSGVO mit Angemessenheitsbeschluss. Umami läuft auf unserer eigenen EU-Infrastruktur — keine Daten verlassen die EU.</p>
</section>
<section>

View File

@@ -3,14 +3,14 @@
{% block title %}Privacy Policy - {{ config.APP_NAME }}{% endblock %}
{% block head %}
<meta name="description" content="Privacy Policy for Padelnomics. GDPR compliant. Learn how we collect, use, and protect your data. We use Umami (cookieless analytics), Paddle (payments), and Resend (email). We never sell your personal information.">
<meta name="description" content="Privacy Policy for Padelnomics. GDPR compliant. Learn how we collect, use, and protect your data. We use Umami (cookieless analytics), Microsoft Clarity (session recordings, with consent), Paddle (payments), and Resend (email). We never sell your personal information.">
{% endblock %}
{% block content %}
<main class="container-page py-12">
<div class="card max-w-3xl mx-auto">
<h1 class="text-2xl mb-1">Privacy Policy</h1>
<p class="text-sm text-slate mb-8">Last updated: February 2026 &mdash; <a href="{{ url_for('public.privacy', lang='de') }}" style="text-decoration:underline">Datenschutzerklärung auf Deutsch</a></p>
<p class="text-sm text-slate mb-8">Last updated: March 2026 &mdash; <a href="{{ url_for('public.privacy', lang='de') }}" style="text-decoration:underline">Datenschutzerklärung auf Deutsch</a></p>
<div class="space-y-6 text-slate-dark leading-relaxed">
@@ -36,6 +36,7 @@
<p class="mt-3"><strong>Data collected automatically:</strong></p>
<ul class="list-disc pl-6 mt-2 space-y-1">
<li>Aggregated, anonymised page-view data via Umami (no IP address stored, no cross-site tracking)</li>
<li>Anonymised interaction data (clicks, scroll depth, session recordings) via Microsoft Clarity — only with your consent</li>
<li>Session cookie to keep you signed in</li>
</ul>
<p class="mt-3"><strong>Data collected at checkout (by Paddle, our payment processor):</strong></p>
@@ -60,6 +61,7 @@
<li><strong>Umami</strong> (self-hosted on our own infrastructure) — cookieless, privacy-first analytics. No personal data transferred to third parties.</li>
<li><strong>Paddle</strong> (paddle.com, UK/USA) — payment processing and subscription management. Paddle acts as merchant of record. See <a href="https://www.paddle.com/legal/privacy" target="_blank" rel="noopener" style="text-decoration:underline">Paddle's Privacy Policy</a>.</li>
<li><strong>Resend</strong> (resend.com, USA) — transactional email delivery (magic links, receipts). Data is transferred under Standard Contractual Clauses. See <a href="https://resend.com/legal/privacy-policy" target="_blank" rel="noopener" style="text-decoration:underline">Resend's Privacy Policy</a>.</li>
<li><strong>Microsoft Clarity</strong> (clarity.microsoft.com, USA) — heatmaps and session recordings for UX improvement. Only active with your consent (functional cookies). Data is transferred under Standard Contractual Clauses. See <a href="https://privacy.microsoft.com/privacystatement" target="_blank" rel="noopener" style="text-decoration:underline">Microsoft Privacy Statement</a>.</li>
</ul>
</section>
@@ -87,6 +89,8 @@
<p class="mt-3 font-semibold text-sm">Functional (require consent)</p>
<ul class="list-disc pl-6 mt-1 space-y-1">
<li><strong>ab_*</strong> — Assigns you to an A/B test variant to help us improve the site. Expires after 30 days. Only set if you accept functional cookies.</li>
<li><strong>_clck</strong> — Microsoft Clarity user identifier. Expires after 12 months. Only set if you accept functional cookies.</li>
<li><strong>_clsk</strong> — Microsoft Clarity session identifier. Expires at end of session. Only set if you accept functional cookies.</li>
</ul>
<p class="mt-3 font-semibold text-sm">Payment (checkout only)</p>
@@ -115,7 +119,7 @@
<section>
<h2 class="text-lg mb-2">8. International Transfers</h2>
<p>Resend processes data in the USA. Transfers are protected by Standard Contractual Clauses (SCCs) approved by the European Commission. Paddle operates under UK GDPR with an adequacy finding. Umami runs on our own EU-based infrastructure — no data leaves the EU.</p>
<p>Resend and Microsoft Clarity process data in the USA. Transfers are protected by Standard Contractual Clauses (SCCs) approved by the European Commission. Paddle operates under UK GDPR with an adequacy finding. Umami runs on our own EU-based infrastructure — no data leaves the EU.</p>
</section>
<section>

View File

@@ -151,6 +151,14 @@
function dismiss(value) {
document.cookie = COOKIE_NAME + '=' + value
+ ';path=/;max-age=' + MAX_AGE + ';SameSite=Lax';
// Bootstrap Clarity immediately on functional consent (no reload needed)
if (value.indexOf('functional') !== -1 && window.clarity === undefined) {
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "{{ config.CLARITY_PROJECT_ID }}");
}
banner.classList.remove('cb-enter');
banner.classList.add('cb-exit');
setTimeout(function () { banner.style.display = 'none'; }, 280);

View File

@@ -18,6 +18,20 @@
<!-- Umami Analytics -->
<script defer src="https://umami.padelnomics.io/Z.js" data-website-id="4474414b-58d6-4c6e-89a1-df5ea1f49d70"{% if ab_tag %} data-tag="{{ ab_tag }}"{% endif %}></script>
<!-- Microsoft Clarity (consent-gated) -->
{% if config.CLARITY_PROJECT_ID %}
<script type="text/javascript">
(function(){
if (!/cookie_consent=[^;]*functional/.test(document.cookie)) return;
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "{{ config.CLARITY_PROJECT_ID }}");
})();
</script>
{% endif %}
<!-- Paddle.js (only on checkout pages via block override) -->
{% block paddle %}{% endblock %}