diff --git a/CUSTOMIZE.md b/CUSTOMIZE.md index 6184890..dfb6e2b 100644 --- a/CUSTOMIZE.md +++ b/CUSTOMIZE.md @@ -54,6 +54,15 @@ Here we will give you some tips on how to customize the website. One important t - [Adding social media information](#adding-social-media-information) - [Adding a newsletter](#adding-a-newsletter) - [Configuring search features](#configuring-search-features) + - [Social media previews](#social-media-previews) + - [How to enable](#how-to-enable) + - [Configuring preview images](#configuring-preview-images) + - [Preview image best practices](#preview-image-best-practices) + - [Related posts](#related-posts) + - [How it works](#how-it-works) + - [Configuration](#configuration-1) + - [Disable related posts for a specific post](#disable-related-posts-for-a-specific-post) + - [Additional configuration in \_config.yml](#additional-configuration-in-_configyml) - [Managing publication display](#managing-publication-display) - [Adding a Google Calendar](#adding-a-google-calendar) - [Basic usage](#basic-usage) @@ -73,6 +82,14 @@ Here we will give you some tips on how to customize the website. One important t - [Scheduled Posts](#scheduled-posts) - [Name Format](#name-format) - [Important Notes](#important-notes) + - [GDPR Cookie Consent Dialog](#gdpr-cookie-consent-dialog) + - [How it works](#how-it-works-1) + - [When to use](#when-to-use) + - [How to enable](#how-to-enable-1) + - [Customizing the consent dialog](#customizing-the-consent-dialog) + - [Supported analytics providers](#supported-analytics-providers) + - [How it integrates with analytics](#how-it-integrates-with-analytics) + - [For developers](#for-developers) - [Setting up a Personal Access Token (PAT) for Google Scholar Citation Updates](#setting-up-a-personal-access-token-pat-for-google-scholar-citation-updates) - [Why is a PAT required?](#why-is-a-pat-required) - [How to set up the PAT](#how-to-set-up-the-pat) @@ -845,6 +862,129 @@ socials_in_search: true All these search features work in real-time and do not require a page reload. +## Social media previews + +**al-folio** supports Open Graph (OG) meta tags, which create rich preview objects when your pages are shared on social media platforms like Twitter, Facebook, LinkedIn, and others. These previews include your site's image, title, and description. + +### How to enable + +To enable social media previews: + +1. Open `_config.yml` and set: + + ```yaml + serve_og_meta: true + ``` + +2. Rebuild your site: + ```bash + docker compose down && docker compose up + # or + bundle exec jekyll serve + ``` + +Once enabled, all your site's pages will automatically include Open Graph meta tags in the HTML head element. + +### Configuring preview images + +You can configure what image displays in social media previews on a per-page or site-wide basis. + +**Site-wide default image:** + +Add the following to `_config.yml`: + +```yaml +og_image: /assets/img/your-default-preview-image.png +``` + +Replace the path with your actual image location in `assets/img/`. + +**Per-page custom image:** + +To override the site-wide default for a specific page, add `og_image` to the page's frontmatter: + +```yaml +--- +layout: page +title: My Page +og_image: /assets/img/custom-preview-image.png +--- +``` + +### Preview image best practices + +- **Dimensions:** Use 1200×630 pixels for optimal display on most social media platforms +- **Format:** PNG or JPG formats work best +- **Size:** Keep file size under 5MB +- **Content:** Ensure the image clearly represents your page content + +When a page is shared on social media, the platform will display your configured image along with the page title, description (from your site title or page description), and URL. + +--- + +## Related posts + +The theme can automatically display related posts at the bottom of each blog post. These are selected by finding the most recent posts that share common tags with the current post. + +### How it works + +- By default, the most recent posts that share at least one tag with the current post are displayed +- You can customize how many posts are shown and how many tags must match +- You can disable related posts for individual posts or across your entire site + +### Configuration + +To customize related posts behavior, edit the `related_blog_posts` section in `_config.yml`: + +```yaml +related_blog_posts: + enabled: true + max_related: 5 +``` + +- `enabled`: Set to `true` (default) to show related posts, or `false` to disable them site-wide +- `max_related`: Maximum number of related posts to display (default: 5) + +The theme also uses tags to find related content. Make sure your blog posts include relevant tags in their frontmatter: + +```yaml +--- +layout: post +title: My Blog Post +tags: machine-learning python +--- +``` + +### Disable related posts for a specific post + +To hide related posts on an individual blog post, add this to the post's frontmatter: + +```yaml +--- +layout: post +title: My Blog Post +related_posts: false +--- +``` + +### Additional configuration in \_config.yml + +You can also customize related posts behavior with these settings: + +```yaml +related_blog_posts: + enabled: true + max_related: 5 +``` + +These settings control: + +- Which posts are considered "related" (based on shared tags) +- How many related posts to display +- The algorithm used to calculate post similarity (uses the `classifier-reborn` gem) + +--- + ## Managing publication display The theme offers several options for customizing how publications are displayed: @@ -1114,6 +1254,111 @@ In this folder you need to store your file in the same format as you would in `_ - `File3.md` will not be posted at all - `2026-02-31-file4.md` is supposed to be posted on 31-February-2026, but there is no 31st in February hence this file will never be posted either +## GDPR Cookie Consent Dialog + +**al-folio** includes a built-in GDPR-compliant cookie consent dialog to help you respect visitor privacy and comply with privacy regulations (GDPR, CCPA, etc.). The feature is powered by [Vanilla Cookie Consent](https://cookieconsent.orestbida.com/) and integrates with all analytics providers. + +### How it works + +- A consent dialog appears on the visitor's first visit to your site +- Visitors can **accept all**, **reject all**, or **customize preferences** for analytics cookies +- Analytics scripts (Google Analytics, Cronitor, Pirsch, Openpanel) are **blocked by default** and only run after explicit consent +- Google Consent Mode ensures Google services operate in privacy mode before consent is granted +- User preferences are saved in their browser and respected on subsequent visits +- The dialog is mobile-responsive and supports multiple languages + +### When to use + +- ✅ **Required** if your site serves EU visitors and uses any analytics +- ✅ Recommended for any website using analytics, tracking, or marketing tools +- ❌ Not needed if your site doesn't use any analytics providers + +### How to enable + +1. Open `_config.yml` and locate the following line: + + ```yaml + enable_cookie_consent: false + ``` + +2. Change it to: + + ```yaml + enable_cookie_consent: true + ``` + +3. Rebuild your site: + + ```bash + docker compose down && docker compose up + # or + bundle exec jekyll serve + ``` + +4. The consent dialog will automatically appear on your site's homepage on first visit + +### Customizing the consent dialog + +The consent dialog configuration and messages are defined in [`_scripts/cookie-consent-setup.js`](_scripts/cookie-consent-setup.js). You can customize: + +- Dialog titles and button labels +- Cookie categories and descriptions +- Contact information links (points to `#contact` by default) +- Language translations + +To modify the dialog, edit the `language.translations.en` section in `_scripts/cookie-consent-setup.js`. For example, to change the consent dialog title: + +```javascript +consentModal: { + title: 'Your custom title here', + description: 'Your custom description...', + // ... other options +} +``` + +### Supported analytics providers + +When cookie consent is enabled, these analytics providers are automatically blocked until the user consents: + +- **Google Analytics (GA4)** – Uses Google Consent Mode for privacy-first operation before consent +- **Cronitor RUM** – Real User Monitoring for performance tracking +- **Pirsch Analytics** – GDPR-compliant analytics alternative +- **Openpanel Analytics** – Privacy-focused analytics platform + +Each provider only collects data if: + +1. It's enabled in `_config.yml` (e.g., `enable_google_analytics: true`) +2. The user has granted consent to the "analytics" category in the consent dialog + +### How it integrates with analytics + +When `enable_cookie_consent: true`, the template automatically: + +1. Adds `type="text/plain" data-category="analytics"` to all analytics script tags +2. This tells the cookie consent library to block these scripts until consent is granted +3. Loads the consent library and initializes Google Consent Mode +4. Updates consent preferences when the user changes them in the dialog + +You don't need to modify any analytics configuration—it works automatically. + +### For developers + +If you want to programmatically check consent status or react to consent changes, the library exposes the following: + +```javascript +// Check if user has granted analytics consent +window.CookieConsent.getCategories().analytics; // returns true or false + +// Listen for consent changes +window.CookieConsent.onChange(function (consentData) { + // Handle consent change +}); +``` + +For more API details, see [Vanilla Cookie Consent documentation](https://cookieconsent.orestbida.com/). + +--- + ## Setting up a Personal Access Token (PAT) for Google Scholar Citation Updates > [!TIP] diff --git a/README.md b/README.md index 31300fd..87a5498 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,7 @@ Run the test yourself: [Google Lighthouse PageSpeed Insights](https://pagespeed. - [Collections](#collections) - [Layouts](#layouts) - [The iconic style of Distill](#the-iconic-style-of-distill) - - [Full support for math & code](#full-support-for-math--code) + - [Full support for math \& code](#full-support-for-math--code) - [Photos, Audio, Video and more](#photos-audio-video-and-more) - [Other features](#other-features) - [GitHub's repositories and user stats](#githubs-repositories-and-user-stats) @@ -275,6 +275,7 @@ Run the test yourself: [Google Lighthouse PageSpeed Insights](https://pagespeed. - [Atom (RSS-like) Feed](#atom-rss-like-feed) - [Related posts](#related-posts) - [Code quality checks](#code-quality-checks) + - [GDPR Cookie Consent Dialog](#gdpr-cookie-consent-dialog) - [FAQ](#faq) - [Contributing](#contributing) - [Maintainers](#maintainers) @@ -351,15 +352,12 @@ This template has a built-in light/dark mode. It detects the user preferred colo ### CV -Your CV can be generated in one of two modern formats: - -- **[RenderCV](https://rendercv.com/) Format** (recommended): Edit [`_data/cv.yml`](_data/cv.yml) using the human-readable RenderCV YAML format. This format enables automatic PDF generation via GitHub Actions and provides professional styling options. -- **[JSONResume](https://jsonresume.org/) Format**: Edit [`assets/json/resume.json`](assets/json/resume.json) using the standardized JSON format. This is compatible with other resume tools and services. - -You can use both formats simultaneously and switch which one is rendered on your CV page using the `cv_format` frontmatter variable, or maintain just the one you prefer. The two files are independent data sources: if you choose to keep both, you must update each file separately—there is no automatic synchronization between them. +Your CV can be generated in one of two modern formats: **RenderCV** (recommended, with automatic PDF generation) or **JSONResume** (standardized JSON format). You can use both simultaneously and switch between them, or maintain just the one you prefer. [![CV Preview](readme_preview/cv.png)](https://alshedivat.github.io/al-folio/cv/) +For setup and customization details, see [Modifying the CV information](CUSTOMIZE.md#modifying-the-cv-information) in [CUSTOMIZE.md](CUSTOMIZE.md). + --- ### People @@ -372,21 +370,21 @@ You can create a people page if you want to feature more than one person. Each p ### Publications -Your publications' page is generated automatically from your BibTex bibliography. Simply edit [\_bibliography/papers.bib](_bibliography/papers.bib). You can also add new `*.bib` files and customize the look of your publications however you like by editing [\_pages/publications.md](_pages/publications.md). By default, the publications will be sorted by year and the most recent will be displayed first. You can change this behavior and more in the `Jekyll Scholar` section in [\_config.yml](_config.yml) file. - -You can add extra information to a publication, like a PDF file in the [assets/pdf/](assets/pdf/) directory and add the path to the PDF file in the BibTeX entry with the `pdf` field. Some of the supported fields are: `abstract`, `altmetric`, `arxiv`, `bibtex_show`, `blog`, `code`, `dimensions`, `doi`, `eprint`, `html`, `isbn`, `pdf`, `pmid`, `poster`, `slides`, `supp`, `video`, and `website`. +Your publications page is generated automatically from your BibTeX bibliography. You can customize publication display, add extra information like PDFs, and control sorting behavior. [![Publications Preview](readme_preview/publications.png)](https://alshedivat.github.io/al-folio/publications/) +For setup, BibTeX field documentation, and customization options, see [Adding a new publication](CUSTOMIZE.md#adding-a-new-publication) and [Managing publication display](CUSTOMIZE.md#managing-publication-display) in [CUSTOMIZE.md](CUSTOMIZE.md). + --- ### Collections -This Jekyll theme implements `collections` to let you break up your work into categories. The theme comes with two default collections: `news` and `projects`. Items from the `news` collection are automatically displayed on the home page. Items from the `projects` collection are displayed on a responsive grid on projects page. +This Jekyll theme implements `collections` to organize content into categories. The theme comes with default collections for `news`, `projects`, `books`, and `teachings`. You can easily create your own collections for apps, stories, courses, or any other creative work. [![Projects Preview](readme_preview/projects.png)](https://alshedivat.github.io/al-folio/projects/) -You can easily create your own collections, apps, short stories, courses, or whatever your creative work is. To do this, edit the collections in the [\_config.yml](_config.yml) file, create a corresponding folder, and create a landing page for your collection, similar to `_pages/projects.md`. +For detailed instructions on creating and customizing collections, see [Adding Collections](CUSTOMIZE.md#adding-collections) in [CUSTOMIZE.md](CUSTOMIZE.md). --- @@ -427,52 +425,27 @@ Photo formatting is made simple using [Bootstrap's grid system](https://getboots #### GitHub's repositories and user stats -**al-folio** uses [github-readme-stats](https://github.com/anuraghazra/github-readme-stats) and [github-profile-trophy](https://github.com/ryo-ma/github-profile-trophy) to display GitHub repositories and user stats on the `/repositories/` page. +**al-folio** displays GitHub repositories and user stats on the `/repositories/` page using [github-readme-stats](https://github.com/anuraghazra/github-readme-stats) and [github-profile-trophy](https://github.com/ryo-ma/github-profile-trophy). [![Repositories Preview](readme_preview/repositories.png)](https://alshedivat.github.io/al-folio/repositories/) -Edit the `_data/repositories.yml` and change the `github_users` and `github_repos` lists to include your own GitHub profile and repositories to the `/repositories/` page. - -You may also use the following codes for displaying this in any other pages. - -```html - -{% if site.data.repositories.github_users %} -
- {% for user in site.data.repositories.github_users %} {% include repository/repo_user.liquid username=user %} {% endfor %} -
-{% endif %} - - -{% if site.repo_trophies.enabled %} {% for user in site.data.repositories.github_users %} {% if site.data.repositories.github_users.size > 1 %} -

{{ user }}

-{% endif %} -
- {% include repository/repo_trophies.liquid username=user %} -
-{% endfor %} {% endif %} - - -{% if site.data.repositories.github_repos %} -
- {% for repo in site.data.repositories.github_repos %} {% include repository/repo.liquid repository=repo %} {% endfor %} -
-{% endif %} -``` +To configure which repositories and GitHub profiles to display, see [Modifying the user and repository information](CUSTOMIZE.md#modifying-the-user-and-repository-information) in [CUSTOMIZE.md](CUSTOMIZE.md). --- #### Theming -A variety of beautiful theme colors have been selected for you to choose from. The default is purple, but you can quickly change it by editing the `--global-theme-color` variable in the `_sass/_themes.scss` file. Other color variables are listed there as well. The stock theme color options available can be found at [\_sass/\_variables.scss](_sass/_variables.scss). You can also add your own colors to this file assigning each a name for ease of use across the template. +**al-folio** offers a variety of beautiful theme colors to choose from. The default is purple, but you can customize colors, fonts, spacing, and more to match your style. + +For detailed customization instructions, see [Changing theme color](CUSTOMIZE.md#changing-theme-color) and [Customizing fonts, spacing, and more](CUSTOMIZE.md#customizing-fonts-spacing-and-more) in [CUSTOMIZE.md](CUSTOMIZE.md). --- #### Social media previews -**al-folio** supports preview images on social media. To enable this functionality you will need to set `serve_og_meta` to `true` in your [\_config.yml](_config.yml). Once you have done so, all your site's pages will include Open Graph data in the HTML head element. +**al-folio** supports Open Graph preview images on social media. When enabled, your site's pages display rich preview objects with images, titles, and descriptions when shared. -You will then need to configure what image to display in your site's social media previews. This can be configured on a per-page basis, by setting the `og_image` page variable. If for an individual page this variable is not set, then the theme will fall back to a site-wide `og_image` variable, configurable in your [\_config.yml](_config.yml). In both the page-specific and site-wide cases, the `og_image` variable needs to hold the URL for the image you wish to display in social media previews. +For setup and customization, see [Social media previews](CUSTOMIZE.md#social-media-previews) in [CUSTOMIZE.md](CUSTOMIZE.md). --- @@ -484,7 +457,9 @@ It generates an Atom (RSS-like) feed of your posts, useful for Atom and RSS read #### Related posts -By default, there will be a related posts section on the bottom of the blog posts. These are generated by selecting the `max_related` most recent posts that share at least `min_common_tags` tags with the current post. If you do not want to display related posts on a specific post, simply add `related_posts: false` to the front matter of the post. If you want to disable it for all posts, simply set `enabled` to false in the `related_blog_posts` section in [\_config.yml](_config.yml). +By default, blog posts display related posts at the bottom. These are selected by finding the most recent posts that share tags with the current post. You can customize this behavior on a per-post or site-wide basis. + +For configuration details, see [Related posts](CUSTOMIZE.md#related-posts) in [CUSTOMIZE.md](CUSTOMIZE.md). --- @@ -498,6 +473,16 @@ Currently, we run some checks to ensure that the code quality and generated site We decided to keep `Axe` runs manual because fixing the issues are not straightforward and might be hard for people without web development knowledge. +--- + +#### GDPR Cookie Consent Dialog + +**al-folio** includes a built-in, GDPR-compliant cookie consent dialog to ensure your website respects visitor privacy. The dialog is powered by [Vanilla Cookie Consent](https://cookieconsent.orestbida.com/) and integrates seamlessly with all supported analytics providers. + +When enabled, analytics scripts are blocked until the user explicitly consents, and user preferences are saved across visits. This is essential for websites serving visitors in the European Union and other regions with strict privacy regulations. + +For complete setup and customization details, see [GDPR Cookie Consent Dialog](#gdpr-cookie-consent-dialog) in [CUSTOMIZE.md](CUSTOMIZE.md). + ## FAQ For frequently asked questions, please refer to [FAQ.md](FAQ.md). diff --git a/_config.yml b/_config.yml index 259ae05..a7a9de0 100644 --- a/_config.yml +++ b/_config.yml @@ -63,8 +63,6 @@ bib_search: true # Dimensions max_width: 930px -# TODO: add layout settings (single page vs. multi-page) - # ----------------------------------------------------------------------------- # Open Graph & Schema.org # ----------------------------------------------------------------------------- @@ -403,6 +401,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 @@ -609,6 +608,14 @@ third_party_libraries: url: js: "https://cdn.jsdelivr.net/npm/swiper@11.1.0/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=" diff --git a/_includes/distill_scripts.liquid b/_includes/distill_scripts.liquid index 44d1d5e..24d80d2 100644 --- a/_includes/distill_scripts.liquid +++ b/_includes/distill_scripts.liquid @@ -189,20 +189,58 @@ {% endif %} +{% if site.enable_cookie_consent %} + + + +{% endif %} + {% if site.enable_google_analytics %} - - + + {% endif %} {% if site.enable_cronitor_analytics %} - - + + {% endif %} {% if site.enable_pirsch_analytics %} {% endif %} {% if site.enable_openpanel_analytics %} - - + + {% endif %} {% if site.enable_progressbar %} diff --git a/_includes/head.liquid b/_includes/head.liquid index 2d53a28..d79bea9 100644 --- a/_includes/head.liquid +++ b/_includes/head.liquid @@ -195,3 +195,14 @@ {% if page.tikzjax %} {% endif %} + +{% if site.enable_cookie_consent %} + + +{% endif %} diff --git a/_includes/scripts.liquid b/_includes/scripts.liquid index be5b0ff..e6b21ae 100644 --- a/_includes/scripts.liquid +++ b/_includes/scripts.liquid @@ -223,20 +223,58 @@ {% endunless %} {% endif %} +{% if site.enable_cookie_consent %} + + + +{% endif %} + {% if site.enable_google_analytics %} - - + + {% endif %} {% if site.enable_cronitor_analytics %} - - + + {% endif %} {% if site.enable_pirsch_analytics %} {% endif %} {% if site.enable_openpanel_analytics %} - - + + {% endif %} {% if site.enable_progressbar %} diff --git a/_scripts/cookie-consent-setup.js b/_scripts/cookie-consent-setup.js new file mode 100644 index 0000000..0b12b97 --- /dev/null +++ b/_scripts/cookie-consent-setup.js @@ -0,0 +1,160 @@ +--- +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 || []; + +// Reuse existing global gtag if it was already defined (e.g. by other GA scripts) +// to avoid redefining it multiple times when consent is granted. +if (typeof window.gtag !== 'function') { + window.gtag = function() { + window.dataLayer.push(arguments); + }; +} + +// Local alias for convenience in this file +var gtag = window.gtag; +gtag('consent', 'default', { + 'ad_storage': 'denied', + 'analytics_storage': 'denied', + 'functionality_storage': 'denied', + 'personalization_storage': 'denied' +}); + +// Wait for the library to be available +var cookieConsentRetryCount = 0; +var COOKIE_CONSENT_MAX_RETRIES = 50; // 5 seconds max wait time + +function initializeCookieConsent() { + // Check if CookieConsent is available + if (!window.CookieConsent) { + if (cookieConsentRetryCount++ < COOKIE_CONSENT_MAX_RETRIES) { + // Library not yet loaded, try again after a short delay + setTimeout(initializeCookieConsent, 100); + } else { + console.error('CookieConsent library failed to load'); + } + 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 contact us.' + } + ] + } + } + } + }, + + // 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(); + diff --git a/assets/js/theme.js b/assets/js/theme.js index 0ac6983..1033906 100644 --- a/assets/js/theme.js +++ b/assets/js/theme.js @@ -29,6 +29,7 @@ let applyTheme = () => { setHighlight(theme); setGiscusTheme(theme); setSearchTheme(theme); + setCookieConsentTheme(theme); updateCalendarUrl(); // if mermaid is not defined, do nothing @@ -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(() => {