Skip to content
9 changes: 9 additions & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ enable_pirsch_analytics: false # enables Pirsch analytics (https://pirsch.io/)
enable_openpanel_analytics: false # enables Openpanel analytics (https://openpanel.dev/)
enable_google_verification: false # enables google site verification
enable_bing_verification: false # enables bing site verification
enable_cookie_consent: false # enables GDPR-compliant cookie consent dialog (https://github.com/orestbida/cookieconsent)
enable_masonry: true # enables automatic project cards arrangement
enable_math: true # enables math typesetting (uses MathJax)
enable_tooltips: false # enables automatic tooltip links generated for each section titles on pages and posts
Expand Down Expand Up @@ -609,6 +610,14 @@ third_party_libraries:
url:
js: "https://cdn.jsdelivr.net/npm/[email protected]/swiper-element-bundle.min.js.map"
version: "11.0.5"
vanilla-cookieconsent:
integrity:
css: "sha256-ygRrixsQlBByBZiOcJamh7JByO9fP+/l5UPtKNJmRsE="
js: "sha256-vG4vLmOB/AJbJ6awr7Wg4fxonG+fxAp4cIrbIFTvRXU="
url:
css: "https://cdn.jsdelivr.net/npm/vanilla-cookieconsent@{{version}}/dist/cookieconsent.css"
js: "https://cdn.jsdelivr.net/npm/vanilla-cookieconsent@{{version}}/dist/cookieconsent.umd.js"
version: "3.1.0"
vega:
integrity:
js: "sha256-Yot/cfgMMMpFwkp/5azR20Tfkt24PFqQ6IQS+80HIZs="
Expand Down
63 changes: 57 additions & 6 deletions _includes/distill_scripts.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -189,29 +189,80 @@
<!-- Removed Pseudocode -->
{% endif %}

{% if site.enable_cookie_consent %}
<!-- Cookie Consent -->
<script
defer
src="{{ site.third_party_libraries.vanilla-cookieconsent.url.js }}"
integrity="{{ site.third_party_libraries.vanilla-cookieconsent.integrity.js }}"
crossorigin="anonymous"
></script>
<script defer src="{{ '/assets/js/cookie-consent-setup.js' | relative_url }}"></script>
{% endif %}

{% if site.enable_google_analytics %}
<!-- Analytics -->
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}"></script>
<script defer src="{{ '/assets/js/google-analytics-setup.js' | relative_url }}"></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
async
src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}"
></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
defer
src="{{ '/assets/js/google-analytics-setup.js' | relative_url }}"
></script>
{% endif %}

{% if site.enable_cronitor_analytics %}
<!-- Cronitor RUM -->
<script async src="https://rum.cronitor.io/script.js"></script>
<script defer src="{{ '/assets/js/cronitor-analytics-setup.js' | relative_url }}"></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
async
src="https://rum.cronitor.io/script.js"
></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
defer
src="{{ '/assets/js/cronitor-analytics-setup.js' | relative_url }}"
></script>
{% endif %}
{% if site.enable_pirsch_analytics %}
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
defer
src="https://api.pirsch.io/pa.js"
id="pianjs"
data-code="{{ site.pirsch_analytics }}"
></script>
{% endif %}
{% if site.enable_openpanel_analytics %}
<script defer src="{{ '/assets/js/open-panel-analytics-setup.js' | relative_url }}"></script>
<script async defer src="https://openpanel.dev/op1.js"></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
defer
src="{{ '/assets/js/open-panel-analytics-setup.js' | relative_url }}"
></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
async
defer
src="https://openpanel.dev/op1.js"
></script>
{% endif %}

{% if site.enable_progressbar %}
Expand Down
11 changes: 11 additions & 0 deletions _includes/head.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,14 @@
{% if page.tikzjax %}
<link defer rel="stylesheet" type="text/css" href="{{ '/assets/css/tikzjax.min.css' | relative_url | bust_file_cache }}">
{% endif %}

{% if site.enable_cookie_consent %}
<!-- Cookie Consent -->
<link
defer
rel="stylesheet"
href="{{ site.third_party_libraries.vanilla-cookieconsent.url.css }}"
integrity="{{ site.third_party_libraries.vanilla-cookieconsent.integrity.css }}"
crossorigin="anonymous"
>
{% endif %}
63 changes: 57 additions & 6 deletions _includes/scripts.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -223,29 +223,80 @@
{% endunless %}
{% endif %}

{% if site.enable_cookie_consent %}
<!-- Cookie Consent -->
<script
defer
src="{{ site.third_party_libraries.vanilla-cookieconsent.url.js }}"
integrity="{{ site.third_party_libraries.vanilla-cookieconsent.integrity.js }}"
crossorigin="anonymous"
></script>
<script defer src="{{ '/assets/js/cookie-consent-setup.js' | relative_url }}"></script>
{% endif %}

{% if site.enable_google_analytics %}
<!-- Analytics -->
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}"></script>
<script defer src="{{ '/assets/js/google-analytics-setup.js' | relative_url }}"></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
async
src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}"
></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
defer
src="{{ '/assets/js/google-analytics-setup.js' | relative_url }}"
></script>
{% endif %}

