jekyll-theme-zer0 0.5.0 → 0.7.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.
data/_data/authors.yml ADDED
@@ -0,0 +1,52 @@
1
+ # Authors Data File
2
+ # =================
3
+ # Define author profiles for blog posts
4
+ #
5
+ # Usage in front matter:
6
+ # author: johndoe (references the key below)
7
+ #
8
+ # Fields:
9
+ # name: Display name (required)
10
+ # bio: Short biography (optional)
11
+ # avatar: Path to avatar image (optional)
12
+ # email: Contact email (optional)
13
+ # twitter: Twitter handle without @ (optional)
14
+ # github: GitHub username (optional)
15
+ # linkedin: LinkedIn profile ID (optional)
16
+ # website: Personal website URL (optional)
17
+ # role: Job title or role (optional)
18
+
19
+ default:
20
+ name: "Zer0-Mistakes Team"
21
+ bio: "The collective voice of the Zer0-Mistakes Jekyll theme development team."
22
+ avatar: "/images/default-avatar.png"
23
+ github: "bamr87"
24
+ website: "https://bamr87.github.io/zer0-mistakes/"
25
+ role: "Development Team"
26
+
27
+ bamr87:
28
+ name: "Amr Bakri"
29
+ bio: "Creator of Zer0-Mistakes Jekyll theme. Developer, automation enthusiast, and Docker advocate."
30
+ avatar: "/images/authors/bamr87.png"
31
+ github: "bamr87"
32
+ twitter: "bamr87"
33
+ role: "Lead Developer"
34
+
35
+ guest:
36
+ name: "Guest Author"
37
+ bio: "A valued contributor to our community."
38
+ avatar: "/images/default-avatar.png"
39
+ role: "Guest Contributor"
40
+
41
+ # Add more authors as needed:
42
+ #
43
+ # author_key:
44
+ # name: "Author Full Name"
45
+ # bio: "Short biography about the author."
46
+ # avatar: "/images/authors/author.png"
47
+ # email: "author@example.com"
48
+ # twitter: "twitterhandle"
49
+ # github: "githubuser"
50
+ # linkedin: "linkedinprofile"
51
+ # website: "https://example.com"
52
+ # role: "Writer"
@@ -1,19 +1,18 @@
1
- - icon: world
2
- url: /posts/world/
3
- title: World
1
+ - title: Development
2
+ icon: code-slash
3
+ url: /posts/development/
4
4
  - title: Technology
5
- icon: tech
6
- url: /posts/tech/
5
+ icon: cpu
6
+ url: /posts/technology/
7
+ - title: Tutorial
8
+ icon: journal-code
9
+ url: /posts/tutorial/
10
+ - title: World
11
+ icon: globe
12
+ url: /posts/world/
7
13
  - title: Business
8
- icon: cash
14
+ icon: briefcase
9
15
  url: /posts/business/
10
16
  - title: Science
11
- icon: flask
17
+ icon: mortarboard
12
18
  url: /posts/science/
