jekyll-theme-zer0 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,281 @@
1
+ <!--
2
+ ===================================================================
3
+ POSTHOG ANALYTICS - Privacy-First Web Analytics Integration
4
+ ===================================================================
5
+
6
+ File: posthog.html
7
+ Path: _includes/analytics/posthog.html
8
+ Purpose: Configurable PostHog analytics implementation with comprehensive
9
+ privacy controls and GDPR/CCPA compliance
10
+
11
+ Template Logic:
12
+ - Conditionally loads only in production environment
13
+ - Respects Do Not Track (DNT) browser settings
14
+ - Integrates with cookie consent management system
15
+ - Provides Jekyll-specific event tracking and page properties
16
+
17
+ Dependencies:
18
+ - site.posthog configuration from _config.yml
19
+ - Cookie consent system for user permission management
20
+ - Jekyll environment detection for production loading
21
+ - PostHog CDN and JavaScript library
22
+
23
+ Privacy Features:
24
+ - Environment-specific loading (production only by default)
25
+ - Cookie consent integration with opt-in/opt-out mechanisms
26
+ - Do Not Track respect with automatic analytics disabling
27
+ - Session recording controls with input masking
28
+ - IP anonymization options for enhanced privacy
29
+
30
+ Custom Event Tracking:
31
+ - File downloads (PDFs, documents, archives)
32
+ - External link clicks and referral tracking
33
+ - Search queries and site navigation patterns
34
+ - Scroll depth analysis and reading engagement
35
+ - Jekyll-specific interactions (TOC, sidebar, code blocks)
36
+ ===================================================================
37
+ -->
38
+
39
+ {% comment %}
40
+ PostHog Analytics Integration for Zer0-Mistakes Jekyll Theme
41
+
42
+ Features:
43
+ - Environment-specific loading (production only by default)
44
+ - Configurable privacy settings
45
+ - Custom event tracking for Jekyll-specific actions
46
+ - GDPR/CCPA compliance options
47
+ - Session recording controls
48
+ - Cookie consent integration
49
+
50
+ Configuration: Set posthog settings in _config.yml
51
+ Privacy: Respects Do Not Track and provides opt-out mechanisms
52
+ {% endcomment %}
53
+
54
+ {% if site.posthog.enabled and jekyll.environment == "production" %}
55
+ <script>
56
+ {% comment %} PostHog Configuration {% endcomment %}
57
+ window.posthogConfig = {
58
+ apiKey: '{{ site.posthog.api_key }}',
59
+ apiHost: '{{ site.posthog.api_host }}',
60
+ personProfiles: '{{ site.posthog.person_profiles | default: "identified_only" }}',
61
+ autocapture: {{ site.posthog.autocapture | default: true }},
62
+ capturePageview: {{ site.posthog.capture_pageview | default: true }},
63
+ capturePageleave: {{ site.posthog.capture_pageleave | default: true }},
64
+ disableCookie: {{ site.posthog.disable_cookie | default: false }},
65
+ respectDnt: {{ site.posthog.respect_dnt | default: true }},
66
+ crossSubdomainCookie: {{ site.posthog.cross_subdomain_cookie | default: false }},
67
+ secureCookie: {{ site.posthog.secure_cookie | default: true }},
68
+ persistence: '{{ site.posthog.persistence | default: "localStorage+cookie" }}',
69
+ sessionRecording: {
70
+ enabled: {{ site.posthog.session_recording | default: false }},
71
+ maskAllText: {{ site.posthog.privacy.mask_all_text | default: false }},
72
+ maskAllInputs: {{ site.posthog.privacy.mask_all_inputs | default: true }}
73
+ }
74
+ };
75
+
76
+ {% comment %} Check for Do Not Track before loading {% endcomment %}
77
+ if (window.posthogConfig.respectDnt && navigator.doNotTrack === '1') {
78
+ console.log('PostHog: Respecting Do Not Track setting');
79
+ } else {
80
+ {% comment %} PostHog Loading Script {% endcomment %}
81
+ !function(t,e){var o,n,p,r;e.__SV||(window.posthog && window.posthog.__loaded)||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init Rr Mr fi Or Ar ci Tr Cr capture Mi calculateEventProperties Lr register register_once register_for_session unregister unregister_for_session Hr getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSurveysLoaded onSessionId getSurveys getActiveMatchingSurveys renderSurvey displaySurvey canRenderSurvey canRenderSurveyAsync identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty Ur jr createPersonProfile zr kr Br opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing get_explicit_consent_status is_capturing clear_opt_in_out_capturing Dr debug M Nr getPageViewId captureTraceFeedback captureTraceMetric $r".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
82
+
83
+ {% comment %} Initialize PostHog with configuration {% endcomment %}
84
+ posthog.init(window.posthogConfig.apiKey, {
85
+ api_host: window.posthogConfig.apiHost,
86
+ person_profiles: window.posthogConfig.personProfiles,
87
+ autocapture: window.posthogConfig.autocapture,
88
+ capture_pageview: window.posthogConfig.capturePageview,
89
+ capture_pageleave: window.posthogConfig.capturePageleave,
90
+ disable_cookie: window.posthogConfig.disableCookie,
91
+ cross_subdomain_cookie: window.posthogConfig.crossSubdomainCookie,
92
+ secure_cookie: window.posthogConfig.secureCookie,
93
+ persistence: window.posthogConfig.persistence,
94
+ session_recording: window.posthogConfig.sessionRecording,
95
+ {% if site.posthog.privacy.ip_anonymization %}
96
+ ip: false,
97
+ {% endif %}
98
+ loaded: function(posthog) {
99
+ {% comment %} PostHog loaded successfully {% endcomment %}
100
+ console.log('PostHog analytics loaded successfully');
101
+
102
+ {% comment %} Track Jekyll-specific page properties {% endcomment %}
103
+ posthog.register({
104
+ 'site_title': '{{ site.title }}',
105
+ 'site_description': '{{ site.description | strip_newlines | strip }}',
106
+ 'jekyll_version': '{{ jekyll.version }}',
107
+ 'theme': '{{ site.remote_theme | default: site.theme }}',
108
+ 'page_layout': '{{ page.layout }}',
109
+ 'page_collection': '{{ page.collection }}',
110
+ {% if page.categories %}
111
+ 'page_categories': {{ page.categories | jsonify }},
112
+ {% endif %}
113
+ {% if page.tags %}
114
+ 'page_tags': {{ page.tags | jsonify }},
115
+ {% endif %}
116
+ 'page_url': window.location.pathname,
117
+ 'page_title': '{{ page.title | strip_newlines | strip }}',
118
+ 'page_author': '{{ page.author | default: site.author.name }}',
119
+ {% if page.date %}
120
+ 'page_date': '{{ page.date | date: "%Y-%m-%d" }}',
121
+ {% endif %}
122
+ 'user_agent': navigator.userAgent,
123
+ 'screen_resolution': screen.width + 'x' + screen.height,
124
+ 'viewport_size': window.innerWidth + 'x' + window.innerHeight
125
+ });
126
+
127
+ {% comment %} Initialize custom event tracking {% endcomment %}
128
+ initializeCustomTracking();
129
+ }
130
+ });
131
+ }
132
+
133
+ {% comment %} Custom Event Tracking Functions {% endcomment %}
134
+ function initializeCustomTracking() {
135
+ {% if site.posthog.custom_events.track_downloads %}
136
+ {% comment %} Track file downloads {% endcomment %}
137
+ document.addEventListener('click', function(e) {
138
+ var target = e.target.closest('a');
139
+ if (target && target.href) {
140
+ var href = target.href.toLowerCase();
141
+ var downloadExtensions = ['.pdf', '.zip', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.csv'];
142
+ var isDownload = downloadExtensions.some(ext => href.includes(ext));
143
+
144
+ if (isDownload) {
145
+ posthog.capture('file_download', {
146
+ 'file_url': target.href,
147
+ 'file_name': target.innerText || target.href.split('/').pop(),
148
+ 'page_url': window.location.pathname
149
+ });
150
+ }
151
+ }
152
+ });
153
+ {% endif %}
154
+
155
+ {% if site.posthog.custom_events.track_external_links %}
156
+ {% comment %} Track external link clicks {% endcomment %}
157
+ document.addEventListener('click', function(e) {
158
+ var target = e.target.closest('a');
159
+ if (target && target.href && target.hostname !== window.location.hostname) {
160
+ posthog.capture('external_link_click', {
161
+ 'link_url': target.href,
162
+ 'link_text': target.innerText,
163
+ 'page_url': window.location.pathname
164
+ });
165
+ }
166
+ });
167
+ {% endif %}
168
+
169
+ {% if site.posthog.custom_events.track_search %}
170
+ {% comment %} Track search queries {% endcomment %}
171
+ var searchInputs = document.querySelectorAll('input[type="search"], .search-input, #search');
172
+ searchInputs.forEach(function(input) {
173
+ input.addEventListener('keypress', function(e) {
174
+ if (e.key === 'Enter' && this.value.trim()) {
175
+ posthog.capture('search_query', {
176
+ 'search_term': this.value.trim(),
177
+ 'page_url': window.location.pathname
178
+ });
179
+ }
180
+ });
181
+ });
182
+ {% endif %}
183
+
184
+ {% if site.posthog.custom_events.track_scroll_depth %}
185
+ {% comment %} Track scroll depth {% endcomment %}
186
+ var scrollDepths = [25, 50, 75, 90];
187
+ var triggeredDepths = [];
188
+
189
+ window.addEventListener('scroll', function() {
190
+ var scrollPercent = Math.round((window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100);
191
+
192
+ scrollDepths.forEach(function(depth) {
193
+ if (scrollPercent >= depth && !triggeredDepths.includes(depth)) {
194
+ triggeredDepths.push(depth);
195
+ posthog.capture('scroll_depth', {
196
+ 'depth_percentage': depth,
197
+ 'page_url': window.location.pathname,
198
+ 'page_title': document.title
199
+ });
200
+ }
201
+ });
202
+ });
203
+ {% endif %}
204
+
205
+ {% comment %} Track Jekyll-specific events {% endcomment %}
206
+
207
+ {% comment %} Track code block interactions {% endcomment %}
208
+ document.addEventListener('click', function(e) {
209
+ if (e.target.closest('.highlight, pre, code')) {
210
+ posthog.capture('code_interaction', {
211
+ 'interaction_type': 'click',
212
+ 'page_url': window.location.pathname
213
+ });
214
+ }
215
+ });
216
+
217
+ {% comment %} Track table of contents clicks {% endcomment %}
218
+ document.addEventListener('click', function(e) {
219
+ if (e.target.closest('.toc, #TableOfContents, .table-of-contents')) {
220
+ var target = e.target.closest('a');
221
+ if (target && target.href) {
222
+ posthog.capture('toc_click', {
223
+ 'toc_link': target.href,
224
+ 'toc_text': target.innerText,
225
+ 'page_url': window.location.pathname
226
+ });
227
+ }
228
+ }
229
+ });
230
+
231
+ {% comment %} Track sidebar navigation {% endcomment %}
232
+ document.addEventListener('click', function(e) {
233
+ if (e.target.closest('.sidebar, .bd-sidebar, nav[class*="sidebar"]')) {
234
+ var target = e.target.closest('a');
235
+ if (target && target.href) {
236
+ posthog.capture('sidebar_navigation', {
237
+ 'nav_link': target.href,
238
+ 'nav_text': target.innerText,
239
+ 'page_url': window.location.pathname
240
+ });
241
+ }
242
+ }
243
+ });
244
+ }
245
+
246
+ {% comment %} Expose PostHog utilities globally for theme usage {% endcomment %}
247
+ window.zer0Analytics = {
248
+ track: function(eventName, properties) {
249
+ if (window.posthog && typeof window.posthog.capture === 'function') {
250
+ posthog.capture(eventName, properties);
251
+ }
252
+ },
253
+ identify: function(userId, properties) {
254
+ if (window.posthog && typeof window.posthog.identify === 'function') {
255
+ posthog.identify(userId, properties);
256
+ }
257
+ },
258
+ reset: function() {
259
+ if (window.posthog && typeof window.posthog.reset === 'function') {
260
+ posthog.reset();
261
+ }
262
+ }
263
+ };
264
+ </script>
265
+ {% else %}
266
+ {% comment %} PostHog disabled or not in production environment {% endcomment %}
267
+ <script>
268
+ {% comment %} Provide no-op analytics functions for development {% endcomment %}
269
+ window.zer0Analytics = {
270
+ track: function(eventName, properties) {
271
+ console.log('Analytics (disabled):', eventName, properties);
272
+ },
273
+ identify: function(userId, properties) {
274
+ console.log('Analytics identify (disabled):', userId, properties);
275
+ },
276
+ reset: function() {
277
+ console.log('Analytics reset (disabled)');
278
+ }
279
+ };
280
+ </script>
281
+ {% endif %}
@@ -0,0 +1,382 @@
1
+ <!--
2
+ ===================================================================
3
+ COOKIE CONSENT - GDPR/CCPA Compliant Privacy Management
4
+ ===================================================================
5
+
6
+ File: cookie-consent.html
7
+ Path: _includes/components/cookie-consent.html
8
+ Purpose: Privacy-compliant cookie consent banner and management modal
9
+ with granular user permission controls
10
+
11
+ Template Logic:
12
+ - Displays consent banner for new visitors after 1-second delay
13
+ - Provides granular consent options (Essential, Analytics, Marketing)
14
+ - Stores user preferences in localStorage with 365-day expiry
15
+ - Integrates with PostHog analytics for consent-based tracking
16
+
17
+ Dependencies:
18
+ - PostHog analytics integration
19
+ - Bootstrap 5 modal and form components
20
+ - Bootstrap Icons for UI elements
21
+ - site.posthog configuration from _config.yml
22
+
23
+ Privacy Features:
24
+ - GDPR/CCPA compliance with explicit user consent
25
+ - Granular permission controls with persistent storage
26
+ - Automatic consent expiry and re-consent after 365 days
27
+ - Integration with PostHog opt-in/opt-out mechanisms
28
+
29
+ Accessibility:
30
+ - ARIA labels for screen readers
31
+ - Keyboard navigation support
32
+ - High contrast design for visibility
33
+ - Semantic HTML structure
34
+ ===================================================================
35
+ -->
36
+
37
+ {% comment %}
38
+ Cookie Consent Banner for Zer0-Mistakes Jekyll Theme
39
+
40
+ Features:
41
+ - GDPR/CCPA compliance
42
+ - Granular consent options (essential, analytics, marketing)
43
+ - PostHog analytics integration
44
+ - Local storage for preferences
45
+ - Bootstrap 5 styling
46
+ - Accessibility compliant
47
+
48
+ Usage: Include in root.html layout
49
+ Configuration: Uses site.posthog settings from _config.yml
50
+ {% endcomment %}
51
+
52
+ <div id="cookieConsent" class="cookie-consent-banner position-fixed bottom-0 start-0 end-0 bg-dark text-light p-3 shadow-lg" style="z-index: 9999; transform: translateY(100%); transition: transform 0.3s ease-in-out;">
53
+ <div class="container-fluid">
54
+ <div class="row align-items-center">
55
+ <div class="col-12 col-lg-8">
56
+ <h6 class="mb-2 mb-lg-0">
57
+ <i class="bi bi-shield-check me-2"></i>
58
+ We value your privacy
59
+ </h6>
60
+ <p class="mb-2 mb-lg-0 small">
61
+ This website uses cookies and similar technologies to enhance your browsing experience, analyze traffic, and provide personalized content.
62
+ <a href="/privacy-policy/" class="text-light text-decoration-underline">Learn more in our Privacy Policy</a>.
63
+ </p>
64
+ </div>
65
+ <div class="col-12 col-lg-4">
66
+ <div class="d-flex flex-wrap gap-2 justify-content-lg-end">
67
+ <button type="button" class="btn btn-sm btn-outline-light" data-bs-toggle="modal" data-bs-target="#cookieSettingsModal">
68
+ <i class="bi bi-gear me-1"></i>
69
+ Manage Cookies
70
+ </button>
71
+ <button type="button" class="btn btn-sm btn-secondary" id="rejectAllCookies">
72
+ Reject All
73
+ </button>
74
+ <button type="button" class="btn btn-sm btn-primary" id="acceptAllCookies">
75
+ Accept All
76
+ </button>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Cookie Settings Modal -->
84
+ <div class="modal fade" id="cookieSettingsModal" tabindex="-1" aria-labelledby="cookieSettingsModalLabel" aria-hidden="true">
85
+ <div class="modal-dialog modal-lg">
86
+ <div class="modal-content">
87
+ <div class="modal-header">
88
+ <h5 class="modal-title" id="cookieSettingsModalLabel">
89
+ <i class="bi bi-shield-check me-2"></i>
90
+ Cookie Preferences
91
+ </h5>
92
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
93
+ </div>
94
+ <div class="modal-body">
95
+ <p class="text-muted">
96
+ Customize your cookie preferences below. You can change these settings at any time by clicking the cookie preferences link in our footer.
97
+ </p>
98
+
99
+ <div class="cookie-category mb-4">
100
+ <div class="d-flex justify-content-between align-items-center mb-2">
101
+ <h6 class="mb-0">
102
+ <i class="bi bi-shield-fill-check text-success me-2"></i>
103
+ Essential Cookies
104
+ </h6>
105
+ <span class="badge bg-success">Always Active</span>
106
+ </div>
107
+ <p class="small text-muted mb-0">
108
+ These cookies are necessary for the website to function properly. They enable core functionality such as navigation, security, and accessibility features.
109
+ </p>
110
+ </div>
111
+
112
+ <div class="cookie-category mb-4">
113
+ <div class="d-flex justify-content-between align-items-center mb-2">
114
+ <h6 class="mb-0">
115
+ <i class="bi bi-graph-up me-2"></i>
116
+ Analytics Cookies
117
+ </h6>
118
+ <div class="form-check form-switch">
119
+ <input class="form-check-input" type="checkbox" id="analyticsCookies" checked>
120
+ <label class="form-check-label" for="analyticsCookies">Enable</label>
121
+ </div>
122
+ </div>
123
+ <p class="small text-muted mb-0">
124
+ These cookies help us understand how visitors interact with our website by collecting anonymous information about page visits, time spent, and user behavior patterns.
125
+ </p>
126
+ <details class="mt-2">
127
+ <summary class="small text-primary cursor-pointer">View analytics providers</summary>
128
+ <ul class="small text-muted mt-2 ps-3">
129
+ <li>PostHog Analytics - Website usage analytics and session recording (when enabled)</li>
130
+ <li>Google Analytics - Traffic analysis and user demographics</li>
131
+ </ul>
132
+ </details>
133
+ </div>
134
+
135
+ <div class="cookie-category mb-4">
136
+ <div class="d-flex justify-content-between align-items-center mb-2">
137
+ <h6 class="mb-0">
138
+ <i class="bi bi-megaphone me-2"></i>
139
+ Marketing Cookies
140
+ </h6>
141
+ <div class="form-check form-switch">
142
+ <input class="form-check-input" type="checkbox" id="marketingCookies">
143
+ <label class="form-check-label" for="marketingCookies">Enable</label>
144
+ </div>
145
+ </div>
146
+ <p class="small text-muted mb-0">
147
+ These cookies are used to deliver personalized advertisements and marketing content based on your interests and browsing behavior.
148
+ </p>
149
+ <p class="small text-muted mt-2">
150
+ <em>Note: We currently do not use marketing cookies, but this option is provided for future enhancements.</em>
151
+ </p>
152
+ </div>
153
+
154
+ <div class="bg-light p-3 rounded">
155
+ <h6 class="text-dark mb-2">
156
+ <i class="bi bi-info-circle me-2"></i>
157
+ Your Privacy Rights
158
+ </h6>
159
+ <ul class="small text-dark mb-0 ps-3">
160
+ <li>You can withdraw consent at any time by updating your cookie preferences</li>
161
+ <li>Essential cookies cannot be disabled as they are necessary for site functionality</li>
162
+ <li>Disabling analytics cookies will prevent us from improving your experience</li>
163
+ <li>Your preferences are stored locally and will be remembered for future visits</li>
164
+ </ul>
165
+ </div>
166
+ </div>
167
+ <div class="modal-footer">
168
+ <button type="button" class="btn btn-outline-secondary" id="rejectAllModal">
169
+ Reject All
170
+ </button>
171
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
172
+ Cancel
173
+ </button>
174
+ <button type="button" class="btn btn-primary" id="saveCookiePreferences">
175
+ Save Preferences
176
+ </button>
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </div>
181
+
182
+ <script>
183
+ // Cookie Consent Management
184
+ (function() {
185
+ 'use strict';
186
+
187
+ const STORAGE_KEY = 'zer0-cookie-consent';
188
+ const CONSENT_EXPIRY_DAYS = 365;
189
+
190
+ // Default consent state
191
+ const defaultConsent = {
192
+ essential: true,
193
+ analytics: false,
194
+ marketing: false,
195
+ timestamp: Date.now(),
196
+ version: '1.0'
197
+ };
198
+
199
+ // Get current consent state
200
+ function getConsentState() {
201
+ try {
202
+ const stored = localStorage.getItem(STORAGE_KEY);
203
+ if (stored) {
204
+ const consent = JSON.parse(stored);
205
+ // Check if consent is expired (365 days)
206
+ const isExpired = (Date.now() - consent.timestamp) > (CONSENT_EXPIRY_DAYS * 24 * 60 * 60 * 1000);
207
+ if (!isExpired) {
208
+ return consent;
209
+ }
210
+ }
211
+ } catch (e) {
212
+ console.warn('Error reading cookie consent:', e);
213
+ }
214
+ return null;
215
+ }
216
+
217
+ // Save consent state
218
+ function saveConsentState(consent) {
219
+ try {
220
+ consent.timestamp = Date.now();
221
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(consent));
222
+ applyConsent(consent);
223
+ } catch (e) {
224
+ console.warn('Error saving cookie consent:', e);
225
+ }
226
+ }
227
+
228
+ // Apply consent decisions
229
+ function applyConsent(consent) {
230
+ // Analytics consent
231
+ if (consent.analytics && window.posthog) {
232
+ window.posthog.opt_in_capturing();
233
+ console.log('PostHog analytics enabled via consent');
234
+ } else if (window.posthog) {
235
+ window.posthog.opt_out_capturing();
236
+ console.log('PostHog analytics disabled via consent');
237
+ }
238
+
239
+ // Set global consent flags for other scripts
240
+ window.cookieConsent = consent;
241
+
242
+ // Dispatch consent event for other scripts to listen to
243
+ document.dispatchEvent(new CustomEvent('cookieConsentChanged', {
244
+ detail: consent
245
+ }));
246
+ }
247
+
248
+ // Show consent banner
249
+ function showConsentBanner() {
250
+ const banner = document.getElementById('cookieConsent');
251
+ if (banner) {
252
+ setTimeout(() => {
253
+ banner.style.transform = 'translateY(0)';
254
+ }, 1000); // Show after 1 second
255
+ }
256
+ }
257
+
258
+ // Hide consent banner
259
+ function hideConsentBanner() {
260
+ const banner = document.getElementById('cookieConsent');
261
+ if (banner) {
262
+ banner.style.transform = 'translateY(100%)';
263
+ }
264
+ }
265
+
266
+ // Update modal UI with current preferences
267
+ function updateModalUI(consent) {
268
+ document.getElementById('analyticsCookies').checked = consent.analytics;
269
+ document.getElementById('marketingCookies').checked = consent.marketing;
270
+ }
271
+
272
+ // Get consent from modal UI
273
+ function getModalConsent() {
274
+ return {
275
+ essential: true, // Always true
276
+ analytics: document.getElementById('analyticsCookies').checked,
277
+ marketing: document.getElementById('marketingCookies').checked,
278
+ version: '1.0'
279
+ };
280
+ }
281
+
282
+ // Initialize on DOM content loaded
283
+ document.addEventListener('DOMContentLoaded', function() {
284
+ const existingConsent = getConsentState();
285
+
286
+ if (existingConsent) {
287
+ // Apply existing consent
288
+ applyConsent(existingConsent);
289
+ updateModalUI(existingConsent);
290
+ } else {
291
+ // Show consent banner for new visitors
292
+ showConsentBanner();
293
+ }
294
+
295
+ // Accept all cookies
296
+ document.getElementById('acceptAllCookies')?.addEventListener('click', function() {
297
+ const consent = {
298
+ essential: true,
299
+ analytics: true,
300
+ marketing: false, // Keep false until we implement marketing
301
+ version: '1.0'
302
+ };
303
+ saveConsentState(consent);
304
+ hideConsentBanner();
305
+ });
306
+
307
+ // Reject all cookies
308
+ document.getElementById('rejectAllCookies')?.addEventListener('click', function() {
309
+ saveConsentState(defaultConsent);
310
+ hideConsentBanner();
311
+ });
312
+
313
+ // Reject all from modal
314
+ document.getElementById('rejectAllModal')?.addEventListener('click', function() {
315
+ saveConsentState(defaultConsent);
316
+ updateModalUI(defaultConsent);
317
+ hideConsentBanner();
318
+ bootstrap.Modal.getInstance(document.getElementById('cookieSettingsModal')).hide();
319
+ });
320
+
321
+ // Save preferences from modal
322
+ document.getElementById('saveCookiePreferences')?.addEventListener('click', function() {
323
+ const consent = getModalConsent();
324
+ saveConsentState(consent);
325
+ hideConsentBanner();
326
+ bootstrap.Modal.getInstance(document.getElementById('cookieSettingsModal')).hide();
327
+ });
328
+
329
+ // Update modal when opened
330
+ document.getElementById('cookieSettingsModal')?.addEventListener('show.bs.modal', function() {
331
+ const currentConsent = getConsentState() || defaultConsent;
332
+ updateModalUI(currentConsent);
333
+ });
334
+ });
335
+
336
+ // Expose functions globally for external use
337
+ window.cookieManager = {
338
+ getConsent: getConsentState,
339
+ setConsent: saveConsentState,
340
+ showBanner: showConsentBanner,
341
+ hideBanner: hideConsentBanner,
342
+ hasConsent: function(type) {
343
+ const consent = getConsentState();
344
+ return consent ? consent[type] : false;
345
+ }
346
+ };
347
+ })();
348
+ </script>
349
+
350
+ <style>
351
+ .cookie-consent-banner {
352
+ background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
353
+ backdrop-filter: blur(10px);
354
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
355
+ }
356
+
357
+ .cursor-pointer {
358
+ cursor: pointer;
359
+ }
360
+
361
+ .cookie-category {
362
+ border: 1px solid #dee2e6;
363
+ border-radius: 8px;
364
+ padding: 1rem;
365
+ }
366
+
367
+ .form-check-input:checked {
368
+ background-color: var(--bs-success);
369
+ border-color: var(--bs-success);
370
+ }
371
+
372
+ @media (max-width: 768px) {
373
+ .cookie-consent-banner .btn {
374
+ width: 100%;
375
+ margin-bottom: 0.5rem;
376
+ }
377
+
378
+ .cookie-consent-banner .btn:last-child {
379
+ margin-bottom: 0;
380
+ }
381
+ }
382
+ </style>
@@ -21,6 +21,11 @@
21
21
  {% include navigation/breadcrumbs.html %}
22
22
  {% include components/searchbar.html %}
23
23
 
24
+ <!-- Theme and System Information -->
25
+ <div class="my-4">
26
+ {% include components/theme-info.html %}
27
+ </div>
28
+
24
29
  <!-- Shortcuts to source bode -->
25
30
  {% include components/dev-shortcuts.html %}
26
31
  <!-- Dark Mode Switch -->