ultimate-jekyll-manager 1.7.2 → 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 (85) hide show
  1. package/.claude/scheduled_tasks.lock +1 -0
  2. package/CHANGELOG.md +61 -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/themes/newsflash/README.md +58 -0
  7. package/dist/assets/themes/newsflash/_config.scss +138 -0
  8. package/dist/assets/themes/newsflash/_theme.js +27 -0
  9. package/dist/assets/themes/newsflash/_theme.scss +37 -0
  10. package/dist/assets/themes/newsflash/css/base/_mixins.scss +50 -0
  11. package/dist/assets/themes/newsflash/css/base/_root.scss +134 -0
  12. package/dist/assets/themes/newsflash/css/base/_typography.scss +49 -0
  13. package/dist/assets/themes/newsflash/css/base/_utilities.scss +58 -0
  14. package/dist/assets/themes/newsflash/css/components/_badges.scss +65 -0
  15. package/dist/assets/themes/newsflash/css/components/_buttons.scss +139 -0
  16. package/dist/assets/themes/newsflash/css/components/_cards.scss +52 -0
  17. package/dist/assets/themes/newsflash/css/components/_editorial.scss +182 -0
  18. package/dist/assets/themes/newsflash/css/components/_forms.scss +75 -0
  19. package/dist/assets/themes/newsflash/css/components/_infinite-scroll.scss +102 -0
  20. package/dist/assets/themes/newsflash/css/components/_panels.scss +91 -0
  21. package/dist/assets/themes/newsflash/css/components/_ticker.scss +70 -0
  22. package/dist/assets/themes/newsflash/css/layout/_general.scss +264 -0
  23. package/dist/assets/themes/newsflash/css/layout/_navigation.scss +164 -0
  24. package/dist/assets/themes/newsflash/js/initialize-tooltips.js +20 -0
  25. package/dist/assets/themes/newsflash/js/masthead-scroll.js +29 -0
  26. package/dist/assets/themes/newsflash/pages/404/index.scss +27 -0
  27. package/dist/assets/themes/newsflash/pages/about/index.scss +70 -0
  28. package/dist/assets/themes/newsflash/pages/blog/index.scss +17 -0
  29. package/dist/assets/themes/newsflash/pages/blog/post.js +29 -0
  30. package/dist/assets/themes/newsflash/pages/blog/post.scss +164 -0
  31. package/dist/assets/themes/newsflash/pages/index.scss +159 -0
  32. package/dist/assets/themes/newsflash/pages/pricing/index.scss +194 -0
  33. package/dist/assets/themes/newsflash/pages/test/libraries/layers/index.js +9 -0
  34. package/dist/assets/themes/newsflash/pages/test/libraries/layers/index.scss +7 -0
  35. package/dist/commands/blogify.js +6 -3
  36. package/dist/commands/test.js +34 -5
  37. package/dist/defaults/CLAUDE.md +17 -4
  38. package/dist/defaults/dist/_includes/core/pricing/resolve-plan.html +59 -0
  39. package/dist/defaults/dist/_includes/themes/classy/frontend/sections/footer.html +20 -3
  40. package/dist/defaults/dist/_layouts/themes/classy/admin/core/minimal-viewport-locked.html +1 -1
  41. package/dist/defaults/dist/_layouts/themes/classy/admin/core/minimal.html +1 -1
  42. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html +5 -40
  43. package/dist/defaults/dist/_layouts/themes/neobrutalism/frontend/pages/pricing.html +33 -34
  44. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/core/base.html +61 -0
  45. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/404.html +86 -0
  46. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/about.html +353 -0
  47. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/categories/category.html +105 -0
  48. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/categories/index.html +93 -0
  49. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/index.html +373 -0
  50. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/post.html +289 -0
  51. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/tags/index.html +90 -0
  52. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/tags/tag.html +107 -0
  53. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/contact.html +340 -0
  54. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/index.html +522 -0
  55. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/pricing.html +485 -0
  56. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/team/index.html +207 -0
  57. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/team/member.html +134 -0
  58. package/dist/defaults/test/README.md +4 -0
  59. package/dist/gulp/tasks/jekyll.js +4 -2
  60. package/dist/test/runner.js +50 -3
  61. package/dist/test/suites/build/attach-log-file.test.js +102 -0
  62. package/dist/test/suites/build/theme-contract.test.js +173 -0
  63. package/dist/test/utils/extended-mode-warning.js +13 -0
  64. package/dist/utils/attach-log-file.js +70 -43
  65. package/docs/appearance.md +1 -0
  66. package/docs/assets.md +9 -0
  67. package/docs/audit.md +27 -7
  68. package/docs/build-system.md +57 -0
  69. package/docs/common-mistakes.md +15 -0
  70. package/docs/{project-structure.md → directory-structure.md} +1 -1
  71. package/docs/environment-detection.md +1 -1
  72. package/docs/javascript-libraries.md +38 -1
  73. package/docs/layouts-and-pages.md +146 -0
  74. package/docs/local-development.md +1 -8
  75. package/docs/logging.md +30 -0
  76. package/docs/migration.md +131 -0
  77. package/docs/no-inline-scripts.md +304 -0
  78. package/docs/purgecss.md +164 -0
  79. package/docs/seo.md +131 -4
  80. package/docs/templating.md +23 -0
  81. package/docs/test-boot-layer.md +1 -1
  82. package/docs/test-framework.md +56 -8
  83. package/docs/themes.md +254 -13
  84. package/logs/test.log +111 -0
  85. package/package.json +1 -1
