jekyll-theme-zer0 0.6.0 → 0.7.1

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,247 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+ <!--
5
+ ===================================================================
6
+ CATEGORY LAYOUT - Blog category archive page
7
+ ===================================================================
8
+
9
+ File: category.html
10
+ Path: _layouts/category.html
11
+ Inherits: default.html (which inherits root.html)
12
+ Purpose: Display posts filtered by category with responsive card grid
13
+
14
+ Template Logic:
15
+ - Filters site.posts by page.category front matter variable
16
+ - Displays category header with description and post count
17
+ - Uses post-card.html component for consistent card rendering
18
+ - Implements responsive Bootstrap 5 grid layout
19
+ - Supports optional category icon and description
20
+
21
+ Front Matter Variables:
22
+ - page.category: Category name to filter posts (required)
23
+ - page.title: Display title for the page
24
+ - page.description: Category description text
25
+ - page.icon: Bootstrap Icon class for category (optional)
26
+
27
+ Dependencies:
28
+ - _includes/components/post-card.html: Reusable post card component
29
+ - Bootstrap 5 grid and card components
30
+ - Bootstrap Icons for category icons
31
+ ===================================================================
32
+ -->
33
+
34
+ <!-- ========================== -->
35
+ <!-- CATEGORY HEADER SECTION -->
36
+ <!-- ========================== -->
37
+ <header class="category-header mb-5">
38
+ <div class="d-flex align-items-center mb-3">
39
+ {% if page.icon %}
40
+ <i class="bi bi-{{ page.icon }} fs-1 me-3 text-primary"></i>
41
+ {% endif %}
42
+ <div>
43
+ <h1 class="display-5 mb-0">{{ page.title }}</h1>
44
+ {% assign category_posts = site.posts | where_exp: "post", "post.categories contains page.category" %}
45
+ <p class="text-muted mb-0">
46
+ <i class="bi bi-collection me-1"></i>
47
+ {{ category_posts.size }} {{ category_posts.size | pluralize: "article", "articles" }}
48
+ </p>
49
+ </div>
50
+ </div>
51
+
52
+ {% if page.description %}
53
+ <p class="lead text-muted">{{ page.description }}</p>
54
+ {% endif %}
55
+
56
+ <!-- Category description from page content -->
57
+ {% if content != blank %}
58
+ <div class="category-description mb-4">
59
+ {{ content }}
60
+ </div>
61
+ {% endif %}
62
+ </header>
63
+
64
+ <!-- ========================== -->
65
+ <!-- CATEGORY FILTER BADGES -->
66
+ <!-- ========================== -->
67
+ <!-- Quick filter for subcategories or related tags -->
68
+ {% assign all_tags = "" | split: "" %}
69
+ {% for post in category_posts %}
70
+ {% for tag in post.tags %}
71
+ {% unless all_tags contains tag %}
72
+ {% assign all_tags = all_tags | push: tag %}
73
+ {% endunless %}
74
+ {% endfor %}
75
+ {% endfor %}
76
+
77
+ {% if all_tags.size > 0 %}
78
+ <div class="category-filters mb-4">
79
+ <span class="text-muted me-2">Filter by tag:</span>
80
+ {% for tag in all_tags limit: 8 %}
81
+ <a href="{{ site.baseurl }}/tags/#{{ tag | slugify }}"
82
+ class="badge bg-secondary text-decoration-none me-1 mb-1">
83
+ {{ tag }}
84
+ </a>
85
+ {% endfor %}
86
+ {% if all_tags.size > 8 %}
87
+ <a href="{{ site.baseurl }}/tags/" class="badge bg-outline-secondary text-decoration-none">
88
+ +{{ all_tags.size | minus: 8 }} more
89
+ </a>
90
+ {% endif %}
91
+ </div>
92
+ {% endif %}
93
+
94
+ <!-- ========================== -->
95
+ <!-- FEATURED POSTS SECTION -->
96
+ <!-- ========================== -->
97
+ {% assign featured_in_category = category_posts | where: "featured", true %}
98
+ {% if featured_in_category.size > 0 %}
99
+ <section class="featured-posts mb-5">
100
+ <h2 class="h4 mb-3">
101
+ <i class="bi bi-star-fill text-warning me-2"></i>Featured in {{ page.category }}
102
+ </h2>
103
+ <div class="row row-cols-1 row-cols-md-2 g-4 mb-4">
104
+ {% for fpost in featured_in_category limit: 2 %}
105
+ <div class="col">
106
+ <div class="card h-100 border-0 shadow-sm">
107
+ <div class="position-relative">
108
+ {% if fpost.featured %}
109
+ <span class="badge bg-warning text-dark position-absolute top-0 end-0 m-2 z-1">
110
+ <i class="bi bi-star-fill me-1"></i>Featured
111
+ </span>
112
+ {% endif %}
113
+ <a href="{{ fpost.url | relative_url }}" class="text-decoration-none">
114
+ {% if fpost.preview %}
115
+ <img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ fpost.preview }}"
116
+ class="card-img-top" alt="{{ fpost.title }}" loading="lazy">
117
+ {% else %}
118
+ <img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ site.teaser }}"
119
+ class="card-img-top" alt="Preview" loading="lazy">
120
+ {% endif %}
121
+ </a>
122
+ </div>
123
+ <div class="card-body">
124
+ <h5 class="card-title">
125
+ <a href="{{ fpost.url | relative_url }}" class="text-decoration-none text-body-emphasis">
126
+ {{ fpost.title }}
127
+ </a>
128
+ </h5>
129
+ <p class="card-text text-muted small">{{ fpost.excerpt | strip_html | truncate: 120 }}</p>
130
+ </div>
131
+ <div class="card-footer bg-transparent">
132
+ <small class="text-muted">
133
+ <i class="bi bi-calendar me-1"></i>{{ fpost.date | date: "%b %d, %Y" }}
134
+ </small>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ {% endfor %}
139
+ </div>
140
+ </section>
141
+ {% endif %}
142
+
143
+ <!-- ========================== -->
144
+ <!-- ALL POSTS GRID -->
145
+ <!-- ========================== -->
146
+ <section class="all-posts">
147
+ <h2 class="h4 mb-3 pb-2 border-bottom">
148
+ <i class="bi bi-newspaper me-2"></i>All {{ page.category }} Articles
149
+ </h2>
150
+
151
+ {% if category_posts.size > 0 %}
152
+ <!-- Responsive card grid: 1 column mobile, 2 tablet, 3 desktop -->
153
+ <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
154
+ {% for cpost in category_posts %}
155
+ <div class="col">
156
+ <div class="card h-100 border-0 shadow-sm">
157
+ <div class="position-relative">
158
+ {% if cpost.breaking %}
159
+ <span class="badge bg-danger position-absolute top-0 start-0 m-2 z-1">
160
+ <i class="bi bi-lightning-fill me-1"></i>Breaking
161
+ </span>
162
+ {% endif %}
163
+ {% if cpost.featured %}
164
+ <span class="badge bg-warning text-dark position-absolute top-0 end-0 m-2 z-1">
165
+ <i class="bi bi-star-fill me-1"></i>Featured
166
+ </span>
167
+ {% endif %}
168
+ <a href="{{ cpost.url | relative_url }}" class="text-decoration-none">
169
+ {% if cpost.preview %}
170
+ <img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ cpost.preview }}"
171
+ class="card-img-top" alt="{{ cpost.title }}" loading="lazy">
172
+ {% else %}
173
+ <img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ site.teaser }}"
174
+ class="card-img-top" alt="Preview" loading="lazy">
175
+ {% endif %}
176
+ </a>
177
+ </div>
178
+ <div class="card-body d-flex flex-column">
179
+ <h5 class="card-title mb-2">
180
+ <a href="{{ cpost.url | relative_url }}" class="text-decoration-none text-body-emphasis">
181
+ {{ cpost.title | truncate: 60 }}
182
+ </a>
183
+ </h5>
184
+ <p class="card-text text-muted small flex-grow-1">
185
+ {{ cpost.excerpt | strip_html | truncate: 100 }}
186
+ </p>
187
+ </div>
188
+ <div class="card-footer bg-transparent border-top-0">
189
+ <div class="d-flex justify-content-between align-items-center small text-muted">
190
+ <span>
191
+ <i class="bi bi-calendar me-1"></i>{{ cpost.date | date: "%b %d, %Y" }}
192
+ </span>
193
+ {% if cpost.author %}
194
+ <span>
195
+ <i class="bi bi-person me-1"></i>{{ cpost.author | truncate: 15 }}
196
+ </span>
197
+ {% endif %}
198
+ </div>
199
+ </div>
200
+ </div>
201
+ </div>
202
+ {% endfor %}
203
+ </div>
204
+
205
+ <!-- ========================== -->
206
+ <!-- PAGINATION (if many posts) -->
207
+ <!-- ========================== -->
208
+ {% if category_posts.size > 12 %}
209
+ <nav aria-label="Category pagination" class="mt-5">
210
+ <p class="text-center text-muted">
211
+ Showing all {{ category_posts.size }} articles.
212
+ <a href="#" class="text-decoration-none">Back to top <i class="bi bi-arrow-up"></i></a>
213
+ </p>
214
+ </nav>
215
+ {% endif %}
216
+
217
+ {% else %}
218
+ <!-- Empty state -->
219
+ <div class="text-center py-5">
220
+ <i class="bi bi-inbox fs-1 text-muted"></i>
221
+ <p class="text-muted mt-3">No articles in this category yet.</p>
222
+ <a href="{{ site.baseurl }}/posts/" class="btn btn-outline-primary">
223
+ Browse all posts
224
+ </a>
225
+ </div>
226
+ {% endif %}
227
+ </section>
228
+
229
+ <!-- ========================== -->
230
+ <!-- RELATED CATEGORIES -->
231
+ <!-- ========================== -->
232
+ <aside class="related-categories mt-5 pt-4 border-top">
233
+ <h3 class="h5 mb-3">Explore Other Categories</h3>
234
+ <div class="d-flex flex-wrap gap-2">
235
+ {% for nav_item in site.data.navigation.posts %}
236
+ {% unless nav_item.title == page.category %}
237
+ <a href="{{ nav_item.url | relative_url }}"
238
+ class="btn btn-outline-secondary btn-sm">
239
+ {% if nav_item.icon %}
240
+ <i class="bi bi-{{ nav_item.icon }} me-1"></i>
241
+ {% endif %}
242
+ {{ nav_item.title }}
243
+ </a>
244
+ {% endunless %}
245
+ {% endfor %}
246
+ </div>
247
+ </aside>
@@ -2,32 +2,281 @@
2
2
  layout: default
