ultimate-jekyll-manager 1.7.2 → 1.8.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +69 -1
  2. package/CLAUDE.md +36 -15
  3. package/README.md +4 -2
  4. package/TODO-AUTH-TESTING.md +1 -1
  5. package/dist/assets/themes/newsflash/README.md +58 -0
  6. package/dist/assets/themes/newsflash/_config.scss +138 -0
  7. package/dist/assets/themes/newsflash/_theme.js +27 -0
  8. package/dist/assets/themes/newsflash/_theme.scss +37 -0
  9. package/dist/assets/themes/newsflash/css/base/_mixins.scss +50 -0
  10. package/dist/assets/themes/newsflash/css/base/_root.scss +134 -0
  11. package/dist/assets/themes/newsflash/css/base/_typography.scss +49 -0
  12. package/dist/assets/themes/newsflash/css/base/_utilities.scss +58 -0
  13. package/dist/assets/themes/newsflash/css/components/_badges.scss +65 -0
  14. package/dist/assets/themes/newsflash/css/components/_buttons.scss +139 -0
  15. package/dist/assets/themes/newsflash/css/components/_cards.scss +52 -0
  16. package/dist/assets/themes/newsflash/css/components/_editorial.scss +182 -0
  17. package/dist/assets/themes/newsflash/css/components/_forms.scss +75 -0
  18. package/dist/assets/themes/newsflash/css/components/_infinite-scroll.scss +102 -0
  19. package/dist/assets/themes/newsflash/css/components/_panels.scss +91 -0
  20. package/dist/assets/themes/newsflash/css/components/_ticker.scss +70 -0
  21. package/dist/assets/themes/newsflash/css/layout/_general.scss +264 -0
  22. package/dist/assets/themes/newsflash/css/layout/_navigation.scss +164 -0
  23. package/dist/assets/themes/newsflash/js/initialize-tooltips.js +20 -0
  24. package/dist/assets/themes/newsflash/js/masthead-scroll.js +29 -0
  25. package/dist/assets/themes/newsflash/pages/404/index.scss +27 -0
  26. package/dist/assets/themes/newsflash/pages/about/index.scss +70 -0
  27. package/dist/assets/themes/newsflash/pages/blog/index.scss +17 -0
  28. package/dist/assets/themes/newsflash/pages/blog/post.js +29 -0
  29. package/dist/assets/themes/newsflash/pages/blog/post.scss +164 -0
  30. package/dist/assets/themes/newsflash/pages/index.scss +159 -0
  31. package/dist/assets/themes/newsflash/pages/pricing/index.scss +194 -0
  32. package/dist/assets/themes/newsflash/pages/test/libraries/layers/index.js +9 -0
  33. package/dist/assets/themes/newsflash/pages/test/libraries/layers/index.scss +7 -0
  34. package/dist/commands/blogify.js +6 -3
  35. package/dist/commands/test.js +34 -5
  36. package/dist/defaults/CLAUDE.md +17 -4
  37. package/dist/defaults/dist/_includes/core/pricing/resolve-plan.html +59 -0
  38. package/dist/defaults/dist/_includes/themes/classy/frontend/sections/footer.html +20 -3
  39. package/dist/defaults/dist/_layouts/themes/classy/admin/core/minimal-viewport-locked.html +1 -1
  40. package/dist/defaults/dist/_layouts/themes/classy/admin/core/minimal.html +1 -1
  41. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html +5 -40
  42. package/dist/defaults/dist/_layouts/themes/neobrutalism/frontend/pages/pricing.html +33 -34
  43. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/core/base.html +61 -0
  44. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/404.html +86 -0
  45. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/about.html +353 -0
  46. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/categories/category.html +105 -0
  47. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/categories/index.html +93 -0
  48. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/index.html +373 -0
  49. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/post.html +289 -0
  50. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/tags/index.html +90 -0
  51. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/tags/tag.html +107 -0
  52. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/contact.html +340 -0
  53. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/index.html +522 -0
  54. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/pricing.html +485 -0
  55. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/team/index.html +207 -0
  56. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/team/member.html +134 -0
  57. package/dist/defaults/test/README.md +4 -0
  58. package/dist/gulp/tasks/jekyll.js +4 -2
  59. package/dist/test/runner.js +50 -3
  60. package/dist/test/suites/build/attach-log-file.test.js +102 -0
  61. package/dist/test/suites/build/theme-contract.test.js +173 -0
  62. package/dist/test/utils/extended-mode-warning.js +13 -0
  63. package/dist/utils/attach-log-file.js +70 -43
  64. package/docs/appearance.md +1 -0
  65. package/docs/assets.md +9 -0
  66. package/docs/audit.md +78 -7
  67. package/docs/build-system.md +57 -0
  68. package/docs/common-mistakes.md +15 -0
  69. package/docs/{project-structure.md → directory-structure.md} +1 -1
  70. package/docs/environment-detection.md +1 -1
  71. package/docs/javascript-libraries.md +38 -1
  72. package/docs/layouts-and-pages.md +146 -0
  73. package/docs/local-development.md +1 -8
  74. package/docs/logging.md +30 -0
  75. package/docs/migration.md +131 -0
  76. package/docs/no-inline-scripts.md +304 -0
  77. package/docs/purgecss.md +164 -0
  78. package/docs/seo.md +131 -4
  79. package/docs/templating.md +23 -0
  80. package/docs/test-boot-layer.md +1 -1
  81. package/docs/test-framework.md +56 -8
  82. package/docs/themes.md +254 -13
  83. package/logs/test.log +111 -0
  84. package/package.json +9 -8