@@ -0,0 +1,61 @@
1
+ ---
2
+ ### ALL PAGES ###
3
+ layout: core/root
4
+
5
+ ### THEME CONFIG ###
6
+ theme:
7
+ # html:
8
+ # class: ""
9
+ head:
10
+ content: |
11
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,300..900&family=Schibsted+Grotesk:ital,wght@0,400..900;1,400..900&display=swap" media="print" onload="this.media='all'">
12
+ body:
13
+ class: "d-flex flex-column min-vh-100"
14
+ # main:
15
+ # class: "flex-fill"
16
+ ---
17
+
18
+ <!-- News ticker — the latest headlines marquee above the masthead.
19
+ Pure CSS animation; the track is duplicated once (aria-hidden) so the
20
+ -50% keyframe loops seamlessly. Absent when the site has no posts. -->
21
+ {% if site.posts.size > 0 %}
22
+ <div class="ticker">
23
+ <div class="ticker-track">
24
+ {% for post in site.posts limit: 5 %}
25
+ <span class="ticker-item">
26
+ {% if forloop.first %}
27
+ <span class="live-dot"></span><b>LATEST</b>
28
+ {% else %}
29
+ {% uj_icon "bolt" %}
30
+ {% endif %}
31
+ <a href="{{ site.url }}{{ post.url }}">{{ post.post.title }}</a>
32
+ </span>
33
+ {% endfor %}
34
+ {% for post in site.posts limit: 5 %}
35
+ <span class="ticker-item" aria-hidden="true">
36
+ {% if forloop.first %}
37
+ <span class="live-dot"></span><b>LATEST</b>
38
+ {% else %}
39
+ {% uj_icon "bolt" %}
40
+ {% endif %}
41
+ <a href="{{ site.url }}{{ post.url }}" tabindex="-1">{{ post.post.title }}</a>
42
+ </span>
43
+ {% endfor %}
44
+ </div>
45
+ </div>
46
+ {% endif %}
47
+
48
+ <!-- Nav (controlled by page/layout front matter) -->
49
+ {% unless page.resolved.theme.nav.enabled == false %}
50
+ {%- include /frontend/sections/nav.html -%}
51
+ {% endunless %}
52
+
53
+ <!-- Main Content -->
54
+ <main id="main-content" class="flex-fill {{ page.resolved.theme.main.class | uj_liquify }}" aria-label="Main content">
55
+ {{ content | uj_content_format }}
56
+ </main>
57
+
58
+ <!-- Footer (controlled by page/layout front matter) -->
59
+ {% unless page.resolved.theme.footer.enabled == false %}
60
+ {%- include /frontend/sections/footer.html -%}
61
+ {% endunless %}
@@ -0,0 +1,86 @@
1
+ ---
2
+ ### ALL PAGES ###
3
+ layout: themes/[ site.theme.id ]/frontend/core/cover
4
+
5
+ ### PAGE CONFIG ###
6
+ suggested_pages:
7
+ - title: "Home"
8
+ description: "Return to our homepage"
9
+ icon: "house"
10
+ link: "/"
11
+ - title: "Pricing"
12
+ description: "View our plans and pricing"
13
+ icon: "tag"
14
+ link: "/pricing"
15
+ - title: "Contact"
16
+ description: "Get in touch with us"
17
+ icon: "envelope"
18
+ link: "/contact"
19
+ - title: "Team"
20
+ description: "Meet our amazing team"
21
+ icon: "users"
22
+ link: "/team"
23
+ ---
24
+
25
+ {% comment %}
26
+ NEWSFLASH 404
27
+ A correction notice from the desk: rotated "Correction" chip, oversized
28
+ stroked 404 numerals, the requested URL in a framed notice, and the
29
+ suggested_pages contract rendered as pill links.
30
+ {% endcomment %}
31
+
32
+ <section class="col-12 col-md-10 col-lg-8 col-xl-6 mw-md">
33
+ <div class="card shadow position-relative">
34
+ <span class="badge bg-body-tertiary error-stamp">Correction</span>
35
+ <div class="card-body p-3 p-md-5">
36
+ <!-- Header -->
37
+ <div class="text-center mb-4">
38
+ <h1 class="error-code mb-3">404</h1>
39
+ <p class="text-body-secondary mb-0">
40
+ This page never went to print. It may have been moved, retracted, or it never existed — our editors regret the error.
41
+ </p>
42
+ </div>
43
+
44
+ <!-- URL Display -->
45
+ <div class="alert alert-warning text-center mb-4" role="alert">
46
+ <small class="text-body">
47
+ <strong>Requested URL:</strong> <span id="page-url" class="text-break">{{ page.canonical.url }}</span>
48
+ </small>
49
+ </div>
50
+
51
+ <!-- Primary Actions -->
52
+ <div class="d-grid gap-2 d-sm-block text-center mb-4">
53
+ <a href="{{ site.url }}" class="btn btn-adaptive btn-lg mb-2 mb-sm-0 me-sm-2">
54
+ {% uj_icon "house", "me-2" %}
55
+ Back to home
56
+ </a>
57
+ <button class="btn btn-outline-dark btn-lg mb-2 mb-sm-0" onclick="window.history.back()">
58
+ {% uj_icon "arrow-left", "me-2" %}
59
+ Go back
60
+ </button>
61
+ </div>
62
+
63
+ <!-- Suggested pages -->
64
+ {% iftruthy page.resolved.suggested_pages %}
65
+ <div class="text-center mb-4">
66
+ {% for suggestion in page.resolved.suggested_pages %}
67
+ <a href="{{ suggestion.link }}" class="btn btn-outline-dark btn-sm me-1 mb-2" title="{{ suggestion.description }}">
68
+ {% uj_icon suggestion.icon, "me-1" %}{{ suggestion.title }}
69
+ </a>
70
+ {% endfor %}
71
+ </div>
72
+ {% endiftruthy %}
73
+
74
+ <!-- Help Section -->
75
+ <div class="text-center pt-4 border-top">
76
+ <p class="text-body-secondary mb-0">
77
+ Still can't find what you're looking for?
78
+ <a href="/contact" class="text-decoration-none fw-semibold">Contact our support team</a>
79
+ and we'll help you out!
80
+ </p>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </section>
85
+
86
+ {{ content | uj_content_format }}
@@ -0,0 +1,353 @@
1
+ ---
2
+ ### ALL PAGES ###
3
+ layout: themes/[ site.theme.id ]/frontend/core/base
4
+
5
+ ### PAGE CONFIG ###
6
+ # News voice throughout: a theme's frontmatter defaults are part of its
7
+ # identity (see docs/themes.md "Frontmatter defaults").
8
+ # Hero Section
9
+ hero:
10
+ headline: "Meet the"
11
+ headline_accent: "newsroom"
12
+ description: "The people, principles, and stubborn curiosity behind {{ site.brand.name }}"
13
+
14
+ # Mission & Vision
15
+ mission:
16
+ title: "Our mission"
17
+ description: "To deliver fearless, independent journalism that helps readers understand the forces shaping their world — clearly, quickly, and without the noise."
18
+ icon: "bullseye"
19
+
20
+ vision:
21
+ title: "Our promise"
22
+ description: "Staying informed should take minutes, not hours — and trust in the news is earned daily, story by story, correction by correction."
23
+ icon: "lightbulb"
24
+
25
+ # Story Timeline Section
26
+ story:
27
+ superheadline:
28
+ icon: "clock-rotate-left"
29
+ text: "Our story"
30
+ headline: "From scrappy newsletter to"
31
+ headline_accent: "newsroom"
32
+ subheadline: "How a weekend side project became a daily habit for millions"
33
+ items:
34
+ - year: "2017"
35
+ title: "The first dispatch"
36
+ description: "{{ site.brand.name }} launches as a weekend newsletter with 200 subscribers and strong opinions about what news should feel like"
37
+ - year: "2018"
38
+ title: "The daily habit"
39
+ description: "The morning briefing goes daily and crosses 50,000 readers — most of them before 7am"
40
+ - year: "2020"
41
+ title: "Original reporting"
42
+ description: "First full-time reporters join the desk and the newsroom publishes its first investigations"
43
+ - year: "2023"
44
+ title: "Award season"
45
+ description: "Recognized for independent journalism with multiple industry awards — the corrections page stays proudly public"
46
+ - year: "{{ site.uj.date.year }}"
47
+ title: "Today"
48
+ description: "Millions of readers, one unchanged standard: report it right, write it sharp"
49
+
50
+ # Newsroom Stats
51
+ stats:
52
+ superheadline:
53
+ icon: "chart-line"
54
+ text: "Numbers"
55
+ headline: "The newsroom in"
56
+ headline_accent: "numbers"
57
+ subheadline: "The milestones that define the desk"
58
+ items:
59
+ - number: "2017"
60
+ label: "Founded"
61
+ icon: "calendar"
62
+ - number: "25+"
63
+ label: "Journalists"
64
+ icon: "pen-nib"
65
+ - number: "2M+"
66
+ label: "Monthly readers"
67
+ icon: "users"
68
+ - number: "120+"
69
+ label: "Countries"
70
+ icon: "globe"
71
+
72
+ # Newsroom Principles Section
73
+ values:
74
+ superheadline:
75
+ icon: "compass"
76
+ text: "Principles"
77
+ headline: "How we"
78
+ headline_accent: "report"
79
+ subheadline: "The standards behind every byline"
80
+ items:
81
+ - title: "Accuracy first"
82
+ description: "Every claim checked, every source named where possible. Being right beats being first — every time"
83
+ icon: "badge-check"
84
+ - title: "Fiercely independent"
85
+ description: "No corporate owner, no advertiser veto. Our readers are the only people we answer to"
86
+ icon: "scale-balanced"
87
+ - title: "Reader obsessed"
88
+ description: "We write for busy, curious people — clear over clever, context over clicks, five minutes well spent"
89
+ icon: "heart"
90
+ - title: "Radically transparent"
91
+ description: "Corrections published proudly, methods explained, conflicts disclosed. Trust is the whole product"
92
+ icon: "magnifying-glass"
93
+
94
+ # Testimonials Section — the morning-coffee crowd (universal key)
95
+ testimonials:
96
+ superheadline:
97
+ icon: "megaphone"
98
+ text: "From our readers"
99
+ headline: "Readers {% uj_icon \"heart\", \"text-danger\" %} "
100
+ headline_accent: "{{ site.brand.name }}"
101
+ subheadline: "What the morning-coffee crowd says about us"
102
+ items:
103
+ - quote: "The only newsletter I actually open every morning. Five minutes, fully briefed."
104
+ author: "Sarah Johnson"
105
+ role: "Product Lead"
106
+ company: "TechStart Inc"
107
+ initial: "S"
108
+ - quote: "They cover stories a week before everyone else — and with twice the context."
109
+ author: "Michael Chen"
110
+ role: "Founder"
111
+ company: "DataFlow"
112
+ initial: "M"
113
+ - quote: "Cancelled three subscriptions and kept this one. Sharpest writing in my inbox."
114
+ author: "Emily Davis"
115
+ role: "Editor"
116
+ company: "Creative Studio"
117
+ initial: "E"
118
+
119
+ # Team CTA Section
120
+ team_cta:
121
+ superheadline:
122
+ icon: "handshake"
123
+ text: "People"
124
+ headline: "The people behind the"
125
+ headline_accent: "bylines"
126
+ subheadline: "Get to know the reporters and editors of {{ site.brand.name }} who chase the stories that matter every day"
127
+ button:
128
+ text: "Meet the team"
129
+ icon: "arrow-right"
130
+ href: "/team"
131
+ ---
132
+
133
+ {% comment %}
134
+ NEWSFLASH ABOUT
135
+ Same data contract as the classy about page, presented as "about the
136
+ publication": serif masthead opening, a single-rail editorial timeline with
137
+ volt year chips, a vermilion mission slab + framed vision card, stroked
138
+ numerals, framed value desk cards, and the dark big-read team CTA band.
139
+ {% endcomment %}
140
+
141
+ <!-- Hero -->
142
+ <section class="pb-4">
143
+ <div class="container text-center" data-lazy="@class animation-slide-up">
144
+ <span class="kicker mb-3">About</span>
145
+ <h1 class="display-4 my-3">
146
+ {{ page.resolved.hero.headline }} <span class="text-accent">{{ page.resolved.hero.headline_accent }}</span>
147
+ </h1>
148
+ {% iftruthy page.resolved.hero.description %}
149
+ <p class="lead mb-0">{{ page.resolved.hero.description }}</p>
150
+ {% endiftruthy %}
151
+ </div>
152
+ </section>
153
+
154
+ <!-- Story timeline -->
155
+ <section class="pt-0">
156
+ <div class="container">
157
+ <div class="section-head" data-lazy="@class animation-slide-up">
158
+ {% iftruthy page.resolved.story.superheadline.text %}
159
+ <h2>{{ page.resolved.story.superheadline.text }}</h2>
160
+ {% endiftruthy %}
161
+ <span class="rule"></span>
162
+ </div>
163
+ <header class="mb-5" data-lazy="@class animation-slide-up">
164
+ <h2 class="h1 mb-2">
165
+ {{ page.resolved.story.headline }} <span class="text-accent">{{ page.resolved.story.headline_accent }}</span>
166
+ </h2>
167
+ {% iftruthy page.resolved.story.subheadline %}
168
+ <p class="lead mb-0">{{ page.resolved.story.subheadline }}</p>
169
+ {% endiftruthy %}
170
+ </header>
171
+
172
+ <div class="row justify-content-center">
173
+ <div class="col-lg-9">
174
+ <div class="timeline">
175
+ {% for item in page.resolved.story.items %}
176
+ <div class="timeline-item" data-lazy="@class animation-slide-up">
177
+ <span class="badge bg-body-tertiary timeline-year">{{ item.year }}</span>
178
+ <div class="timeline-body">
179
+ <h3 class="h4 mb-2">{{ item.title }}</h3>
180
+ <p class="text-body-secondary mb-0">{{ item.description }}</p>
181
+ </div>
182
+ </div>
183
+ {% endfor %}
184
+ </div>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ </section>
189
+
190
+ <!-- Mission & Vision -->
191
+ <section class="pt-0">
192
+ <div class="container">
193
+ <div class="row g-4">
194
+ <div class="col-lg-6" data-lazy="@class animation-slide-up">
195
+ <div class="card h-100 bg-primary text-white p-4 p-md-5 text-center">
196
+ <span class="feature-icon d-inline-flex align-items-center justify-content-center rounded-circle bg-white text-primary mb-3 mx-auto">
197
+ {% uj_icon page.resolved.mission.icon %}
198
+ </span>
199
+ <h3 class="h3 mb-3">{{ page.resolved.mission.title }}</h3>
200
+ <p class="fs-5 mb-0">{{ page.resolved.mission.description }}</p>
201
+ </div>
202
+ </div>
203
+
204
+ <div class="col-lg-6" data-lazy="@class animation-slide-up">
205
+ <div class="card h-100 p-4 p-md-5 text-center">
206
+ <span class="feature-icon d-inline-flex align-items-center justify-content-center rounded-circle text-bg-warning mb-3 mx-auto">
207
+ {% uj_icon page.resolved.vision.icon %}
208
+ </span>
209
+ <h3 class="h3 mb-3">{{ page.resolved.vision.title }}</h3>
210
+ <p class="fs-5 text-body-secondary mb-0">{{ page.resolved.vision.description }}</p>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ </div>
215
+ </section>
216
+
217
+ <!-- Stats -->
218
+ <section class="pt-0">
219
+ <div class="container">
220
+ <div class="section-head" data-lazy="@class animation-slide-up">
221
+ {% iftruthy page.resolved.stats.superheadline.text %}
222
+ <h2>{{ page.resolved.stats.superheadline.text }}</h2>
223
+ {% endiftruthy %}
224
+ <span class="rule"></span>
225
+ </div>
226
+ <header class="mb-5" data-lazy="@class animation-slide-up">
227
+ <h2 class="h1 mb-2">
228
+ {{ page.resolved.stats.headline }}
229
+ {% iftruthy page.resolved.stats.headline_accent %}
230
+ <span class="text-accent">{{ page.resolved.stats.headline_accent }}</span>
231
+ {% endiftruthy %}
232
+ </h2>
233
+ {% iftruthy page.resolved.stats.subheadline %}
234
+ <p class="lead mb-0">{{ page.resolved.stats.subheadline }}</p>
235
+ {% endiftruthy %}
236
+ </header>
237
+
238
+ <div class="row text-center g-4">
239
+ {% for stat in page.resolved.stats.items %}
240
+ <div class="col-lg-3 col-md-6" data-lazy="@class animation-slide-up">
241
+ <div class="stat-num">{{ stat.number }}</div>
242
+ <div class="fw-bold">{% uj_icon stat.icon, "me-2 text-primary" %}{{ stat.label }}</div>
243
+ </div>
244
+ {% endfor %}
245
+ </div>
246
+ </div>
247
+ </section>
248
+
249
+ <!-- Values -->
250
+ <section class="pt-0">
251
+ <div class="container">
252
+ <div class="section-head" data-lazy="@class animation-slide-up">
253
+ {% iftruthy page.resolved.values.superheadline.text %}
254
+ <h2>{{ page.resolved.values.superheadline.text }}</h2>
255
+ {% endiftruthy %}
256
+ <span class="rule"></span>
257
+ </div>
258
+ <header class="mb-5" data-lazy="@class animation-slide-up">
259
+ <h2 class="h1 mb-2">
260
+ {{ page.resolved.values.headline }} <span class="text-accent">{{ page.resolved.values.headline_accent }}</span>
261
+ </h2>
262
+ {% iftruthy page.resolved.values.subheadline %}
263
+ <p class="lead mb-0">{{ page.resolved.values.subheadline }}</p>
264
+ {% endiftruthy %}
265
+ </header>
266
+
267
+ <div class="row g-4">
268
+ {% for value in page.resolved.values.items %}
269
+ <div class="col-lg-3 col-md-6" data-lazy="@class animation-slide-up">
270
+ <div class="card h-100 p-4">
271
+ <span class="feature-icon d-inline-flex align-items-center justify-content-center rounded-circle text-bg-primary mb-3">
272
+ {% uj_icon value.icon %}
273
+ </span>
274
+ <h3 class="h5 mb-2">{{ value.title }}</h3>
275
+ <p class="text-body-secondary mb-0">{{ value.description }}</p>
276
+ </div>
277
+ </div>
278
+ {% endfor %}
279
+ </div>
280
+ </div>
281
+ </section>
282
+
283
+ <!-- Testimonials — letters to the editor (framed reader quotes) -->
284
+ {% iftruthy page.resolved.testimonials %}
285
+ <section class="pt-0">
286
+ <div class="container">
287
+ <div class="section-head" data-lazy="@class animation-slide-up">
288
+ {% iftruthy page.resolved.testimonials.superheadline.text %}
289
+ <h2>{{ page.resolved.testimonials.superheadline.text }}</h2>
290
+ {% endiftruthy %}
291
+ <span class="rule"></span>
292
+ </div>
293
+
294
+ <header class="mb-5" data-lazy="@class animation-slide-up">
295
+ <h2 class="h1 mb-2">
296
+ {{ page.resolved.testimonials.headline }}
297
+ {% iftruthy page.resolved.testimonials.headline_accent %}
298
+ <span class="text-accent">{{ page.resolved.testimonials.headline_accent }}</span>
299
+ {% endiftruthy %}
300
+ </h2>
301
+ {% iftruthy page.resolved.testimonials.subheadline %}
302
+ <p class="lead mb-0">{{ page.resolved.testimonials.subheadline }}</p>
303
+ {% endiftruthy %}
304
+ </header>
305
+
306
+ <div class="row g-4">
307
+ {% for testimonial in page.resolved.testimonials.items %}
308
+ <div class="col-md-4" data-lazy="@class animation-slide-up">
309
+ <figure class="card quote-card h-100 p-4 mb-0">
310
+ <p class="mb-3">{{ testimonial.quote }}</p>
311
+ <figcaption class="small text-body-secondary mt-auto">
312
+ <b class="text-body">{{ testimonial.author }}</b>
313
+ {% iftruthy testimonial.role %} · {{ testimonial.role }}{% endiftruthy %}{% iftruthy testimonial.company %}, {{ testimonial.company }}{% endiftruthy %}
314
+ </figcaption>
315
+ </figure>
316
+ </div>
317
+ {% endfor %}
318
+ </div>
319
+ </div>
320
+ </section>
321
+ {% endiftruthy %}
322
+
323
+ <!-- Team CTA — the dark big-read band -->
324
+ <section class="pt-0">
325
+ <div class="container">
326
+ <div class="cta-panel" data-lazy="@class animation-slide-up">
327
+ <div class="cta-rings" aria-hidden="true"></div>
328
+ <div class="position-relative">
329
+ {% iftruthy page.resolved.team_cta.superheadline.text %}
330
+ <span class="kicker mb-3">
331
+ {% uj_icon page.resolved.team_cta.superheadline.icon, "me-1" %}{{ page.resolved.team_cta.superheadline.text }}
332
+ </span>
333
+ {% endiftruthy %}
334
+ <h2 class="cta-title">
335
+ {{ page.resolved.team_cta.headline }}
336
+ {% iftruthy page.resolved.team_cta.headline_accent %}
337
+ <span class="text-accent">{{ page.resolved.team_cta.headline_accent }}</span>
338
+ {% endiftruthy %}
339
+ </h2>
340
+ {% iftruthy page.resolved.team_cta.subheadline %}
341
+ <p class="cta-desc">{{ page.resolved.team_cta.subheadline }}</p>
342
+ {% endiftruthy %}
343
+ <div class="d-flex flex-wrap gap-3">
344
+ <a href="{{ page.resolved.team_cta.button.href }}" class="btn btn-warning btn-lg">
345
+ {% uj_icon page.resolved.team_cta.button.icon, "me-2" %}{{ page.resolved.team_cta.button.text }}
346
+ </a>
347
+ </div>
348
+ </div>
349
+ </div>
350
+ </div>
351
+ </section>
352
+
353
+ {{ content | uj_content_format }}
@@ -0,0 +1,105 @@
1
+ ---
2
+ ### ALL PAGES ###
3
+ layout: themes/[ site.theme.id ]/frontend/core/base
4
+ asset_path: blog/categories/category
5
+
6
+ ### PAGE CONFIG ###
7
+ # Hero Section
8
+ hero:
9
+ subheadline: "Every story filed to this desk, newest first."
10
+ ---
11
+
12
+ {% comment %}
13
+ NEWSFLASH CATEGORY ARCHIVE
14
+ Same data contract as classy's category page (page.category.name/slug +
15
+ posts filtered from site.posts), presented as a desk front: breadcrumb,
16
+ serif desk headline, and the same story-card grid as the blog index.
17
+ {% endcomment %}
18
+
19
+ <!-- ============================================ -->
20
+ <!-- DESK 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/categories" class="text-decoration-none">Desks</a>
31
+ </li>
32
+ <li class="breadcrumb-item active" aria-current="page">{{ page.category.name }}</li>
33
+ </ol>
34
+ </nav>
35
+
36
+ <span class="kicker mb-2">The desk</span>
37
+ <h1 class="display-4 my-2">{{ page.category.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 category (max 100) {% endcomment %}
50
+ {% assign category_slug = page.category.slug %}
51
+ {% assign filtered_posts = '' | split: ',' %}
52
+ {% for post in site.posts %}
53
+ {% if filtered_posts.size >= 100 %}
54
+ {% break %}
55
+ {% endif %}
56
+ {% for category in post.post.categories %}
57
+ {% assign cat_slug = category | slugify %}
58
+ {% if cat_slug == category_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>Latest from this desk</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
+ <span class="kicker mb-1">{{ page.category.name }}</span>
78
+ <h3 class="h5 my-2">{{ post.post.title }}</h3>
79
+ <p class="small text-body-secondary mb-0">
80
+ <b class="text-body">{%- uj_member post.post.author, "name" -%}</b>
81
+ · {{ post.date | date: "%b %d, %Y" }}
82
+ · {% uj_readtime post.content %} min read
83
+ </p>
84
+ </a>
85
+ </div>
86
+ {% endfor %}
87
+ </div>
88
+ {% else %}
89
+ <div class="text-center py-5" data-lazy="@class animation-slide-up">
90
+ <span class="kicker mb-2">Nothing filed yet</span>
91
+ <h2 class="h3 mb-2">This desk is between stories</h2>
92
+ <p class="text-body-secondary mb-4">Check back soon — new coverage is on the way.</p>
93
+ </div>
94
+ {% endif %}
95
+
96
+ <!-- Back to the desks -->
97
+ <div class="text-center" data-lazy="@class animation-slide-up">
98
+ <a href="{{ site.url }}/blog/categories" class="btn btn-outline-dark">
99
+ {% uj_icon "arrow-left", "me-2" %}All desks
100
+ </a>
101
+ </div>
102
+ </div>
103
+ </section>
104
+
105
+ {{ content | uj_content_format }}