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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +148 -52
- data/README.md +108 -5
- data/_data/content_statistics.yml +401 -0
- data/_data/generate_statistics.rb +275 -0
- data/_data/navigation/home.yml +15 -0
- data/_data/navigation/main.yml +2 -5
- data/_includes/analytics/posthog.html +281 -0
- data/_includes/components/cookie-consent.html +382 -0
- data/_includes/components/info-section.html +5 -0
- data/_includes/components/theme-info.html +312 -0
- data/_includes/content/sitemap.html +935 -108
- data/_includes/core/footer.html +16 -0
- data/_includes/navigation/navbar.html +6 -0
- data/_includes/stats/README.md +273 -0
- data/_includes/stats/stats-categories.html +146 -0
- data/_includes/stats/stats-header.html +123 -0
- data/_includes/stats/stats-metrics.html +243 -0
- data/_includes/stats/stats-no-data.html +180 -0
- data/_includes/stats/stats-overview.html +119 -0
- data/_includes/stats/stats-tags.html +142 -0
- data/_layouts/root.html +6 -0
- data/_layouts/sitemap-collection.html +500 -0
- data/_layouts/stats.html +178 -0
- data/assets/css/main.scss +5 -5
- data/assets/css/stats.css +392 -0
- metadata +23 -7
- /data/_sass/{it-journey → core}/_docs.scss +0 -0
- /data/_sass/{it-journey → core}/_syntax.scss +0 -0
- /data/_sass/{it-journey → core}/_theme.scss +0 -0
- /data/_sass/{it-journey → core}/_variables.scss +0 -0
- /data/_sass/{it-journey → core}/code-copy.scss +0 -0
|
@@ -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 -->
|