ultimate-jekyll-manager 1.7.1 → 1.8.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.
Files changed (87) hide show
  1. package/.claude/scheduled_tasks.lock +1 -0
  2. package/CHANGELOG.md +68 -1
  3. package/CLAUDE.md +36 -15
  4. package/README.md +4 -2
  5. package/TODO-AUTH-TESTING.md +1 -1
  6. package/dist/assets/js/libs/form-manager.js +4 -1
  7. package/dist/assets/js/pages/payment/confirmation/index.js +9 -0
  8. package/dist/assets/themes/newsflash/README.md +58 -0
  9. package/dist/assets/themes/newsflash/_config.scss +138 -0
  10. package/dist/assets/themes/newsflash/_theme.js +27 -0
  11. package/dist/assets/themes/newsflash/_theme.scss +37 -0
  12. package/dist/assets/themes/newsflash/css/base/_mixins.scss +50 -0
  13. package/dist/assets/themes/newsflash/css/base/_root.scss +134 -0
  14. package/dist/assets/themes/newsflash/css/base/_typography.scss +49 -0
  15. package/dist/assets/themes/newsflash/css/base/_utilities.scss +58 -0
  16. package/dist/assets/themes/newsflash/css/components/_badges.scss +65 -0
  17. package/dist/assets/themes/newsflash/css/components/_buttons.scss +139 -0
  18. package/dist/assets/themes/newsflash/css/components/_cards.scss +52 -0
  19. package/dist/assets/themes/newsflash/css/components/_editorial.scss +182 -0
  20. package/dist/assets/themes/newsflash/css/components/_forms.scss +75 -0
  21. package/dist/assets/themes/newsflash/css/components/_infinite-scroll.scss +102 -0
  22. package/dist/assets/themes/newsflash/css/components/_panels.scss +91 -0
  23. package/dist/assets/themes/newsflash/css/components/_ticker.scss +70 -0
  24. package/dist/assets/themes/newsflash/css/layout/_general.scss +264 -0
  25. package/dist/assets/themes/newsflash/css/layout/_navigation.scss +164 -0
  26. package/dist/assets/themes/newsflash/js/initialize-tooltips.js +20 -0
  27. package/dist/assets/themes/newsflash/js/masthead-scroll.js +29 -0
  28. package/dist/assets/themes/newsflash/pages/404/index.scss +27 -0
  29. package/dist/assets/themes/newsflash/pages/about/index.scss +70 -0
  30. package/dist/assets/themes/newsflash/pages/blog/index.scss +17 -0
  31. package/dist/assets/themes/newsflash/pages/blog/post.js +29 -0
  32. package/dist/assets/themes/newsflash/pages/blog/post.scss +164 -0
  33. package/dist/assets/themes/newsflash/pages/index.scss +159 -0
  34. package/dist/assets/themes/newsflash/pages/pricing/index.scss +194 -0
  35. package/dist/assets/themes/newsflash/pages/test/libraries/layers/index.js +9 -0
  36. package/dist/assets/themes/newsflash/pages/test/libraries/layers/index.scss +7 -0
  37. package/dist/commands/blogify.js +6 -3
  38. package/dist/commands/test.js +34 -5
  39. package/dist/defaults/CLAUDE.md +17 -4
  40. package/dist/defaults/dist/_includes/core/pricing/resolve-plan.html +59 -0
  41. package/dist/defaults/dist/_includes/themes/classy/frontend/sections/footer.html +20 -3
  42. package/dist/defaults/dist/_layouts/themes/classy/admin/core/minimal-viewport-locked.html +1 -1
  43. package/dist/defaults/dist/_layouts/themes/classy/admin/core/minimal.html +1 -1
  44. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html +5 -40
  45. package/dist/defaults/dist/_layouts/themes/neobrutalism/frontend/pages/pricing.html +33 -34
  46. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/core/base.html +61 -0
  47. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/404.html +86 -0
  48. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/about.html +353 -0
  49. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/categories/category.html +105 -0
  50. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/categories/index.html +93 -0
  51. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/index.html +373 -0
  52. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/post.html +289 -0
  53. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/tags/index.html +90 -0
  54. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/tags/tag.html +107 -0
  55. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/contact.html +340 -0
  56. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/index.html +522 -0
  57. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/pricing.html +485 -0
  58. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/team/index.html +207 -0
  59. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/team/member.html +134 -0
  60. package/dist/defaults/test/README.md +4 -0
  61. package/dist/gulp/tasks/jekyll.js +4 -2
  62. package/dist/test/runner.js +50 -3
  63. package/dist/test/suites/build/attach-log-file.test.js +102 -0
  64. package/dist/test/suites/build/theme-contract.test.js +173 -0
  65. package/dist/test/utils/extended-mode-warning.js +13 -0
  66. package/dist/utils/attach-log-file.js +70 -43
  67. package/docs/appearance.md +1 -0
  68. package/docs/assets.md +9 -0
  69. package/docs/audit.md +27 -7
  70. package/docs/build-system.md +57 -0
  71. package/docs/common-mistakes.md +15 -0
  72. package/docs/{project-structure.md → directory-structure.md} +1 -1
  73. package/docs/environment-detection.md +1 -1
  74. package/docs/javascript-libraries.md +38 -1
  75. package/docs/layouts-and-pages.md +146 -0
  76. package/docs/local-development.md +1 -8
  77. package/docs/logging.md +30 -0
  78. package/docs/migration.md +131 -0
  79. package/docs/no-inline-scripts.md +304 -0
  80. package/docs/purgecss.md +164 -0
  81. package/docs/seo.md +131 -4
  82. package/docs/templating.md +23 -0
  83. package/docs/test-boot-layer.md +1 -1
  84. package/docs/test-framework.md +56 -8
  85. package/docs/themes.md +254 -13
  86. package/logs/test.log +111 -0
  87. package/package.json +1 -1
