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,93 @@
1
+ ---
2
+ ### ALL PAGES ###
3
+ layout: themes/[ site.theme.id ]/frontend/core/base
4
+
5
+ ### PAGE CONFIG ###
6
+ # Hero Section — categories are the paper's desks. News voice throughout: a
7
+ # theme's frontmatter defaults are part of its identity (see docs/themes.md).
8
+ hero:
9
+ headline: "Browse by"
10
+ headline_accent: "desk"
11
+ subheadline: "Every story we publish is filed to a desk. Pick one and read the coverage."
12
+ ---
13
+
14
+ {% comment %}
15
+ NEWSFLASH BLOG CATEGORIES
16
+ Same data contract as classy's categories index (categories + counts built
17
+ from site.posts), presented as the desk directory: editorial cards with
18
+ stroked story-count numerals instead of folder icons.
19
+ {% endcomment %}
20
+
21
+ <!-- ============================================ -->
22
+ <!-- HERO -->
23
+ <!-- ============================================ -->
24
+ <section class="pb-4">
25
+ <div class="container text-center" data-lazy="@class animation-slide-up">
26
+ <span class="kicker mb-3">{% uj_icon "newspaper", "me-1" %}The desks</span>
27
+ <h1 class="display-4 my-3">
28
+ {{ page.resolved.hero.headline }}
29
+ <span class="text-accent">{{ page.resolved.hero.headline_accent }}</span>
30
+ </h1>
31
+ {% iftruthy page.resolved.hero.subheadline %}
32
+ <p class="lead mb-0">{{ page.resolved.hero.subheadline }}</p>
33
+ {% endiftruthy %}
34
+ </div>
35
+ </section>
36
+
37
+ <!-- ============================================ -->
38
+ <!-- DESK DIRECTORY -->
39
+ <!-- ============================================ -->
40
+ <section class="pt-0">
41
+ <div class="container">
42
+ {% comment %} Build unique categories list with post counts {% endcomment %}
43
+ {% assign categories = '' | split: ',' %}
44
+ {% assign category_counts = '' | split: ',' %}
45
+ {% for post in site.posts %}
46
+ {% for category in post.post.categories %}
47
+ {% assign category_fixed = category | uj_title_case %}
48
+ {% unless categories contains category_fixed %}
49
+ {% assign categories = categories | push: category_fixed %}
50
+ {% comment %} Count posts for this category {% endcomment %}
51
+ {% assign count = 0 %}
52
+ {% for p in site.posts %}
53
+ {% for c in p.post.categories %}
54
+ {% assign c_fixed = c | uj_title_case %}
55
+ {% if c_fixed == category_fixed %}
56
+ {% assign count = count | plus: 1 %}
57
+ {% break %}
58
+ {% endif %}
59
+ {% endfor %}
60
+ {% endfor %}
61
+ {% assign category_counts = category_counts | push: count %}
62
+ {% endunless %}
63
+ {% endfor %}
64
+ {% endfor %}
65
+
66
+ <div class="row g-4">
67
+ {% for category in categories %}
68
+ <div class="col-xl-3 col-lg-4 col-md-6" data-lazy="@class animation-slide-up">
69
+ <a href="{{ site.url }}/blog/categories/{{ category | slugify }}" class="card h-100 p-4 text-decoration-none text-center">
70
+ <span class="stat-num">{{ category_counts[forloop.index0] }}</span>
71
+ <h2 class="h4 mb-1">{{ category }}</h2>
72
+ <p class="small text-body-secondary mb-0">
73
+ {{ category_counts[forloop.index0] | uj_pluralize: 'story', 'stories' }} on this desk
74
+ </p>
75
+ </a>
76
+ </div>
77
+ {% endfor %}
78
+ </div>
79
+
80
+ {% if categories.size == 0 %}
81
+ <div class="text-center py-5" data-lazy="@class animation-slide-up">
82
+ <span class="kicker mb-2">No desks yet</span>
83
+ <h2 class="h3 mb-2">The newsroom is still setting up</h2>
84
+ <p class="text-body-secondary mb-4">Check back soon — the first stories are being filed.</p>
85
+ <a href="{{ site.url }}/blog" class="btn btn-primary">
86
+ {% uj_icon "arrow-left", "me-2" %}Back to the news
87
+ </a>
88
+ </div>
89
+ {% endif %}
90
+ </div>
91
+ </section>
92
+
93
+ {{ content | uj_content_format }}
@@ -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 }}