{% if site.enable_cronitor_analytics %}
<!-- Cronitor RUM -->
<script async src="https://rum.cronitor.io/script.js"></script>
<script defer src="{{ '/assets/js/cronitor-analytics-setup.js' | relative_url }}"></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
async
src="https://rum.cronitor.io/script.js"
></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
defer
src="{{ '/assets/js/cronitor-analytics-setup.js' | relative_url }}"
></script>
{% endif %}
{% if site.enable_pirsch_analytics %}
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
defer
src="https://api.pirsch.io/pa.js"
id="pianjs"
data-code="{{ site.pirsch_analytics }}"
></script>
{% endif %}
{% if site.enable_openpanel_analytics %}
<script defer src="{{ '/assets/js/open-panel-analytics-setup.js' | relative_url }}"></script>
<script async defer src="https://openpanel.dev/op1.js"></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
defer
src="{{ '/assets/js/open-panel-analytics-setup.js' | relative_url }}"
></script>
<script
{% if site.enable_cookie_consent %}
type="text/plain" data-category="analytics"
{% endif %}
async
defer
src="https://openpanel.dev/op1.js"
></script>
{% endif %}

{% if site.enable_progressbar %}
Expand Down
145 changes: 145 additions & 0 deletions _scripts/cookie-consent-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
permalink: /assets/js/cookie-consent-setup.js
---
/**
* Cookie Consent Configuration
* Documentation: https://cookieconsent.orestbida.com/
*
* GDPR-Compliant Approach:
* - Analytics scripts use type="text/plain" data-category="analytics"
* - The library blocks all marked scripts until user consents
* - Scripts NEVER run until explicit consent is given
* - Google Consent Mode is used for Google Analytics privacy mode before consent
* - Other analytics (Cronitor, Pirsch, OpenPanel) are blocked until consent given
*
* Supported Analytics Providers:
* - Cronitor RUM
* - Google Analytics (GA4)
* - OpenPanel Analytics
* - Pirsch Analytics
*/

// Initialize Google Consent Mode BEFORE any tracking
// This tells Google services to operate in privacy mode until user consents
window.dataLayer = window.dataLayer || [];
function gtag() {
window.dataLayer.push(arguments);
}
gtag('consent', 'default', {
'ad_storage': 'denied',
'analytics_storage': 'denied',
'functionality_storage': 'denied',
'personalization_storage': 'denied'
});

// Wait for the library to be available
function initializeCookieConsent() {
// Check if CookieConsent is available
if (!window.CookieConsent) {
// Library not yet loaded, try again after a short delay
setTimeout(initializeCookieConsent, 100);
return;
}

window.CookieConsent.run({
categories: {
necessary: {
enabled: true,
readOnly: true
},
analytics: {}
},

language: {
default: 'en',
translations: {
en: {
consentModal: {
title: 'We use cookies',
description: 'This website uses cookies to improve your experience and analyze site traffic. By clicking "Accept all", you consent to our use of cookies.',
acceptAllBtn: 'Accept all',
acceptNecessaryBtn: 'Reject all',
showPreferencesBtn: 'Manage Individual preferences'
},
preferencesModal: {
title: 'Manage cookie preferences',
acceptAllBtn: 'Accept all',
acceptNecessaryBtn: 'Reject all',
savePreferencesBtn: 'Accept current selection',
closeIconLabel: 'Close modal',
sections: [
{
title: 'Cookie usage',
description: 'We use cookies to ensure the basic functionalities of the website and to enhance your online experience. You can choose for each category to opt-in/out whenever you want.'
},
{
title: 'Strictly Necessary cookies',
description: 'These cookies are essential for the proper functioning of the website. Without these cookies, the website would not work properly.',
linkedCategory: 'necessary'
},
{
title: 'Analytics cookies',
description: 'These cookies allow us to measure traffic and analyze your behavior to improve our service.',
linkedCategory: 'analytics'
},
{
title: 'More information',
description: 'For any queries in relation to our policy on cookies and your choices, please <a class="cc-link" href="{{ site.url }}{{ site.baseurl }}/#contact">contact us</a>.'
}
]
}
}
}
},

// Callback when user accepts/rejects consent
onFirstConsent: function(consentData) {
updateConsentMode(consentData);
},

// Callback when user changes preferences
onChange: function(consentData) {
updateConsentMode(consentData);
}
});

/**
* Update Google Consent Mode based on user preferences
* This ensures Google services respect user choices
*/
function updateConsentMode(consentData) {
// Handle both callback data structures
var categories = consentData.categories || consentData;

// Ensure categories is an object
if (!categories || typeof categories !== 'object') {
console.warn('Invalid consent data structure:', consentData);
return;
}

gtag('consent', 'update', {
'analytics_storage': categories.analytics ? 'granted' : 'denied',
'ad_storage': 'denied',
'functionality_storage': 'denied',
'personalization_storage': 'denied'
});

if (categories.analytics) {
console.debug('✓ Analytics consent granted - tracking enabled for all providers');
// Analytics scripts with data-category="analytics" will automatically run
// when the library re-evaluates them after this consent update
} else {
console.debug('✗ Analytics consent denied - no tracking data collected');
// Analytics scripts are already blocked by the library (type="text/plain")
// No tracking will occur for:
// - Cronitor RUM
// - Google Analytics (GA4)
// - OpenPanel Analytics
// - Pirsch Analytics
}
}
}

// Initialize when the library is available
initializeCookieConsent();

13 changes: 13 additions & 0 deletions assets/js/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ let applyTheme = () => {
setHighlight(theme);
setGiscusTheme(theme);
setSearchTheme(theme);
setCookieConsentTheme(theme);
updateCalendarUrl();

// if mermaid is not defined, do nothing
Expand Down Expand Up @@ -245,6 +246,18 @@ let setSearchTheme = (theme) => {
}
};

let setCookieConsentTheme = (theme) => {
// Sync cookie consent modal with site's theme
// The cookie consent library supports dark mode via the cc--darkmode class
var htmlElement = document.documentElement;

if (theme === "dark") {
htmlElement.classList.add("cc--darkmode");
} else {
htmlElement.classList.remove("cc--darkmode");
}
};

let transTheme = () => {
document.documentElement.classList.add("transition");
window.setTimeout(() => {
Expand Down
Loading