@@ -0,0 +1,373 @@
1
+ ---
2
+ ### ALL PAGES ###
3
+ layout: themes/[ site.theme.id ]/frontend/core/base
4
+
5
+ ### PAGE CONFIG ###
6
+ # Hero Section
7
+ hero:
8
+ headline: "The {{ site.brand.name }}"
9
+ headline_accent: "wire"
10
+ subheadline: "Discover insights, tutorials, and news from our team"
11
+
12
+ # Newsletter CTA Section
13
+ newsletter_cta:
14
+ superheadline:
15
+ icon: "envelope"
16
+ text: "Newsletter"
17
+ headline: "Never miss a"
18
+ headline_accent: "story"
19
+ subheadline: "Get the latest insights delivered straight to your inbox. No spam, unsubscribe anytime."
20
+ button:
21
+ text: "Subscribe"
22
+ form:
23
+ placeholder: "Enter your email"
24
+ success_message: "Thank you for subscribing! Check your email to confirm."
25
+ error_message: "Something went wrong. Please try again."
26
+ ---
27
+
28
+ {% comment %}
29
+ NEWSFLASH BLOG INDEX
30
+ The newspaper front page: serif masthead title + pill search, a framed lead
31
+ story splash, an editorial tile grid for the rest of the page's posts, pill
32
+ pagination, category/tag chips, and the vermilion newsletter slab. Preserves
33
+ classy's contracts: paginator, /search/cse form, #newsletter-form + alert
34
+ classes (wired by the main-layer page JS), and the adsense include.
35
+ {% endcomment %}
36
+
37
+ <!-- Page head: serif title + dek + search -->
38
+ <section class="pb-4">
39
+ <div class="container">
40
+ <div class="row align-items-end g-4" data-lazy="@class animation-slide-up">
41
+ <div class="col-lg-8">
42
+ <span class="kicker mb-2">The latest</span>
43
+ <h1 class="display-4 mb-2">
44
+ {{ page.resolved.hero.headline }}
45
+ {% iftruthy page.resolved.hero.headline_accent %}
46
+ <span class="text-accent">{{ page.resolved.hero.headline_accent }}</span>
47
+ {% endiftruthy %}
48
+ </h1>
49
+ {% iftruthy page.resolved.hero.subheadline %}
50
+ <p class="lead mb-0">{{ page.resolved.hero.subheadline }}</p>
51
+ {% endiftruthy %}
52
+ </div>
53
+ <div class="col-lg-4">
54
+ <form action="{{ site.url }}/search/cse" target="_blank">
55
+ <div class="input-group">
56
+ <input
57
+ type="search"
58
+ class="form-control"
59
+ name="q"
60
+ placeholder="Search posts..."
61
+ aria-label="Search posts"
62
+ >
63
+ <button class="btn btn-outline-dark" type="submit">
64
+ {% uj_icon "search" %}
65
+ </button>
66
+ </div>
67
+ </form>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </section>
72
+
73
+ <!-- Lead story + feed -->
74
+ <section class="pt-0">
75
+ <div class="container">
76
+ {% if paginator.posts.size > 0 %}
77
+ <!-- Lead story splash -->
78
+ {% assign lead = paginator.posts.first %}
79
+ <div class="section-head" data-lazy="@class animation-slide-up">
80
+ <h2>Lead story</h2>
81
+ <span class="rule"></span>
82
+ </div>
83
+ <a href="{{ site.url }}{{ lead.url }}" class="story-card story-card--lead mb-5" data-lazy="@class animation-slide-up">
84
+ <div class="row g-4 g-lg-5 align-items-center">
85
+ <div class="col-lg-7">
86
+ <div class="art-frame shadow">
87
+ {%- uj_post lead.post.id, "image-tag", class="w-100" -%}
88
+ </div>
89
+ </div>
90
+ <div class="col-lg-5">
91
+ {% if lead.post.categories[0] %}
92
+ <span class="kicker mb-2">{{ lead.post.categories[0] | uj_title_case }}</span>
93
+ {% endif %}
94
+ <h3 class="h1 my-2">{{ lead.post.title }}</h3>
95
+ <p class="text-body-secondary mb-3">
96
+ <b class="text-body">{%- uj_member lead.post.author, "name" -%}</b>
97
+ · {{ lead.date | date: "%b %d, %Y" }}
98
+ · {% uj_readtime lead.content %} min read
99
+ </p>
100
+ <span class="btn btn-outline-dark btn-sm">Read the story {% uj_icon "arrow-right", "ms-1" %}</span>
101
+ </div>
102
+ </div>
103
+ </a>
104
+
105
+ <!-- The rest of this page's posts -->
106
+ {% if paginator.posts.size > 1 %}
107
+ <div class="section-head" data-lazy="@class animation-slide-up">
108
+ <h2>More stories</h2>
109
+ <span class="rule"></span>
110
+ </div>
111
+ <div class="row g-4 mb-4">
112
+ {% for post in paginator.posts offset: 1 %}
113
+ <div class="col-xl-4 col-lg-6 col-md-12" data-lazy="@class animation-slide-up">
114
+ <a href="{{ site.url }}{{ post.url }}" class="story-card">
115
+ <div class="art-frame mb-3">
116
+ {%- uj_post post.post.id, "image-tag", class="w-100" -%}
117
+ </div>
118
+ {% if post.post.categories[0] %}
119
+ <span class="kicker mb-1">{{ post.post.categories[0] | uj_title_case }}</span>
120
+ {% endif %}
121
+ <h3 class="h5 my-2">{{ post.post.title }}</h3>
122
+ <p class="small text-body-secondary mb-0">
123
+ <b class="text-body">{%- uj_member post.post.author, "name" -%}</b>
124
+ · {{ post.date | date: "%b %d, %Y" }}
125
+ · {% uj_readtime post.content %} min read
126
+ </p>
127
+ </a>
128
+ </div>
129
+ {% endfor %}
130
+ </div>
131
+ {% endif %}
132
+ {% else %}
133
+ <!-- No posts yet -->
134
+ <div class="text-center py-5" data-lazy="@class animation-slide-up">
135
+ <span class="kicker mb-2">No dispatches yet</span>
136
+ <h2 class="h3 mb-2">The presses are warming up</h2>
137
+ <p class="text-body-secondary mb-0">Check back soon for our first stories.</p>
138
+ </div>
139
+ {% endif %}
140
+
141
+ <!-- Pagination -->
142
+ <div class="row mb-5">
143
+ <div class="col-12">
144
+ {% if paginator.total_pages > 1 %}
145
+ <nav aria-label="Blog pagination" class="d-flex justify-content-center" data-lazy="@class animation-slide-up">
146
+ <ul class="pagination mb-0">
147
+ <!-- Previous Page -->
148
+ {% if paginator.previous_page %}
149
+ <li class="page-item">
150
+ <a class="page-link" href="{{ paginator.previous_page_path }}" aria-label="Previous page">
151
+ {% uj_icon "chevron-left" %}
152
+ <span class="d-none d-sm-inline ms-1">Previous</span>
153
+ </a>
154
+ </li>
155
+ {% else %}
156
+ <li class="page-item disabled">
157
+ <span class="page-link">
158
+ {% uj_icon "chevron-left" %}
159
+ <span class="d-none d-sm-inline ms-1">Previous</span>
160
+ </span>
161
+ </li>
162
+ {% endif %}
163
+
164
+ <!-- Page Numbers -->
165
+ {% assign start_page = paginator.page | minus: 2 | at_least: 1 %}
166
+ {% assign end_page = paginator.page | plus: 2 | at_most: paginator.total_pages %}
167
+
168
+ {% if start_page > 1 %}
169
+ <li class="page-item">
170
+ <a class="page-link" href="{{ site.url }}/blog">1</a>
171
+ </li>
172
+ {% if start_page > 2 %}
173
+ <li class="page-item disabled">
174
+ <span class="page-link">...</span>
175
+ </li>
176
+ {% endif %}
177
+ {% endif %}
178
+
179
+ {% for page in (start_page..end_page) %}
180
+ <li class="page-item {% if page == paginator.page %}active{% endif %}">
181
+ {% if page == 1 %}
182
+ <a class="page-link" href="{{ site.url }}/blog">{{ page }}</a>
183
+ {% else %}
184
+ <a class="page-link" href="{{ site.url }}/blog/page/{{ page }}">{{ page }}</a>
185
+ {% endif %}
186
+ </li>
187
+ {% endfor %}
188
+
189
+ {% if end_page < paginator.total_pages %}
190
+ {% assign second_to_last = paginator.total_pages | minus: 1 %}
191
+ {% if end_page < second_to_last %}
192
+ <li class="page-item disabled">
193
+ <span class="page-link">...</span>
194
+ </li>
195
+ {% endif %}
196
+ <li class="page-item">
197
+ <a class="page-link" href="{{ site.url }}/blog/page/{{ paginator.total_pages }}">
198
+ {{ paginator.total_pages }}
199
+ </a>
200
+ </li>
201
+ {% endif %}
202
+
203
+ <!-- Next Page -->
204
+ {% if paginator.next_page %}
205
+ <li class="page-item">
206
+ <a class="page-link" href="{{ paginator.next_page_path }}" aria-label="Next page">
207
+ <span class="d-none d-sm-inline me-1">Next</span>
208
+ {% uj_icon "chevron-right" %}
209
+ </a>
210
+ </li>
211
+ {% else %}
212
+ <li class="page-item disabled">
213
+ <span class="page-link">
214
+ <span class="d-none d-sm-inline me-1">Next</span>
215
+ {% uj_icon "chevron-right" %}
216
+ </span>
217
+ </li>
218
+ {% endif %}
219
+ </ul>
220
+ </nav>
221
+ {% endif %}
222
+ </div>
223
+ </div>
224
+
225
+ <!-- In-Feed Ad -->
226
+ <div class="row justify-content-center mb-5">
227
+ <div class="col-12 col-lg-8" data-lazy="@class animation-slide-up">
228
+ {% include modules/adunits/adsense.html type="in-feed" %}
229
+ </div>
230
+ </div>
231
+
232
+ <!-- Categories & Tags -->
233
+ {% comment %} Build unique categories list (max 10) {% endcomment %}
234
+ {% assign categories = '' | split: ',' %}
235
+ {% for post in site.posts %}
236
+ {% if categories.size >= 10 %}
237
+ {% break %}
238
+ {% endif %}
239
+ {% for category in post.post.categories %}
240
+ {% assign category_fixed = category | uj_title_case %}
241
+ {% unless categories contains category_fixed %}
242
+ {% assign categories = categories | push: category_fixed %}
243
+ {% endunless %}
244
+ {% endfor %}
245
+ {% endfor %}
246
+
247
+ {% comment %} Build unique tags list (max 10) {% endcomment %}
248
+ {% assign tags = '' | split: ',' %}
249
+ {% for post in site.posts %}
250
+ {% if tags.size >= 10 %}
251
+ {% break %}
252
+ {% endif %}
253
+ {% for tag in post.post.tags %}
254
+ {% assign tag_fixed = tag | uj_title_case %}
255
+ {% unless tags contains tag_fixed %}
256
+ {% assign tags = tags | push: tag_fixed %}
257
+ {% endunless %}
258
+ {% endfor %}
259
+ {% endfor %}
260
+
261
+ {% if categories.size > 0 or tags.size > 0 %}
262
+ <div class="row justify-content-center">
263
+ <div class="col-lg-10" data-lazy="@class animation-slide-up">
264
+ <!-- Categories -->
265
+ {% if categories.size > 0 %}
266
+ <div class="mb-5 text-center">
267
+ <h3 class="h5 mb-3">
268
+ {% uj_icon "folder", "me-2" %}
269
+ Categories
270
+ </h3>
271
+ <div>
272
+ {% for category in categories %}
273
+ <a href="{{ site.url }}/blog/categories/{{ category | slugify }}" class="badge bg-primary-soft text-primary text-decoration-none me-1 mb-2">
274
+ {{ category }}
275
+ </a>
276
+ {% endfor %}
277
+ <a href="{{ site.url }}/blog/categories" class="badge bg-body-tertiary text-decoration-none ms-1 mb-2">
278
+ View all {% uj_icon "arrow-right", "ms-1" %}
279
+ </a>
280
+ </div>
281
+ </div>
282
+ {% endif %}
283
+
284
+ <!-- Tags -->
285
+ {% if tags.size > 0 %}
286
+ <div class="text-center">
287
+ <h3 class="h5 mb-3">
288
+ {% uj_icon "tags", "me-2" %}
289
+ Tags
290
+ </h3>
291
+ <div>
292
+ {% for tag in tags %}
293
+ <a href="{{ site.url }}/blog/tags/{{ tag | slugify }}" class="badge bg-body-tertiary text-decoration-none me-1 mb-2">
294
+ {{ tag }}
295
+ </a>
296
+ {% endfor %}
297
+ <a href="{{ site.url }}/blog/tags" class="badge bg-body-tertiary text-decoration-none ms-1 mb-2">
298
+ View all {% uj_icon "arrow-right", "ms-1" %}
299
+ </a>
300
+ </div>
301
+ </div>
302
+ {% endif %}
303
+ </div>
304
+ </div>
305
+ {% endif %}
306
+ </div>
307
+ </section>
308
+
309
+ <!-- Newsletter CTA — the vermilion slab -->
310
+ <section>
311
+ <div class="container">
312
+ <div class="card bg-primary text-white p-4 p-md-5 text-center position-relative overflow-hidden" data-lazy="@class animation-slide-up">
313
+ <div class="position-relative">
314
+ {% iftruthy page.resolved.newsletter_cta.superheadline.text %}
315
+ <span class="badge bg-body-tertiary p-2 mb-3">
316
+ {% iftruthy page.resolved.newsletter_cta.superheadline.icon %}
317
+ {% uj_icon page.resolved.newsletter_cta.superheadline.icon, "me-1" %}
318
+ {% endiftruthy %}
319
+ {{ page.resolved.newsletter_cta.superheadline.text }}
320
+ </span>
321
+ {% endiftruthy %}
322
+
323
+ <h2 class="h2 fst-italic mb-3">
324
+ {{ page.resolved.newsletter_cta.headline }}
325
+ {% iftruthy page.resolved.newsletter_cta.headline_accent %}
326
+ {{ page.resolved.newsletter_cta.headline_accent }}
327
+ {% endiftruthy %}
328
+ </h2>
329
+
330
+ {% iftruthy page.resolved.newsletter_cta.subheadline %}
331
+ <p class="lead text-white mb-4">{{ page.resolved.newsletter_cta.subheadline }}</p>
332
+ {% endiftruthy %}
333
+
334
+ <!-- Newsletter Form -->
335
+ <form id="newsletter-form" class="newsletter-form" novalidate onsubmit="return false">
336
+ <div class="row g-3 justify-content-center">
337
+ <div class="col-md-7">
338
+ <input
339
+ type="email"
340
+ class="form-control form-control-lg"
341
+ placeholder="{{ page.resolved.newsletter_cta.form.placeholder }}"
342
+ name="email"
343
+ required
344
+ pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
345
+ autocomplete="email"
346
+ >
347
+ <div class="invalid-feedback text-start">
348
+ Please enter a valid email address.
349
+ </div>
350
+ </div>
351
+ <div class="col-md-auto">
352
+ <button type="submit" class="btn btn-dark btn-lg px-4">
353
+ <span class="button-text">{{ page.resolved.newsletter_cta.button.text }}</span>
354
+ </button>
355
+ </div>
356
+ </div>
357
+
358
+ <!-- Success/Error Messages -->
359
+ <div class="newsletter-success-alert alert alert-success mt-3 d-none" role="alert">
360
+ {% uj_icon "check-circle", "me-2" %}
361
+ {{ page.resolved.newsletter_cta.form.success_message }}
362
+ </div>
363
+ <div class="newsletter-error-alert alert alert-danger mt-3 d-none" role="alert">
364
+ {% uj_icon "exclamation-circle", "me-2" %}
365
+ <span class="error-message">{{ page.resolved.newsletter_cta.form.error_message }}</span>
366
+ </div>
367
+ </form>
368
+ </div>
369
+ </div>
370
+ </div>
371
+ </section>
372
+
373
+ {{ content | uj_content_format }}
@@ -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>