13
- - title: Health
14
- icon: heart
15
- url: /posts/health/
16
- - title: Politics
17
- icon: politics
18
- url: /posts/politics/
19
-
@@ -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,177 @@
1
+ <!--
2
+ ===================================================================
3
+ AUTHOR CARD COMPONENT - Display author information
4
+ ===================================================================
5
+
6
+ File: author-card.html
7
+ Path: _includes/components/author-card.html
8
+ Purpose: Reusable author profile display for posts and author pages
9
+
10
+ Parameters:
11
+ - author (required): Author key from _data/authors.yml OR author name string
12
+ - style (optional): "compact" | "full" | "inline" (default: "compact")
13
+ - show_bio (optional): Show author bio (default: true for full, false for compact)
14
+ - show_social (optional): Show social links (default: true for full, false for compact)
15
+
16
+ Usage:
17
+ {% include components/author-card.html author=page.author %}
18
+ {% include components/author-card.html author="bamr87" style="full" %}
19
+ {% include components/author-card.html author=page.author style="inline" %}
20
+
21
+ Dependencies:
22
+ - _data/authors.yml: Author data definitions
23
+ - Bootstrap 5 card and utility classes
24
+ - Bootstrap Icons
25
+ ===================================================================
26
+ -->
27
+
28
+ {% comment %} Parameter defaults {% endcomment %}
29
+ {% assign style = include.style | default: "compact" %}
30
+
31
+ {% comment %}
32
+ Resolve author: can be a key to _data/authors.yml or a plain name string
33
+ {% endcomment %}
34
+ {% assign author_key = include.author %}
35
+ {% assign author_data = site.data.authors[author_key] %}
36
+
37
+ {% comment %} If no match in authors.yml, use string as display name {% endcomment %}
38
+ {% unless author_data %}
39
+ {% assign author_data = site.data.authors.default %}
40
+ {% if include.author and include.author != "" %}
41
+ {% capture author_name %}{{ include.author }}{% endcapture %}
42
+ {% else %}
43
+ {% assign author_name = author_data.name %}
44
+ {% endif %}
45
+ {% else %}
46
+ {% assign author_name = author_data.name %}
47
+ {% endunless %}
48
+
49
+ {% comment %} Style-specific defaults {% endcomment %}
50
+ {% if style == "full" %}
51
+ {% assign show_bio = include.show_bio | default: true %}
52
+ {% assign show_social = include.show_social | default: true %}
53
+ {% elsif style == "inline" %}
54
+ {% assign show_bio = false %}
55
+ {% assign show_social = false %}
56
+ {% else %}
57
+ {% assign show_bio = include.show_bio | default: false %}
58
+ {% assign show_social = include.show_social | default: false %}
59
+ {% endif %}
60
+
61
+ <!-- ========================== -->
62
+ <!-- INLINE STYLE -->
63
+ <!-- ========================== -->
64
+ {% if style == "inline" %}
65
+ <span class="author-inline d-inline-flex align-items-center">
66
+ {% if author_data.avatar %}
67
+ <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ author_data.avatar }}"
68
+ alt="{{ author_name }}"
69
+ class="rounded-circle me-2"
70
+ width="24" height="24"
71
+ style="object-fit: cover;">
72
+ {% else %}
73
+ <i class="bi bi-person-circle me-1"></i>
74
+ {% endif %}
75
+ <span class="author-name">{{ author_name }}</span>
76
+ </span>
77
+
78
+ <!-- ========================== -->
79
+ <!-- COMPACT STYLE (default) -->
80
+ <!-- ========================== -->
81
+ {% elsif style == "compact" %}
82
+ <div class="author-card-compact d-flex align-items-center">
83
+ {% if author_data.avatar %}
84
+ <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ author_data.avatar }}"
85
+ alt="{{ author_name }}"
86
+ class="rounded-circle me-3"
87
+ width="48" height="48"
88
+ style="object-fit: cover;">
89
+ {% else %}
90
+ <div class="rounded-circle bg-primary text-white d-flex align-items-center justify-content-center me-3"
91
+ style="width: 48px; height: 48px;">
92
+ <i class="bi bi-person fs-5"></i>
93
+ </div>
94
+ {% endif %}
95
+ <div>
96
+ <strong class="d-block">{{ author_name }}</strong>
97
+ {% if author_data.role %}
98
+ <small class="text-muted">{{ author_data.role }}</small>
99
+ {% endif %}
100
+ {% if show_bio and author_data.bio %}
101
+ <p class="text-muted small mb-0 mt-1">{{ author_data.bio | truncate: 100 }}</p>
102
+ {% endif %}
103
+ </div>
104
+ </div>
105
+
106
+ <!-- ========================== -->
107
+ <!-- FULL STYLE -->
108
+ <!-- ========================== -->
109
+ {% elsif style == "full" %}
110
+ <div class="author-card card border-0 shadow-sm">
111
+ <div class="card-body">
112
+ <div class="d-flex align-items-start">
113
+ {% if author_data.avatar %}
114
+ <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ author_data.avatar }}"
115
+ alt="{{ author_name }}"
116
+ class="rounded-circle me-3"
117
+ width="80" height="80"
118
+ style="object-fit: cover;">
119
+ {% else %}
120
+ <div class="rounded-circle bg-primary text-white d-flex align-items-center justify-content-center me-3"
121
+ style="width: 80px; height: 80px;">
122
+ <i class="bi bi-person fs-1"></i>
123
+ </div>
124
+ {% endif %}
125
+ <div class="flex-grow-1">
126
+ <h5 class="card-title mb-1">{{ author_name }}</h5>
127
+ {% if author_data.role %}
128
+ <p class="text-primary mb-2">{{ author_data.role }}</p>
129
+ {% endif %}
130
+ {% if show_bio and author_data.bio %}
131
+ <p class="card-text text-muted">{{ author_data.bio }}</p>
132
+ {% endif %}
133
+
134
+ {% if show_social %}
135
+ <div class="author-social d-flex gap-2 mt-2">
136
+ {% if author_data.github %}
137
+ <a href="https://github.com/{{ author_data.github }}"
138
+ class="btn btn-sm btn-outline-secondary"
139
+ target="_blank" rel="noopener" title="GitHub">
140
+ <i class="bi bi-github"></i>
141
+ </a>
142
+ {% endif %}
143
+ {% if author_data.twitter %}
144
+ <a href="https://twitter.com/{{ author_data.twitter }}"
145
+ class="btn btn-sm btn-outline-secondary"
146
+ target="_blank" rel="noopener" title="Twitter">
147
+ <i class="bi bi-twitter"></i>
148
+ </a>
149
+ {% endif %}
150
+ {% if author_data.linkedin %}
151
+ <a href="https://linkedin.com/in/{{ author_data.linkedin }}"
152
+ class="btn btn-sm btn-outline-secondary"
153
+ target="_blank" rel="noopener" title="LinkedIn">
154
+ <i class="bi bi-linkedin"></i>
155
+ </a>
156
+ {% endif %}
157
+ {% if author_data.website %}
158
+ <a href="{{ author_data.website }}"
159
+ class="btn btn-sm btn-outline-secondary"
160
+ target="_blank" rel="noopener" title="Website">
161
+ <i class="bi bi-globe"></i>
162
+ </a>
163
+ {% endif %}
164
+ {% if author_data.email %}
165
+ <a href="mailto:{{ author_data.email }}"
166
+ class="btn btn-sm btn-outline-secondary"
167
+ title="Email">
168
+ <i class="bi bi-envelope"></i>
169
+ </a>
170
+ {% endif %}
171
+ </div>
172
+ {% endif %}
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ {% endif %}