3
3
  ---
4
4
  <!--
5
- Simple journals layout for debugging stack overflow issue
5
+ ===================================================================
6
+ JOURNALS LAYOUT - Blog Post and Article Display
7
+ ===================================================================
8
+
9
+ File: journals.html
10
+ Path: _layouts/journals.html
11
+ Inherits: default.html (which inherits root.html)
12
+ Purpose: Display blog posts with rich metadata, navigation, and comments
13
+
14
+ Template Logic:
15
+ - Displays article content with semantic HTML5 markup
16
+ - Shows post metadata (author, date, reading time, tags)
17
+ - Provides previous/next post navigation
18
+ - Integrates Giscus comment system (when enabled)
19
+ - Uses Schema.org BlogPosting markup for SEO
20
+
21
+ Dependencies:
22
+ - content/giscus.html: GitHub Discussions comment system
23
+ - site.giscus configuration for comment system
24
+ - Bootstrap 5 pagination components
25
+ ===================================================================
6
26
  -->
7
27
 
8
- <div id="main" role="main">
9
- <div class="archive">
10
- <h1 id="page-title">{{ page.title }}</h1>
11
- {{ content }}
28
+ <article id="main" class="post h-entry" role="main" itemscope itemtype="https://schema.org/BlogPosting">
29
+
30
+ <!-- ================================ -->
31
+ <!-- ARTICLE HEADER -->
32
+ <!-- ================================ -->
33
+ <header class="post-header mb-4">
34
+ <h1 id="page-title" class="post-title p-name" itemprop="headline">
35
+ {{ page.title }}
36
+ </h1>
37
+
38
+ <!-- Post Metadata -->
39
+ <div class="post-meta text-muted mb-3">
40
+ <!-- Author -->
41
+ {% if page.author %}
42
+ <span class="post-author" itemprop="author" itemscope itemtype="https://schema.org/Person">
43
+ <i class="bi bi-person me-1"></i>
44
+ <span itemprop="name">{{ page.author }}</span>
45
+ </span>
46
+ <span class="mx-2">•</span>
47
+ {% endif %}
48
+
49
+ <!-- Publication Date -->
50
+ {% if page.date %}
51
+ <time class="post-date dt-published" datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">
52
+ <i class="bi bi-calendar me-1"></i>
53
+ {{ page.date | date: "%B %d, %Y" }}
54
+ </time>
55
+ <span class="mx-2">•</span>
56
+ {% endif %}
57
+
58
+ <!-- Reading Time -->
59
+ {% assign words = content | number_of_words %}
60
+ {% assign minutes = words | divided_by: 200 %}
61
+ {% if minutes < 1 %}{% assign minutes = 1 %}{% endif %}
62
+ <span class="reading-time">
63
+ <i class="bi bi-clock me-1"></i>
64
+ {{ minutes }} min read
65
+ </span>
66
+
67
+ <!-- Last Modified -->
68
+ {% if page.lastmod %}
69
+ <span class="mx-2">•</span>
70
+ <span class="post-updated" itemprop="dateModified" content="{{ page.lastmod | date_to_xmlschema }}">
71
+ <i class="bi bi-pencil me-1"></i>
72
+ Updated: {{ page.lastmod | date: "%B %d, %Y" }}
73
+ </span>
74
+ {% endif %}
12
75
  </div>
