From 018eacb0f308e93004b1ac629dc5de1a3d827b91 Mon Sep 17 00:00:00 2001 From: Deeman Date: Tue, 10 Mar 2026 16:14:58 +0100 Subject: [PATCH] feat(analytics): add Microsoft Clarity with consent-gated loading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .env.dev.sops | 5 +++-- .env.prod.sops | 6 ++++-- CHANGELOG.md | 1 + web/src/padelnomics/core.py | 1 + .../padelnomics/public/templates/privacy_de.html | 10 +++++++--- .../padelnomics/public/templates/privacy_en.html | 10 +++++++--- web/src/padelnomics/templates/_cookie_banner.html | 8 ++++++++ web/src/padelnomics/templates/base.html | 14 ++++++++++++++ 8 files changed, 45 insertions(+), 10 deletions(-) diff --git a/.env.dev.sops b/.env.dev.sops index c04c0b9..1506c99 100644 --- a/.env.dev.sops +++ b/.env.dev.sops @@ -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 diff --git a/.env.prod.sops b/.env.prod.sops index 280a097..cf3b22b 100644 --- a/.env.prod.sops +++ b/.env.prod.sops @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 61d6a4e..79d6ad7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/web/src/padelnomics/core.py b/web/src/padelnomics/core.py index 1baa9c2..8e813c4 100644 --- a/web/src/padelnomics/core.py +++ b/web/src/padelnomics/core.py @@ -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") diff --git a/web/src/padelnomics/public/templates/privacy_de.html b/web/src/padelnomics/public/templates/privacy_de.html index 85eacc2..1e1904e 100644 --- a/web/src/padelnomics/public/templates/privacy_de.html +++ b/web/src/padelnomics/public/templates/privacy_de.html @@ -3,14 +3,14 @@ {% block title %}Datenschutzerklärung - {{ config.APP_NAME }}{% endblock %} {% block head %} - + {% endblock %} {% block content %}

Datenschutzerklärung

-

Stand: Februar 2026 — Read in English

+

Stand: März 2026 — Read in English

@@ -36,6 +36,7 @@

Automatisch erhobene Daten:

  • Aggregierte, anonymisierte Seitenaufruf-Daten über Umami (keine IP-Speicherung, kein siteübergreifendes Tracking)
  • +
  • Anonymisierte Interaktionsdaten (Klicks, Scrolltiefe, Sitzungsaufzeichnungen) über Microsoft Clarity — nur mit Ihrer Einwilligung
  • Session-Cookie zur Aufrechterhaltung der Anmeldung

Beim Checkout erhobene Daten (durch Paddle, unseren Zahlungsdienstleister):

@@ -60,6 +61,7 @@
  • Umami (selbst gehostet auf unserer eigenen Infrastruktur) — cookielose, datenschutzfreundliche Webanalyse. Keine Übermittlung personenbezogener Daten an Dritte.
  • Paddle (paddle.com, UK/USA) — Zahlungsabwicklung und Abonnementverwaltung. Paddle agiert als Merchant of Record. Siehe Datenschutzerklärung von Paddle.
  • Resend (resend.com, USA) — Versand transaktionaler E-Mails (Magic Links, Belege). Die Übermittlung erfolgt auf Basis von Standardvertragsklauseln (SCC) der Europäischen Kommission. Siehe Datenschutzerklärung von Resend.
  • +
  • Microsoft Clarity (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 Datenschutzerklärung von Microsoft.
  • @@ -87,6 +89,8 @@

    Funktional (erfordert Einwilligung)

    • ab_* — 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.
    • +
    • _clck — Microsoft Clarity Nutzerkennung. Gültig 12 Monate. Wird nur gesetzt, wenn Sie funktionalen Cookies zugestimmt haben.
    • +
    • _clsk — Microsoft Clarity Sitzungskennung. Gültig bis zum Ende der Sitzung. Wird nur gesetzt, wenn Sie funktionalen Cookies zugestimmt haben.

    Zahlung (nur beim Checkout)

    @@ -115,7 +119,7 @@

    8. Internationale Datenübermittlung

    -

    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.

    +

    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.

    diff --git a/web/src/padelnomics/public/templates/privacy_en.html b/web/src/padelnomics/public/templates/privacy_en.html index ba03d94..bea944d 100644 --- a/web/src/padelnomics/public/templates/privacy_en.html +++ b/web/src/padelnomics/public/templates/privacy_en.html @@ -3,14 +3,14 @@ {% block title %}Privacy Policy - {{ config.APP_NAME }}{% endblock %} {% block head %} - + {% endblock %} {% block content %}

    Privacy Policy

    -

    Last updated: February 2026 — Datenschutzerklärung auf Deutsch

    +

    Last updated: March 2026 — Datenschutzerklärung auf Deutsch

    @@ -36,6 +36,7 @@

    Data collected automatically:

    • Aggregated, anonymised page-view data via Umami (no IP address stored, no cross-site tracking)
    • +
    • Anonymised interaction data (clicks, scroll depth, session recordings) via Microsoft Clarity — only with your consent
    • Session cookie to keep you signed in

    Data collected at checkout (by Paddle, our payment processor):

    @@ -60,6 +61,7 @@
  • Umami (self-hosted on our own infrastructure) — cookieless, privacy-first analytics. No personal data transferred to third parties.
  • Paddle (paddle.com, UK/USA) — payment processing and subscription management. Paddle acts as merchant of record. See Paddle's Privacy Policy.
  • Resend (resend.com, USA) — transactional email delivery (magic links, receipts). Data is transferred under Standard Contractual Clauses. See Resend's Privacy Policy.
  • +
  • Microsoft Clarity (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 Microsoft Privacy Statement.
  • @@ -87,6 +89,8 @@

    Functional (require consent)

    • ab_* — 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.
    • +
    • _clck — Microsoft Clarity user identifier. Expires after 12 months. Only set if you accept functional cookies.
    • +
    • _clsk — Microsoft Clarity session identifier. Expires at end of session. Only set if you accept functional cookies.

    Payment (checkout only)

    @@ -115,7 +119,7 @@

    8. International Transfers

    -

    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.

    +

    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.

    diff --git a/web/src/padelnomics/templates/_cookie_banner.html b/web/src/padelnomics/templates/_cookie_banner.html index d24677a..35e4ab0 100644 --- a/web/src/padelnomics/templates/_cookie_banner.html +++ b/web/src/padelnomics/templates/_cookie_banner.html @@ -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); diff --git a/web/src/padelnomics/templates/base.html b/web/src/padelnomics/templates/base.html index 8e5905c..0df2a50 100644 --- a/web/src/padelnomics/templates/base.html +++ b/web/src/padelnomics/templates/base.html @@ -18,6 +18,20 @@ + + {% if config.CLARITY_PROJECT_ID %} + + {% endif %} + {% block paddle %}{% endblock %}