@@ -0,0 +1,289 @@
1
+ ---
2
+ ### ALL PAGES ###
3
+ layout: themes/[ site.theme.id ]/frontend/core/base
4
+ asset_path: blog/post
5
+
6
+ ### THEME CONFIG ###
7
+ theme:
8
+ post:
9
+ image:
10
+ class: "img-fluid rounded-3 w-100 my-5"
11
+
12
+ ### WEB MANAGER CONFIG ###
13
+ web_manager:
14
+ socialSharing:
15
+ enabled: true
16
+
17
+ ### PAGE CONFIG ###
18
+ author_section:
19
+ author_position: "Staff Writer"
20
+
21
+ related_posts:
22
+ headline: "Read"
23
+ headline_accent: "next"
24
+ limit: 3
25
+
26
+ newsletter:
27
+ superheadline:
28
+ icon: "envelope"
29
+ text: "Newsletter"
30
+ headline: "Stay in the"
31
+ headline_accent: "loop"
32
+ subheadline: "Join our newsletter and get resources, curated content, and inspiration delivered straight to your inbox."
33
+ button:
34
+ text: "Submit"
35
+ form:
36
+ placeholder: "jonsnow@gmail.com"
37
+ ---
38
+
39
+ {% comment %}
40
+ NEWSFLASH BLOG POST
41
+ The editorial article: a reading-progress bar, centered article head
42
+ (kicker, serif display headline, byline row with share buttons), framed
43
+ hero image, drop-cap body with serif crossheads and volt highlights, tag
44
+ pills, a framed author card, comments, read-next tiles, and the vermilion
45
+ newsletter slab. Preserves classy's contracts: asset_path blog/post, the
46
+ load-bearing .blog-post-content class (main-layer ad injection), the
47
+ data-social-share div, giscus + adsense includes, author_section /
48
+ related_posts / newsletter frontmatter.
49
+ {% endcomment %}
50
+
51
+ <!-- Reading progress — filled by the theme page JS (pages/blog/post.js) -->
52
+ <div class="reading-progress" aria-hidden="true"><span></span></div>
53
+
54
+ <!-- Article Header & Content -->
55
+ <article class="pt-8">
56
+ <div class="container">
57
+ <!-- Article head — centered editorial opening -->
58
+ <div class="row justify-content-center">
59
+ <div class="col-xl-9 col-lg-10 col-md-12 col-12">
60
+ <header class="text-center mb-4" data-lazy="@class animation-slide-up">
61
+ {% if page.post.categories[0] %}
62
+ <span class="kicker mb-3">{{ page.post.categories[0] | uj_title_case }}</span>
63
+ {% else %}
64
+ <span class="kicker mb-3">Dispatch</span>
65
+ {% endif %}
66
+ <h1 class="display-4 mt-2 mb-0">{{ page.post.title }}</h1>
67
+ </header>
68
+
69
+ <!-- Byline row -->
70
+ <div class="d-flex flex-wrap justify-content-center align-items-center gap-3 mb-5" data-lazy="@class animation-slide-up">
71
+ <div class="d-flex align-items-center">
72
+ <a href="{%- uj_member page.post.author, "url" -%}" class="text-decoration-none me-2">
73
+ <div class="avatar avatar-md">
74
+ {%- uj_member page.post.author, "image-tag", class="rounded-circle" -%}
75
+ </div>
76
+ </a>
77
+ <div class="text-start">
78
+ <a href="{%- uj_member page.post.author, "url" -%}" class="text-decoration-none text-body fw-semibold d-block">
79
+ {%- uj_member page.post.author, "name" -%}
80
+ </a>
81
+ <span class="text-body-secondary small">
82
+ {% iftruthy page.resolved.author_section.author_position %}
83
+ {{ page.resolved.author_section.author_position }} ·
84
+ {% endiftruthy %}
85
+ <time datetime="{{ page.date | date_to_xmlschema }}">{{ page.date | date: "%b %d, %Y" }}</time>
86
+ </span>
87
+ </div>
88
+ </div>
89
+ <div class="vr d-none d-sm-block"></div>
90
+ <div class="d-flex align-items-center gap-2">
91
+ <span class="badge bg-primary">{% uj_readtime content %} min read</span>
92
+ <div data-social-share
93
+ data-platforms=""
94
+ data-title=""
95
+ data-description=""
96
+ data-url=""
97
+ data-size="sm"
98
+ data-labels="false">
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ </div>
104
+
105
+ <!-- Hero image — the framed splash -->
106
+ <div class="row justify-content-center">
107
+ <div class="col-xl-10 col-lg-10 col-md-12 col-12 mb-5" data-lazy="@class animation-slide-up">
108
+ <div class="art-frame article-hero shadow">
109
+ {%- uj_post page.post.id, "image-tag", class="w-100 blog-post-image" -%}
110
+ </div>
111
+ </div>
112
+ </div>
113
+
114
+ <!-- Body — drop cap, serif crossheads, volt marks -->
115
+ <div class="row justify-content-center mb-3">
116
+ <div class="blog-post-content col-xl-8 col-lg-8 col-md-12 col-12" data-lazy="@class animation-slide-up">
117
+ {{ content | uj_content_format }}
118
+ </div>
119
+ </div>
120
+
121
+ <!-- Ad -->
122
+ <div class="row justify-content-center mb-3">
123
+ <div class="col-xl-8 col-lg-8 col-md-12 col-12" data-lazy="@class animation-slide-up">
124
+ {% include /modules/adunits/adsense.html type="in-article" %}
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </article>
129
+
130
+ <!-- Tags, Author & Comments -->
131
+ <section class="pt-0">
132
+ <div class="container">
133
+ <!-- Tags -->
134
+ <div class="row justify-content-center mb-4">
135
+ <div class="col-xl-8 col-lg-8 col-md-12 col-12" data-lazy="@class animation-slide-up">
136
+ <div class="tag-row d-flex flex-wrap gap-2 pt-4">
137
+ {% assign tags = '' | split: ',' %}
138
+ {% for tag in page.post.tags %}
139
+ {% assign tag_fixed = tag | uj_title_case %}
140
+
141
+ {% unless tags contains tag_fixed %}
142
+ {% assign tags = tags | push: tag_fixed %}
143
+ {% endunless %}
144
+ {% endfor %}
145
+ {% for tag in tags %}
146
+ <a href="{{ site.url }}/blog/tags/{{ tag | slugify }}" class="btn btn-outline-dark btn-sm">{% uj_icon "tag", "me-1" %}{{ tag }}</a>
147
+ {% endfor %}
148
+ </div>
149
+ </div>
150
+ </div>
151
+
152
+ <!-- Author card -->
153
+ <div class="row justify-content-center mb-5">
154
+ <div class="col-xl-8 col-lg-8 col-md-12 col-12" data-lazy="@class animation-slide-up">
155
+ <div class="card p-4">
156
+ <div class="d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center gap-3">
157
+ <div class="d-flex">
158
+ <div class="flex-shrink-0">
159
+ <a href="{% uj_member page.post.author, "url" %}">
160
+ <div class="avatar avatar-lg">
161
+ {% uj_member page.post.author, "image-tag", class="rounded-circle" %}
162
+ </div>
163
+ </a>
164
+ </div>
165
+ <div class="ms-3">
166
+ <a href="{% uj_member page.post.author, "url" %}" class="text-body text-decoration-none">
167
+ <div class="h5 mb-0">
168
+ {%- uj_member page.post.author, "name" -%}
169
+ </div>
170
+ </a>
171
+ {% iftruthy page.resolved.author_section.author_position %}
172
+ <div class="text-body-secondary small">
173
+ {{ page.resolved.author_section.author_position }}
174
+ </div>
175
+ {% endiftruthy %}
176
+ <a href="{% uj_member page.post.author, "url" %}" class="small fw-bold text-decoration-none">
177
+ More from this author {% uj_icon "arrow-right", "ms-1" %}
178
+ </a>
179
+ </div>
180
+ </div>
181
+ <div data-social-share
182
+ data-platforms=""
183
+ data-title=""
184
+ data-description=""
185
+ data-url=""
186
+ data-size="sm"
187
+ data-labels="false">
188
+ </div>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+
194
+ <!-- Comments -->
195
+ <div class="row justify-content-center mb-5">
196
+ <div class="col-xl-8 col-lg-8 col-md-12 col-12" data-lazy="@class animation-slide-up">
197
+ {%- include modules/engagement/giscus.html -%}
198
+ </div>
199
+ </div>
200
+ </div>
201
+ </section>
202
+
203
+ <!-- Read Next -->
204
+ <section class="pt-0">
205
+ <div class="container">
206
+ <div class="section-head" data-lazy="@class animation-slide-up">
207
+ <h2>
208
+ {{ page.resolved.related_posts.headline }}
209
+ {% iftruthy page.resolved.related_posts.headline_accent %}
210
+ {{ page.resolved.related_posts.headline_accent }}
211
+ {% endiftruthy %}
212
+ </h2>
213
+ <span class="rule"></span>
214
+ <a href="{{ site.url }}/blog">View all {% uj_icon "arrow-right", "ms-1" %}</a>
215
+ </div>
216
+ <div class="row g-4">
217
+ <!-- Loop through posts based on configured limit -->
218
+ {% assign count = 0 %}
219
+ {% assign limit = page.resolved.related_posts.limit | default: 3 %}
220
+ {% for post in site.posts %}
221
+ {% if count < limit %}
222
+ {% if post.id != page.post.id %}
223
+ {% assign count = count | plus: 1 %}
224
+ <div class="col-xl-4 col-lg-6 col-md-12" data-lazy="@class animation-slide-up">
225
+ <a href="{{ site.url }}{{ post.url }}" class="story-card">
226
+ <div class="art-frame mb-3">
227
+ {%- uj_post post.post.id, "image-tag", class="w-100" -%}
228
+ </div>
229
+ {% if post.post.categories[0] %}
230
+ <span class="kicker mb-1">{{ post.post.categories[0] | uj_title_case }}</span>
231
+ {% endif %}
232
+ <h3 class="h5 my-2">{{ post.post.title }}</h3>
233
+ <p class="small text-body-secondary mb-0">
234
+ <b class="text-body">{%- uj_member post.post.author, "name" -%}</b>
235
+ · {% uj_readtime post.content %} min read
236
+ </p>
237
+ </a>
238
+ </div>
239
+ {% endif %}
240
+ {% endif %}
241
+ {% endfor %}
242
+ </div>
243
+ </div>
244
+ </section>
245
+
246
+ <!-- Newsletter — the vermilion slab -->
247
+ <section class="pt-0">
248
+ <div class="container">
249
+ <div class="row justify-content-center">
250
+ <div class="col-xl-8 col-lg-8 col-md-12 col-12">
251
+ <div class="card bg-primary text-white p-4 p-md-5 text-center position-relative overflow-hidden" data-lazy="@class animation-slide-up">
252
+ <div class="position-relative">
253
+ {% iftruthy page.resolved.newsletter.superheadline.text %}
254
+ <span class="badge bg-body-tertiary p-2 mb-3">
255
+ {% iftruthy page.resolved.newsletter.superheadline.icon %}
256
+ {% uj_icon page.resolved.newsletter.superheadline.icon, "me-1" %}
257
+ {% endiftruthy %}
258
+ {{ page.resolved.newsletter.superheadline.text }}
259
+ </span>
260
+ {% endiftruthy %}
261
+
262
+ {% iftruthy page.resolved.newsletter.headline %}
263
+ <h2 class="h2 fst-italic mb-3">
264
+ {{ page.resolved.newsletter.headline }}
265
+ {% iftruthy page.resolved.newsletter.headline_accent %}
266
+ {{ page.resolved.newsletter.headline_accent }}
267
+ {% endiftruthy %}
268
+ </h2>
269
+ {% endiftruthy %}
270
+
271
+ {% iftruthy page.resolved.newsletter.subheadline %}
272
+ <p class="lead text-white mb-4">{{ page.resolved.newsletter.subheadline }}</p>
273
+ {% endiftruthy %}
274
+
275
+ <form class="row g-3 justify-content-center needs-validation" action="{{ site.url }}/email-subscription">
276
+ <div class="col-md-7">
277
+ <label for="newsletter-email" class="visually-hidden">Email address</label>
278
+ <input type="email" name="email" id="newsletter-email" class="form-control form-control-lg" placeholder="{% iftruthy page.resolved.newsletter.form.placeholder %}{{ page.resolved.newsletter.form.placeholder }}{% endiftruthy %}" autocomplete="email" required>
279
+ </div>
280
+ <div class="col-md-auto">
281
+ <button type="submit" class="btn btn-dark btn-lg px-4">{% iftruthy page.resolved.newsletter.button.text %}{{ page.resolved.newsletter.button.text }}{% endiftruthy %}</button>
282
+ </div>
283
+ </form>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ </div>
288
+ </div>
289
+ </section>
@@ -0,0 +1,90 @@
1
+ ---
2
+ ### ALL PAGES ###
3
+ layout: themes/[ site.theme.id ]/frontend/core/base
4
+
5
+ ### PAGE CONFIG ###
6
+ # Hero Section
7
+ hero:
8
+ headline: "Follow the"
9
+ headline_accent: "threads"
10
+ subheadline: "Topics that run through our coverage — pick one and pull."
11
+ ---
12
+
13
+ {% comment %}
14
+ NEWSFLASH BLOG TAGS
15
+ Same data contract as classy's tags index (tags + counts built from
16
+ site.posts), presented as a topics wall of pill buttons with volt counts.
17
+ {% endcomment %}
18
+
19
+ <!-- ============================================ -->
20
+ <!-- HERO -->
21
+ <!-- ============================================ -->
22
+ <section class="pb-4">
23
+ <div class="container text-center" data-lazy="@class animation-slide-up">
24
+ <span class="kicker mb-3">{% uj_icon "tags", "me-1" %}Topics</span>
25
+ <h1 class="display-4 my-3">
26
+ {{ page.resolved.hero.headline }}
27
+ <span class="text-accent">{{ page.resolved.hero.headline_accent }}</span>
28
+ </h1>
29
+ {% iftruthy page.resolved.hero.subheadline %}
30
+ <p class="lead mb-0">{{ page.resolved.hero.subheadline }}</p>
31
+ {% endiftruthy %}
32
+ </div>
33
+ </section>
34
+
35
+ <!-- ============================================ -->
36
+ <!-- TOPICS WALL -->
37
+ <!-- ============================================ -->
38
+ <section class="pt-0">
39
+ <div class="container">
40
+ {% comment %} Build unique tags list with post counts {% endcomment %}
41
+ {% assign tags = '' | split: ',' %}
42
+ {% assign tag_counts = '' | split: ',' %}
43
+ {% for post in site.posts %}
44
+ {% for tag in post.post.tags %}
45
+ {% assign tag_fixed = tag | uj_title_case %}
46
+ {% unless tags contains tag_fixed %}
47
+ {% assign tags = tags | push: tag_fixed %}
48
+ {% comment %} Count posts for this tag {% endcomment %}
49
+ {% assign count = 0 %}
50
+ {% for p in site.posts %}
51
+ {% for t in p.post.tags %}
52
+ {% assign t_fixed = t | uj_title_case %}
53
+ {% if t_fixed == tag_fixed %}
54
+ {% assign count = count | plus: 1 %}
55
+ {% break %}
56
+ {% endif %}
57
+ {% endfor %}
58
+ {% endfor %}
59
+ {% assign tag_counts = tag_counts | push: count %}
60
+ {% endunless %}
61
+ {% endfor %}
62
+ {% endfor %}
63
+
64
+ <div class="row justify-content-center">
65
+ <div class="col-lg-10">
66
+ <div class="d-flex flex-wrap gap-2 justify-content-center" data-lazy="@class animation-slide-up">
67
+ {% for tag in tags %}
68
+ <a href="{{ site.url }}/blog/tags/{{ tag | slugify }}" class="btn btn-outline-dark d-inline-flex align-items-center gap-2">
69
+ {{ tag }}
70
+ <span class="badge bg-body-tertiary">{{ tag_counts[forloop.index0] }}</span>
71
+ </a>
72
+ {% endfor %}
73
+ </div>
74
+ </div>
75
+ </div>
76
+
77
+ {% if tags.size == 0 %}
78
+ <div class="text-center py-5" data-lazy="@class animation-slide-up">
79
+ <span class="kicker mb-2">No topics yet</span>
80
+ <h2 class="h3 mb-2">The threads start with the first story</h2>
81
+ <p class="text-body-secondary mb-4">Check back soon for tagged coverage.</p>
82
+ <a href="{{ site.url }}/blog" class="btn btn-primary">
83
+ {% uj_icon "arrow-left", "me-2" %}Back to the news
84
+ </a>
85
+ </div>
86
+ {% endif %}
87
+ </div>
88
+ </section>
89
+
90
+ {{ content | uj_content_format }}
@@ -0,0 +1,107 @@
1
+ ---
2
+ ### ALL PAGES ###
3
+ layout: themes/[ site.theme.id ]/frontend/core/base
4
+ asset_path: blog/tags/tag
5
+
6
+ ### PAGE CONFIG ###
7
+ # Hero Section
8
+ hero:
9
+ subheadline: "Every story on this thread, newest first."
10
+ ---
11
+
12
+ {% comment %}
13
+ NEWSFLASH TAG ARCHIVE
14
+ Same data contract as classy's tag page (page.tag.name/slug + posts
15
+ filtered from site.posts), presented as a topic front: breadcrumb, serif
16
+ topic headline, and the same story-card grid as the blog index.
17
+ {% endcomment %}
18
+
19
+ <!-- ============================================ -->
20
+ <!-- TOPIC HEAD -->
21
+ <!-- ============================================ -->
22
+ <section class="pb-4">
23
+ <div class="container" data-lazy="@class animation-slide-up">
24
+ <nav aria-label="breadcrumb" class="mb-3">
25
+ <ol class="breadcrumb mb-0">
26
+ <li class="breadcrumb-item">
27
+ <a href="{{ site.url }}/blog" class="text-decoration-none">News</a>
28
+ </li>
29
+ <li class="breadcrumb-item">
30
+ <a href="{{ site.url }}/blog/tags" class="text-decoration-none">Topics</a>
31
+ </li>
32
+ <li class="breadcrumb-item active" aria-current="page">{{ page.tag.name }}</li>
33
+ </ol>
34
+ </nav>
35
+
36
+ <span class="kicker mb-2">The topic</span>
37
+ <h1 class="display-4 my-2">{{ page.tag.name }}</h1>
38
+ {% iftruthy page.resolved.hero.subheadline %}
39
+ <p class="lead mb-0">{{ page.resolved.hero.subheadline }}</p>
40
+ {% endiftruthy %}
41
+ </div>
42
+ </section>
43
+
44
+ <!-- ============================================ -->
45
+ <!-- STORIES -->
46
+ <!-- ============================================ -->
47
+ <section class="pt-0">
48
+ <div class="container">
49
+ {% comment %} Filter posts by tag (max 100) {% endcomment %}
50
+ {% assign tag_slug = page.tag.slug %}
51
+ {% assign filtered_posts = '' | split: ',' %}
52
+ {% for post in site.posts %}
53
+ {% if filtered_posts.size >= 100 %}
54
+ {% break %}
55
+ {% endif %}
56
+ {% for tag in post.post.tags %}
57
+ {% assign t_slug = tag | slugify %}
58
+ {% if t_slug == tag_slug %}
59
+ {% assign filtered_posts = filtered_posts | push: post %}
60
+ {% break %}
61
+ {% endif %}
62
+ {% endfor %}
63
+ {% endfor %}
64
+
65
+ {% if filtered_posts.size > 0 %}
66
+ <div class="section-head" data-lazy="@class animation-slide-up">
67
+ <h2>On this thread</h2>
68
+ <span class="rule"></span>
69
+ </div>
70
+ <div class="row g-4 mb-5">
71
+ {% for post in filtered_posts %}
72
+ <div class="col-xl-4 col-lg-6 col-md-12" data-lazy="@class animation-slide-up">
73
+ <a href="{{ site.url }}{{ post.url }}" class="story-card">
74
+ <div class="art-frame mb-3">
75
+ {%- uj_post post.post.id, "image-tag", class="w-100" -%}
76
+ </div>
77
+ {% if post.post.categories[0] %}
78
+ <span class="kicker mb-1">{{ post.post.categories[0] | uj_title_case }}</span>
79
+ {% endif %}
80
+ <h3 class="h5 my-2">{{ post.post.title }}</h3>
81
+ <p class="small text-body-secondary mb-0">
82
+ <b class="text-body">{%- uj_member post.post.author, "name" -%}</b>
83
+ · {{ post.date | date: "%b %d, %Y" }}
84
+ · {% uj_readtime post.content %} min read
85
+ </p>
86
+ </a>
87
+ </div>
88
+ {% endfor %}
89
+ </div>
90
+ {% else %}
91
+ <div class="text-center py-5" data-lazy="@class animation-slide-up">
92
+ <span class="kicker mb-2">Nothing on this thread yet</span>
93
+ <h2 class="h3 mb-2">The story hasn't broken</h2>
94
+ <p class="text-body-secondary mb-4">Check back soon — coverage lands here as it's filed.</p>
95
+ </div>
96
+ {% endif %}
97
+
98
+ <!-- Back to topics -->
99
+ <div class="text-center" data-lazy="@class animation-slide-up">
100
+ <a href="{{ site.url }}/blog/tags" class="btn btn-outline-dark">
101
+ {% uj_icon "arrow-left", "me-2" %}All topics
102
+ </a>
103
+ </div>
104
+ </div>
105
+ </section>
106
+
107
+ {{ content | uj_content_format }}