13
76
 
14
- <!-- Simple navigation without complex logic -->
15
- <nav aria-label="Page navigation">
16
- <ul class="pagination pagination-lg justify-content-center">
17
- <li class="page-item">
18
- {% if page.previous %}
19
- <a class="page-link" href="{{ page.previous.url }}">Previous</a>
20
- {% else %}
21
- <span class="page-link disabled">Previous</span>
77
+ <!-- Tags -->
78
+ {% if page.tags and page.tags.size > 0 %}
79
+ <div class="post-tags mb-3">
80
+ {% for tag in page.tags %}
81
+ <a href="{{ site.baseurl }}/tags/#{{ tag | slugify }}" class="badge bg-secondary text-decoration-none me-1">
82
+ <i class="bi bi-tag me-1"></i>{{ tag }}
83
+ </a>
84
+ {% endfor %}
85
+ </div>
86
+ {% endif %}
87
+ </header>
88
+
89
+ <!-- ================================ -->
90
+ <!-- ARTICLE CONTENT -->
91
+ <!-- ================================ -->
92
+ <div class="post-content e-content" itemprop="articleBody">
93
+ {{ content }}
94
+ </div>
95
+
96
+ <!-- ================================ -->
97
+ <!-- AUTHOR BIO SECTION -->
98
+ <!-- ================================ -->
99
+ {% if page.author %}
100
+ {% assign author_key = page.author %}
101
+ {% assign author_data = site.data.authors[author_key] %}
102
+ {% unless author_data %}
103
+ {% assign author_data = site.data.authors.default %}
104
+ {% assign author_name = page.author %}
105
+ {% else %}
106
+ {% assign author_name = author_data.name %}
107
+ {% endunless %}
108
+
109
+ <aside class="author-section my-5 pt-4 border-top">
110
+ <h3 class="h5 mb-3">
111
+ <i class="bi bi-person-badge me-2"></i>About the Author
112
+ </h3>
113
+ <div class="author-card card border-0 shadow-sm">
114
+ <div class="card-body">
115
+ <div class="d-flex align-items-start">
116
+ {% if author_data.avatar %}
117
+ <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ author_data.avatar }}"
118
+ alt="{{ author_name }}"
119
+ class="rounded-circle me-3"
120
+ width="80" height="80"
121
+ style="object-fit: cover;">
122
+ {% else %}
123
+ <div class="rounded-circle bg-primary text-white d-flex align-items-center justify-content-center me-3"
124
+ style="width: 80px; height: 80px;">
125
+ <i class="bi bi-person fs-1"></i>
126
+ </div>
127
+ {% endif %}
128
+ <div class="flex-grow-1">
129
+ <h5 class="card-title mb-1">{{ author_name }}</h5>
130
+ {% if author_data.role %}
131
+ <p class="text-primary mb-2">{{ author_data.role }}</p>
132
+ {% endif %}
133
+ {% if author_data.bio %}
134
+ <p class="card-text text-muted">{{ author_data.bio }}</p>
135
+ {% endif %}
136
+ <div class="author-social d-flex gap-2 mt-2">
137
+ {% if author_data.github %}
138
+ <a href="https://github.com/{{ author_data.github }}"
139
+ class="btn btn-sm btn-outline-secondary"
140
+ target="_blank" rel="noopener" title="GitHub">
141
+ <i class="bi bi-github"></i>
142
+ </a>
143
+ {% endif %}
144
+ {% if author_data.twitter %}
145
+ <a href="https://twitter.com/{{ author_data.twitter }}"
146
+ class="btn btn-sm btn-outline-secondary"
147
+ target="_blank" rel="noopener" title="Twitter">
148
+ <i class="bi bi-twitter"></i>
149
+ </a>
22
150
  {% endif %}
