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,485 @@
1
+ ---
2
+ ### ALL PAGES ###
3
+ layout: themes/[ site.theme.id ]/frontend/core/base
4
+
5
+ ### PAGE CONFIG ###
6
+ # Hero Section — memberships fund the journalism. News voice throughout: a
7
+ # theme's frontmatter defaults are part of its identity (see docs/themes.md).
8
+ hero:
9
+ headline: "Journalism worth"
10
+ headline_accent: "paying for"
11
+ subheadline: "Simple memberships that keep the reporting independent. No hidden fees, cancel anytime."
12
+
13
+ # Pricing Section — reader membership tiers
14
+ pricing:
15
+ price_per_unit:
16
+ enabled: false
17
+ feature_id: "stories"
18
+ label: "story"
19
+ definitions:
20
+ - id: "stories"
21
+ definition: "Member-only stories you can read each month, on top of our free coverage."
22
+ - id: "briefing"
23
+ definition: "The daily morning briefing delivered to your inbox before 6am."
24
+ - id: "ad_free"
25
+ definition: "Read every page without ads or sponsored placements."
26
+ - id: "archive"
27
+ definition: "Full access to every story we've ever published."
28
+ - id: "comments"
29
+ definition: "Join the discussion with our reporters and fellow members."
30
+ - id: "events"
31
+ definition: "Invites to member-only briefings, AMAs, and live events."
32
+ plans:
33
+ - id: "reader"
34
+ name: "Reader"
35
+ tagline: "best for casual readers"
36
+ url: "/signup"
37
+ features:
38
+ - id: "stories"
39
+ name: "Free stories"
40
+ icon: "newspaper"
41
+ - id: "briefing"
42
+ name: "Daily briefing"
43
+ icon: "envelope"
44
+ - id: "supporter"
45
+ name: "Supporter"
46
+ tagline: "best for daily readers"
47
+ url: null
48
+ features:
49
+ - id: "stories"
50
+ name: "Member stories"
51
+ icon: "newspaper"
52
+ - id: "briefing"
53
+ name: "Daily briefing"
54
+ icon: "envelope"
55
+ - id: "ad_free"
56
+ name: "Ad-free reading"
57
+ icon: "eye-slash"
58
+ - id: "comments"
59
+ name: "Comments"
60
+ icon: "comments"
61
+ - id: "insider"
62
+ name: "Insider"
63
+ tagline: "best for news junkies"
64
+ url: null
65
+ popular: true
66
+ features:
67
+ - id: "stories"
68
+ name: "Member stories"
69
+ icon: "newspaper"
70
+ - id: "briefing"
71
+ name: "Daily briefing"
72
+ icon: "envelope"
73
+ - id: "archive"
74
+ name: "Full archive"
75
+ icon: "box-archive"
76
+ - id: "newsroom"
77
+ name: "Newsroom"
78
+ tagline: "best for teams"
79
+ url: null
80
+ features:
81
+ - id: "stories"
82
+ name: "Member stories"
83
+ icon: "newspaper"
84
+ - id: "briefing"
85
+ name: "Daily briefing"
86
+ icon: "envelope"
87
+ - id: "events"
88
+ name: "Member events"
89
+ icon: "calendar-star"
90
+ - id: "seats"
91
+ name: "Team seats"
92
+ icon: "users"
93
+
94
+ # Feature Comparison Section
95
+ feature_comparison:
96
+ superheadline:
97
+ icon: "sparkles"
98
+ text: "Features"
99
+ headline: "Compare all"
100
+ headline_accent: "memberships"
101
+ subheadline: "See exactly what's included in each membership"
102
+
103
+ # Social Proof Section
104
+ social_proof:
105
+ items:
106
+ - number: "2M"
107
+ label: "Monthly readers"
108
+ - number: "#1"
109
+ label: "Independent newsroom"
110
+ - number: "4.8"
111
+ label: "Apple News"
112
+ - number: "4.8"
113
+ label: "Google News"
114
+
115
+ # CTA Section
116
+ cta:
117
+ superheadline:
118
+ icon: "comments"
119
+ text: "Support"
120
+ headline: "Questions about your"
121
+ headline_accent: "membership?"
122
+ subheadline: "The support desk answers 24/7"
123
+ button:
124
+ text: "Talk to us"
125
+ icon: "headset"
126
+ href: "/contact"
127
+
128
+ # FAQs Section
129
+ faqs:
130
+ superheadline:
131
+ icon: "messages-question"
132
+ text: "FAQs"
133
+ headline: "Frequently asked"
134
+ headline_accent: "questions"
135
+ subheadline: "Everything you need to know about {{ site.brand.name }} memberships & billing."
136
+ items:
137
+ - question: "Can I cancel my membership anytime?"
138
+ answer: "Yes, you can cancel anytime, no questions asked. Your member benefits stay active until the end of the billing period."
139
+ - question: "Is there a free way to read?"
140
+ answer: "Always. The Reader tier is free forever — our core coverage and the daily briefing stay open to everyone. Memberships unlock the extras and keep the lights on."
141
+ - question: "Do memberships really fund the journalism?"
142
+ answer: "Directly. Reader memberships are our primary source of revenue, which is exactly why no advertiser or owner gets a say in what we publish."
143
+ ---
144
+
145
+ {% comment %}
146
+ NEWSFLASH PRICING
147
+ Same data contract + price-resolution Liquid as the classy pricing page,
148
+ presented as the "subscription desk": pill billing toggle, framed paper plan
149
+ cards (the popular plan is the vermilion-framed "Editor's pick"), an
150
+ enterprise strip, stroked-numeral social proof, FAQ accordion, and the dark
151
+ big-read CTA band. See docs/themes.md "Per-page HTML layout overrides".
152
+ {% endcomment %}
153
+
154
+ <!-- ============================================ -->
155
+ <!-- PROMO BANNER — revealed + populated by the framework pricing JS
156
+ (sale name, countdown, navbar offset). The ids are the contract;
157
+ ship it hidden and let the JS do the rest. -->
158
+ <!-- ============================================ -->
159
+ <div id="pricing-promo-banner" class="position-fixed top-0 start-0 w-100 text-center animation-slide-down" style="z-index: 1050;" hidden>
160
+ <div class="bg-primary text-white py-2 position-relative">
161
+ <button type="button" class="btn-close btn-close-white position-absolute top-0 end-0 mt-2 me-2" aria-label="Close" onclick="this.closest('#pricing-promo-banner').hidden=true;document.querySelector('.navbar-wrapper').style.marginTop='';document.querySelector('main > section:first-of-type').style.paddingTop=''"></button>
162
+ <div class="container-fluid">
163
+ <div class="d-flex align-items-center justify-content-center gap-3 flex-wrap pe-4">
164
+ <span id="pricing-promo-badge" class="badge bg-warning text-dark px-2 py-1 fs-6 animation-wiggle">
165
+ 15% OFF!
166
+ </span>
167
+ <span id="pricing-promo-text" class="fw-bold">
168
+ Flash sale
169
+ </span>
170
+ <span class="text-white-50">
171
+ Ending in <span id="pricing-promo-countdown" class="fw-semibold text-white">--</span>
172
+ </span>
173
+ <span class="text-white-50">
174
+ Use code <span id="pricing-promo-code" class="badge bg-white bg-opacity-25 px-2 py-1 fs-6">WELCOME15</span>
175
+ </span>
176
+ </div>
177
+ </div>
178
+ </div>
179
+ </div>
180
+
181
+ <!-- ============================================ -->
182
+ <!-- HERO -->
183
+ <!-- ============================================ -->
184
+ <section class="pb-4">
185
+ <div class="container text-center" data-lazy="@class animation-slide-up">
186
+ <span class="kicker mb-3">{% uj_icon "tag", "me-1" %}Pricing</span>
187
+ <h1 class="display-4 my-3">
188
+ {{ page.resolved.hero.headline }}
189
+ <span class="text-accent">{{ page.resolved.hero.headline_accent }}</span>
190
+ </h1>
191
+ {% iftruthy page.resolved.hero.subheadline %}
192
+ <p class="lead mb-0">{{ page.resolved.hero.subheadline }}</p>
193
+ {% endiftruthy %}
194
+ </div>
195
+ </section>
196
+
197
+ <!-- ============================================ -->
198
+ <!-- PLANS -->
199
+ <!-- ============================================ -->
200
+ <section class="pt-0">
201
+ <div class="container">
202
+ <!-- Billing toggle: pill segmented control -->
203
+ <div class="billing-toggle" role="group" aria-label="Billing toggle">
204
+ <input type="radio" class="btn-check" name="billing" id="monthly" autocomplete="off" data-billing="monthly">
205
+ <label class="billing-option" for="monthly">Monthly</label>
206
+ <input type="radio" class="btn-check" name="billing" id="annually" autocomplete="off" checked data-billing="annually">
207
+ <label class="billing-option" for="annually">Annually <span class="billing-save">up to 20% off</span></label>
208
+ </div>
209
+
210
+ <p class="text-center fw-medium mb-5">
211
+ {% uj_icon "shield-check", "me-1 text-success" %}
212
+ Every paid membership backed by a 7-day money-back guarantee
213
+ </p>
214
+
215
+ {% assign plan_count = page.resolved.pricing.plans | size %}
216
+
217
+ {% comment %} Detect common features across ALL plans (same logic as classy) {% endcomment %}
218
+ {% assign common_feature_ids = "" | split: "," %}
219
+ {% if plan_count > 0 %}
220
+ {% assign first_plan = page.resolved.pricing.plans[0] %}
221
+ {% for first_feature in first_plan.features %}
222
+ {% assign is_common = true %}
223
+ {% for check_plan in page.resolved.pricing.plans %}
224
+ {% assign found_in_plan = false %}
225
+ {% for check_feature in check_plan.features %}
226
+ {% if check_feature.id == first_feature.id %}{% assign found_in_plan = true %}{% break %}{% endif %}
227
+ {% endfor %}
228
+ {% unless found_in_plan %}{% assign is_common = false %}{% break %}{% endunless %}
229
+ {% endfor %}
230
+ {% if is_common %}{% assign common_feature_ids = common_feature_ids | push: first_feature.id %}{% endif %}
231
+ {% endfor %}
232
+ {% endif %}
233
+
234
+ <div class="pricing-plan-grid">
235
+ {% for plan in page.resolved.pricing.plans %}
236
+ {% comment %} Shared pricing math (assigns _plan_monthly/_plan_annually/per-unit into this scope) {% endcomment %}
237
+ {% include core/pricing/resolve-plan.html plan=plan %}
238
+
239
+ <article class="card pricing-plan {% if plan.popular %}pricing-plan--popular{% endif %}">
240
+ {% if plan.popular %}
241
+ <span class="pricing-plan-flag badge bg-body-tertiary">Editor's pick</span>
242
+ {% endif %}
243
+
244
+ <!-- .card-title is load-bearing: the framework pricing JS reads the plan
245
+ name for analytics via `.h3, .h2, .card-title` inside the card. -->
246
+ <h3 class="card-title pricing-plan-name">{{ plan.name }}</h3>
247
+ <p class="pricing-plan-tagline kicker">{{ plan.tagline }}</p>
248
+
249
+ <!-- Price -->
250
+ <div class="pricing-plan-price">
251
+ {% if _plan_monthly == 0 %}
252
+ <span class="pricing-plan-amount">Free</span>
253
+ {% else %}
254
+ <span class="pricing-plan-currency">$</span><span class="pricing-plan-amount amount" data-monthly="{{ _plan_monthly }}" data-annually="{{ _plan_annually | divided_by: 12 | round }}">{{ _plan_annually | divided_by: 12 | round }}</span><span class="pricing-plan-per">/mo</span>
255
+ {% endif %}
256
+ </div>
257
+
258
+ {% if page.resolved.pricing.price_per_unit.enabled %}
259
+ <p class="pricing-plan-ppu">
260
+ {% if monthly_price_per_unit %}
261
+ <span class="price-per-unit" data-monthly="${{ monthly_price_per_unit }}" data-annually="${{ annual_price_per_unit }}">${{ annual_price_per_unit }}</span> per {{ page.resolved.pricing.price_per_unit.label }}
262
+ {% elsif _plan_monthly == 0 %}
263
+ Perfect for trying out
264
+ {% endif %}
265
+ </p>
266
+ {% endif %}
267
+
268
+ <!-- CTA: paid plans use the solid vermilion button; only the FREE
269
+ plan uses the outline variant so it reads as the entry-level option. -->
270
+ <div class="d-grid mb-3">
271
+ {% if _plan_monthly == 0 %}{% assign _cta_class = "btn-outline-dark" %}{% else %}{% assign _cta_class = "btn-primary" %}{% endif %}
272
+ {% iftruthy plan.url %}
273
+ <a href="{{ plan.url }}" class="btn {{ _cta_class }} btn-lg fw-bold">
274
+ {% if _plan_monthly == 0 %}Get started{% elsif plan.trial.days > 0 %}Get free trial{% else %}Get started{% endif %}
275
+ </a>
276
+ {% endiftruthy %}
277
+ {% iffalsy plan.url %}
278
+ <button class="btn {{ _cta_class }} btn-lg fw-bold" data-plan-id="{{ plan.id }}">
279
+ {% if plan.trial.days > 0 %}Get free trial{% else %}Get started{% endif %}
280
+ </button>
281
+ {% endiffalsy %}
282
+ </div>
283
+
284
+ <!-- Billing info -->
285
+ <p class="pricing-plan-billing">
286
+ {% if _plan_monthly == 0 %}
287
+ No credit card required
288
+ {% else %}
289
+ <span class="billing-info" data-monthly="Billed ${{ _plan_monthly | uj_commaify }} monthly" data-annually="Billed ${{ _plan_annually | uj_commaify }} annually">
290
+ Billed ${{ _plan_annually | uj_commaify }} annually
291
+ </span>
292
+ {% endif %}
293
+ </p>
294
+
295
+ <!-- Guarantee (every plan) -->
296
+ <p class="pricing-plan-guarantee">
297
+ {% if _plan_monthly > 0 %}
298
+ {% uj_icon "shield-check", "me-1 text-success" %}7-day money-back guarantee
299
+ {% else %}
300
+ {% uj_icon "rocket", "me-1 text-success" %}Upgrade any time
301
+ {% endif %}
302
+ </p>
303
+
304
+ <hr class="pricing-plan-rule">
305
+
306
+ {% comment %}
307
+ Feature list — same tier-inheritance logic as classy:
308
+ 1. Common features (vary by plan, shown on every card)
309
+ 2. Basic (first plan): "What you get:" + its unique features
310
+ 3. Other plans: "Everything in <prev>, and more:" + only the ADDED features
311
+ Helpers below render a value + the name (wrapped in a tooltip span when the
312
+ feature has a definition).
313
+ {% endcomment %}
314
+
315
+ <!-- 1. Common features -->
316
+ <ul class="pricing-plan-features">
317
+ {% for feature in plan.features %}
318
+ {% if common_feature_ids contains feature.id %}
319
+ {% assign _feature_value = feature.value %}
320
+ {% if _config_product and _feature_value == nil %}
321
+ {% assign _config_limit = nil %}{% for _lim in _config_product.limits %}{% if _lim[0] == feature.id %}{% assign _config_limit = _lim[1] %}{% break %}{% endif %}{% endfor %}
322
+ {% if _config_limit == -1 %}{% assign _feature_value = "Unlimited" %}{% elsif _config_limit %}{% assign _feature_value = _config_limit %}{% endif %}
323
+ {% endif %}
324
+ {% assign feature_definition = nil %}
325
+ {% for def in page.resolved.pricing.definitions %}{% if def.id == feature.id %}{% assign feature_definition = def.definition %}{% break %}{% endif %}{% endfor %}
326
+ <li>
327
+ <span class="pricing-plan-feature-icon">{% uj_icon feature.icon, "fa-sm" %}</span>
328
+ <span>
329
+ {% if _feature_value == "Unlimited" %}Unlimited{% elsif _feature_value == "24/7" %}{{ _feature_value }}{% elsif _feature_value == true or _feature_value == "Included" or _feature_value == "Available" or _feature_value == "Full" %}{% else %}{{ _feature_value | uj_commaify }}{% endif %}
330
+ {% iftruthy feature_definition %}<span class="text-decoration-underline text-decoration-dotted cursor-help" data-bs-toggle="tooltip" data-bs-title="{{ feature_definition }}">{{ feature.name }}</span>{% endiftruthy %}
331
+ {% iffalsy feature_definition %}{{ feature.name }}{% endiffalsy %}
332
+ </span>
333
+ </li>
334
+ {% endif %}
335
+ {% endfor %}
336
+ </ul>
337
+
338
+ {% comment %} Does this plan add any non-common (unique) features? {% endcomment %}
339
+ {% assign has_additional = false %}
340
+ {% for feature in plan.features %}{% unless common_feature_ids contains feature.id %}{% assign has_additional = true %}{% break %}{% endunless %}{% endfor %}
341
+
342
+ <!-- 2/3. Inheritance label + the plan's additional features -->
343
+ {% if forloop.index == 1 %}
344
+ {% if has_additional %}
345
+ <p class="pricing-plan-inherit">What you get:</p>
346
+ {% endif %}
347
+ {% else %}
348
+ {% assign _prev_index = forloop.index0 | minus: 1 %}
349
+ {% assign _prev_plan = page.resolved.pricing.plans[_prev_index] %}
350
+ <p class="pricing-plan-inherit">Everything in <strong>{{ _prev_plan.name }}</strong>{% if has_additional %}, and more:{% endif %}</p>
351
+ {% endif %}
352
+
353
+ {% if has_additional %}
354
+ <ul class="pricing-plan-features">
355
+ {% for feature in plan.features %}
356
+ {% unless common_feature_ids contains feature.id %}
357
+ {% assign _feature_value = feature.value %}
358
+ {% if _config_product and _feature_value == nil %}
359
+ {% assign _config_limit = nil %}{% for _lim in _config_product.limits %}{% if _lim[0] == feature.id %}{% assign _config_limit = _lim[1] %}{% break %}{% endif %}{% endfor %}
360
+ {% if _config_limit == -1 %}{% assign _feature_value = "Unlimited" %}{% elsif _config_limit %}{% assign _feature_value = _config_limit %}{% endif %}
361
+ {% endif %}
362
+ {% assign feature_definition = nil %}
363
+ {% for def in page.resolved.pricing.definitions %}{% if def.id == feature.id %}{% assign feature_definition = def.definition %}{% break %}{% endif %}{% endfor %}
364
+ <li>
365
+ <span class="pricing-plan-feature-icon">{% uj_icon feature.icon, "fa-sm" %}</span>
366
+ <span>
367
+ {% if _feature_value == "Unlimited" %}Unlimited{% elsif _feature_value == "24/7" %}{{ _feature_value }}{% elsif _feature_value == true or _feature_value == "Included" or _feature_value == "Available" or _feature_value == "Full" %}{% else %}{{ _feature_value | uj_commaify }}{% endif %}
368
+ {% iftruthy feature_definition %}<span class="text-decoration-underline text-decoration-dotted cursor-help" data-bs-toggle="tooltip" data-bs-title="{{ feature_definition }}">{{ feature.name }}</span>{% endiftruthy %}
369
+ {% iffalsy feature_definition %}{{ feature.name }}{% endiffalsy %}
370
+ </span>
371
+ </li>
372
+ {% endunless %}
373
+ {% endfor %}
374
+ </ul>
375
+ {% endif %}
376
+ </article>
377
+ {% endfor %}
378
+ </div>
379
+
380
+ <!-- Group access strip -->
381
+ <div class="card enterprise-panel" data-lazy="@class animation-slide-up">
382
+ <div>
383
+ <h3 class="h3 mb-1">Group & campus access</h3>
384
+ <p class="mb-0 text-body-secondary">Bulk memberships for companies, newsrooms, schools, and libraries — custom invoicing and easy seat management.</p>
385
+ </div>
386
+ <button class="btn btn-primary btn-lg fw-bold flex-shrink-0" data-plan-id="enterprise">
387
+ {% uj_icon "envelope", "me-2" %}Contact us
388
+ </button>
389
+ </div>
390
+ </div>
391
+ </section>
392
+
393
+ <!-- ============================================ -->
394
+ <!-- SOCIAL PROOF — by the numbers -->
395
+ <!-- ============================================ -->
396
+ <section class="pt-0">
397
+ <div class="container">
398
+ <div class="section-head" data-lazy="@class animation-slide-up">
399
+ <h2>The receipts</h2>
400
+ <span class="rule"></span>
401
+ </div>
402
+ <div class="row g-4 text-center">
403
+ {% for item in page.resolved.social_proof.items %}
404
+ <div class="col-6 col-lg-3" data-lazy="@class animation-slide-up">
405
+ <div class="stat-num">{{ item.number }}</div>
406
+ <div class="fw-bold">{{ item.label }}</div>
407
+ </div>
408
+ {% endfor %}
409
+ </div>
410
+ </div>
411
+ </section>
412
+
413
+ <!-- ============================================ -->
414
+ <!-- FAQ -->
415
+ <!-- ============================================ -->
416
+ <section class="pt-0">
417
+ <div class="container">
418
+ <div class="row justify-content-center">
419
+ <div class="col-lg-9">
420
+ <div class="section-head" data-lazy="@class animation-slide-up">
421
+ {% iftruthy page.resolved.faqs.superheadline.text %}
422
+ <h2>{{ page.resolved.faqs.superheadline.text }}</h2>
423
+ {% endiftruthy %}
424
+ <span class="rule"></span>
425
+ </div>
426
+ <header class="mb-4" data-lazy="@class animation-slide-up">
427
+ <h2 class="h1 mb-2">
428
+ {{ page.resolved.faqs.headline }}
429
+ <span class="text-accent">{{ page.resolved.faqs.headline_accent }}</span>
430
+ </h2>
431
+ {% iftruthy page.resolved.faqs.subheadline %}
432
+ <p class="lead mb-0">{{ page.resolved.faqs.subheadline }}</p>
433
+ {% endiftruthy %}
434
+ </header>
435
+
436
+ {% if page.resolved.faqs.items %}
437
+ <div class="accordion" id="faqAccordion">
438
+ {% for faq in page.resolved.faqs.items %}
439
+ <div class="accordion-item mb-3" data-lazy="@class animation-slide-up">
440
+ <h2 class="accordion-header">
441
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faq{{ forloop.index }}" aria-expanded="false" aria-controls="faq{{ forloop.index }}">
442
+ <span class="fw-bold">{{ faq.question }}</span>
443
+ </button>
444
+ </h2>
445
+ <div id="faq{{ forloop.index }}" class="accordion-collapse collapse" data-bs-parent="#faqAccordion">
446
+ <div class="accordion-body">{{ faq.answer }}</div>
447
+ </div>
448
+ </div>
449
+ {% endfor %}
450
+ </div>
451
+ {% endif %}
452
+ </div>
453
+ </div>
454
+ </div>
455
+ </section>
456
+
457
+ <!-- ============================================ -->
458
+ <!-- CTA — the dark big-read band -->
459
+ <!-- ============================================ -->
460
+ <section class="pt-0">
461
+ <div class="container">
462
+ <div class="cta-panel" data-lazy="@class animation-slide-up">
463
+ <div class="cta-rings" aria-hidden="true"></div>
464
+ <div class="position-relative">
465
+ {% iftruthy page.resolved.cta.superheadline.text %}
466
+ <span class="kicker mb-3">{% uj_icon page.resolved.cta.superheadline.icon, "me-1" %}{{ page.resolved.cta.superheadline.text }}</span>
467
+ {% endiftruthy %}
468
+ <h2 class="cta-title">
469
+ {{ page.resolved.cta.headline }}
470
+ <span class="text-accent">{{ page.resolved.cta.headline_accent }}</span>
471
+ </h2>
472
+ {% iftruthy page.resolved.cta.subheadline %}
473
+ <p class="cta-desc">{{ page.resolved.cta.subheadline }}</p>
474
+ {% endiftruthy %}
475
+ <div class="d-flex flex-wrap gap-3">
476
+ <a href="{{ page.resolved.cta.button.href }}" class="btn btn-warning btn-lg">
477
+ {% uj_icon page.resolved.cta.button.icon, "me-2" %}{{ page.resolved.cta.button.text }}
478
+ </a>
479
+ </div>
480
+ </div>
481
+ </div>
482
+ </div>
483
+ </section>
484
+
485
+ {{ content | uj_content_format }}