23
- </li>
24
- <li class="page-item">
25
- {% if page.next %}
26
- <a class="page-link" href="{{ page.next.url }}">Next</a>
27
- {% else %}
28
- <span class="page-link disabled">Next</span>
151
+ {% if author_data.website %}
152
+ <a href="{{ author_data.website }}"
153
+ class="btn btn-sm btn-outline-secondary"
154
+ target="_blank" rel="noopener" title="Website">
155
+ <i class="bi bi-globe"></i>
156
+ </a>
29
157
  {% endif %}
30
- </li>
31
- </ul>
32
- </nav>
33
- </div>
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ </aside>
164
+ {% endif %}
165
+
166
+ <!-- ================================ -->
167
+ <!-- RELATED POSTS SECTION -->
168
+ <!-- ================================ -->
169
+ {% if page.tags.size > 0 %}
170
+ {% assign related_posts = "" | split: "" %}
171
+ {% for tag in page.tags %}
172
+ {% assign tagged = site.posts | where_exp: "post", "post.tags contains tag" | where_exp: "post", "post.url != page.url" %}
173
+ {% for post in tagged %}
174
+ {% unless related_posts contains post %}
175
+ {% assign related_posts = related_posts | push: post %}
176
+ {% endunless %}
177
+ {% endfor %}
178
+ {% endfor %}
179
+ {% assign related_posts = related_posts | slice: 0, 3 %}
180
+
181
+ {% if related_posts.size > 0 %}
182
+ <aside class="related-posts my-5 pt-4 border-top">
183
+ <h3 class="h5 mb-3">
184
+ <i class="bi bi-journal-text me-2"></i>Related Posts
185
+ </h3>
186
+ <div class="row row-cols-1 row-cols-md-3 g-3">
187
+ {% for rpost in related_posts %}
188
+ <div class="col">
189
+ <div class="card h-100 border-0 shadow-sm">
190
+ <a href="{{ rpost.url | relative_url }}" class="text-decoration-none">
191
+ {% if rpost.preview %}
192
+ <img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ rpost.preview }}"
193
+ class="card-img-top" alt="{{ rpost.title }}" loading="lazy">
194
+ {% else %}
195
+ <img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ site.teaser }}"
196
+ class="card-img-top" alt="Preview" loading="lazy">
197
+ {% endif %}
198
+ </a>
199
+ <div class="card-body">
200
+ <h6 class="card-title mb-1">
201
+ <a href="{{ rpost.url | relative_url }}" class="text-decoration-none text-body-emphasis">
202
+ {{ rpost.title | truncate: 60 }}
203
+ </a>
204
+ </h6>
205
+ <small class="text-muted">
206
+ <i class="bi bi-calendar me-1"></i>{{ rpost.date | date: "%b %d, %Y" }}
207
+ </small>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ {% endfor %}
212
+ </div>
213
+ </aside>
214
+ {% endif %}
215
+ {% endif %}
216
+
217
+ <!-- ================================ -->
218
+ <!-- POST NAVIGATION -->
219
+ <!-- ================================ -->
220
+ <nav class="post-navigation my-5 pt-4 border-top" aria-label="Post navigation">
221
+ <div class="row g-3">
222
+ <!-- Previous Post -->
223
+ <div class="col-md-6">
224
+ {% if page.previous %}
225
+ <a href="{{ page.previous.url | relative_url }}" class="card h-100 text-decoration-none border-0 shadow-sm">
226
+ <div class="card-body">
227
+ <small class="text-muted d-block mb-1">
228
+ <i class="bi bi-chevron-left me-1"></i>Previous
229
+ </small>
230
+ <span class="text-body-emphasis">{{ page.previous.title | truncate: 50 }}</span>
231
+ </div>
232
+ </a>
233
+ {% else %}
234
+ <div class="card h-100 border-0 bg-body-tertiary opacity-50">
235
+ <div class="card-body">
236
+ <small class="text-muted d-block mb-1">
237
+ <i class="bi bi-chevron-left me-1"></i>Previous
238
+ </small>
239
+ <span class="text-muted">No previous post</span>
240
+ </div>
241
+ </div>
242
+ {% endif %}
243
+ </div>
244
+
245
+ <!-- Next Post -->
246
+ <div class="col-md-6">
247
+ {% if page.next %}
248
+ <a href="{{ page.next.url | relative_url }}" class="card h-100 text-decoration-none border-0 shadow-sm">
249
+ <div class="card-body text-end">
250
+ <small class="text-muted d-block mb-1">
251
+ Next<i class="bi bi-chevron-right ms-1"></i>
252
+ </small>
253
+ <span class="text-body-emphasis">{{ page.next.title | truncate: 50 }}</span>
254
+ </div>
255
+ </a>
256
+ {% else %}
257
+ <div class="card h-100 border-0 bg-body-tertiary opacity-50">
258
+ <div class="card-body text-end">
259
+ <small class="text-muted d-block mb-1">
260
+ Next<i class="bi bi-chevron-right ms-1"></i>
261
+ </small>
262
+ <span class="text-muted">No next post</span>
263
+ </div>
264
+ </div>
265
+ {% endif %}
266
+ </div>
267
+ </div>
268
+ </nav>
269
+
270
+ <!-- ================================ -->
271
+ <!-- COMMENT SYSTEM -->
272
+ <!-- ================================ -->
273
+ {% if page.comments != false and site.giscus %}
274
+ <section class="post-comments mt-5 pt-4 border-top" id="comments">
275
+ <h2 class="h4 mb-4">
276
+ <i class="bi bi-chat-dots me-2"></i>Comments
277
+ </h2>
278
+ {% include content/giscus.html %}
279
+ </section>
280
+ {% endif %}
281
+
282
+ </article>
data/_layouts/tag.html ADDED
@@ -0,0 +1,111 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+ <!--
5
+ ===================================================================
6
+ TAG LAYOUT - Tag archive page displaying posts by tag
7
+ ===================================================================
8
+
9
+ File: tag.html
10
+ Path: _layouts/tag.html
11
+ Inherits: default.html (which inherits root.html)
12
+ Purpose: Display posts filtered by a specific tag
13
+
14
+ Template Logic:
15
+ - Filters site.posts by page.tag front matter variable
16
+ - Displays tag header with post count
17
+ - Uses post-card.html component for consistent rendering
18
+ - Shows related tags for discovery
19
+
20
+ Front Matter Variables:
21
+ - page.tag: Tag name to filter posts (required)
22
+ - page.title: Display title for the page
23
+ - page.description: Tag description text (optional)
24
+
25
+ Dependencies:
26
+ - _includes/components/post-card.html: Reusable post card component
27
+ - Bootstrap 5 grid and badge components
28
+ ===================================================================
29
+ -->
30
+
31
+ <!-- ========================== -->
32
+ <!-- TAG HEADER SECTION -->
33
+ <!-- ========================== -->
34
+ <header class="tag-header mb-5">
35
+ <nav aria-label="breadcrumb" class="mb-3">
36
+ <ol class="breadcrumb">
37
+ <li class="breadcrumb-item"><a href="{{ site.baseurl }}/">Home</a></li>
38
+ <li class="breadcrumb-item"><a href="{{ site.baseurl }}/tags/">Tags</a></li>
39
+ <li class="breadcrumb-item active" aria-current="page">{{ page.tag }}</li>
40
+ </ol>
41
+ </nav>
42
+
43
+ <div class="d-flex align-items-center mb-3">
44
+ <i class="bi bi-tag-fill fs-1 me-3 text-primary"></i>
45
+ <div>
46
+ <h1 class="display-5 mb-0">{{ page.title | default: page.tag }}</h1>
47
+ {% assign tag_posts = site.posts | where_exp: "post", "post.tags contains page.tag" %}
48
+ <p class="text-muted mb-0">
49
+ <i class="bi bi-file-earmark-text me-1"></i>
50
+ {{ tag_posts.size }} {{ tag_posts.size | pluralize: "post", "posts" }} tagged
51
+ </p>
52
+ </div>
53
+ </div>
54
+
55
+ {% if page.description %}
56
+ <p class="lead text-muted">{{ page.description }}</p>
57
+ {% endif %}
58
+
59
+ {{ content }}
60
+ </header>
61
+
62
+ <!-- ========================== -->
63
+ <!-- TAGGED POSTS GRID -->
64
+ <!-- ========================== -->
65
+ <section class="tagged-posts">
66
+ {% if tag_posts.size > 0 %}
67
+ <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
68
+ {% for post in tag_posts %}
69
+ {% include components/post-card.html post=post %}
70
+ {% endfor %}
71
+ </div>
72
+ {% else %}
73
+ <!-- Empty state -->
74
+ <div class="text-center py-5">
75
+ <i class="bi bi-tag fs-1 text-muted"></i>
76
+ <p class="text-muted mt-3">No posts with this tag yet.</p>
77
+ <a href="{{ site.baseurl }}/tags/" class="btn btn-outline-primary">
78
+ Browse all tags
79
+ </a>
80
+ </div>
81
+ {% endif %}
82
+ </section>
83
+
84
+ <!-- ========================== -->
85
+ <!-- RELATED TAGS -->
86
+ <!-- ========================== -->
87
+ {% comment %} Find tags that appear with this tag {% endcomment %}
88
+ {% assign related_tags = "" | split: "" %}
89
+ {% for post in tag_posts %}
90
+ {% for tag in post.tags %}
91
+ {% unless tag == page.tag or related_tags contains tag %}
92
+ {% assign related_tags = related_tags | push: tag %}
93
+ {% endunless %}
94
+ {% endfor %}
95
+ {% endfor %}
96
+
97
+ {% if related_tags.size > 0 %}
98
+ <aside class="related-tags mt-5 pt-4 border-top">
99
+ <h3 class="h5 mb-3">
100
+ <i class="bi bi-tags me-2"></i>Related Tags
101
+ </h3>
102
+ <div class="d-flex flex-wrap gap-2">
103
+ {% for tag in related_tags limit: 12 %}
104
+ <a href="{{ site.baseurl }}/tags/#{{ tag | slugify }}"
105
+ class="badge bg-secondary text-decoration-none fs-6">
106
+ {{ tag }}
107
+ </a>
108
+ {% endfor %}
109
+ </div>
110
+ </aside>
111
+ {% endif %}