jekyll-theme-zer0 1.28.0 → 1.29.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +430 -13
  3. data/_data/backlog.yml +356 -2
  4. data/_data/consumers.yml +109 -4
  5. data/_data/features.yml +157 -0
  6. data/_data/i18n/fr.yml +12 -7
  7. data/_data/i18n/manifest.yml +39 -12
  8. data/_data/ingredient_densities.yml +122 -0
  9. data/_data/navigation/main.yml +16 -0
  10. data/_data/recipe_courses.yml +64 -0
  11. data/_data/theme-manifest.yml +68 -40
  12. data/_data/ui-text.yml +8 -0
  13. data/_includes/README.md +15 -1
  14. data/_includes/components/background-customizer.html +2 -2
  15. data/_includes/components/cookie-consent.html +4 -4
  16. data/_includes/components/info-section.html +8 -3
  17. data/_includes/components/language-toggle.html +168 -21
  18. data/_includes/components/page-views-init.html +55 -0
  19. data/_includes/components/page-views.html +33 -0
  20. data/_includes/components/recipe-card.html +67 -0
  21. data/_includes/components/recipe-duration.html +50 -0
  22. data/_includes/components/recipe-grams.html +58 -0
  23. data/_includes/components/recipe-index.html +96 -0
  24. data/_includes/components/recipe-ingredients.html +90 -0
  25. data/_includes/components/recipe-meta.html +96 -0
  26. data/_includes/components/recipe-nutrition.html +57 -0
  27. data/_includes/components/recipe-qty.html +73 -0
  28. data/_includes/components/recipe-ratio.html +151 -0
  29. data/_includes/components/recipe-scaler.html +73 -0
  30. data/_includes/components/recipe-steps.html +86 -0
  31. data/_includes/components/recipe-temp.html +45 -0
  32. data/_includes/components/search-modal.html +2 -2
  33. data/_includes/components/shortcuts-modal.html +3 -0
  34. data/_includes/components/theme-info.html +8 -1
  35. data/_includes/content/jsonld-software.html +22 -3
  36. data/_includes/core/footer.html +18 -16
  37. data/_includes/core/head.html +12 -0
  38. data/_includes/core/header.html +7 -4
  39. data/_includes/navigation/section-sidebar.html +93 -11
  40. data/_includes/navigation/sidebar-left.html +2 -1
  41. data/_includes/navigation/sidebar-right.html +2 -1
  42. data/_includes/setup/wizard.html +173 -86
  43. data/_includes/stats/stats-metrics.html +2 -2
  44. data/_layouts/README.md +2 -0
  45. data/_layouts/admin.html +2 -2
  46. data/_layouts/article.html +5 -0
  47. data/_layouts/cookbook.html +88 -0
  48. data/_layouts/default.html +4 -4
  49. data/_layouts/recipe.html +274 -0
  50. data/_layouts/root.html +22 -3
  51. data/_layouts/section.html +39 -10
  52. data/_sass/components/_callout.scss +1 -1
  53. data/_sass/components/_footer.scss +37 -1
  54. data/_sass/components/_page-views.scss +36 -0
  55. data/_sass/components/_recipe.scss +506 -0
  56. data/_sass/components/_setup-wizard.scss +235 -0
  57. data/_sass/components/_ui-enhancements.scss +6 -6
  58. data/_sass/core/_navbar.scss +250 -15
  59. data/_sass/layouts/_landing.scss +2 -2
  60. data/_sass/tokens/_color.scss +6 -0
  61. data/_sass/tokens/_index.scss +2 -0
  62. data/_sass/tokens/_radius.scss +21 -0
  63. data/_sass/tokens/_typography.scss +4 -0
  64. data/_sass/utilities/_focus.scss +14 -0
  65. data/assets/css/main.scss +3 -0
  66. data/assets/js/modules/navigation/navbar.js +55 -0
  67. data/assets/js/page-views.js +372 -0
  68. data/assets/js/recipe-scaler.js +501 -0
  69. data/assets/js/search-modal.js +10 -0
  70. data/assets/js/setup-wizard.js +322 -20
  71. data/scripts/README.md +29 -0
  72. data/scripts/bin/audit-consumer +39 -7
  73. data/scripts/bin/giscus-discussions +213 -14
  74. data/scripts/bin/manifest +35 -12
  75. data/scripts/ci/agent_review_result.py +164 -0
  76. data/scripts/ci/test_agent_review_result.py +172 -0
  77. data/scripts/design-system-check.rb +170 -0
  78. data/scripts/lib/audit.sh +42 -2
  79. data/scripts/lint-liquid-raw.rb +137 -0
  80. data/scripts/test/lib/run_tests.sh +2 -1
  81. data/scripts/test/lib/test_agent_review_result.sh +27 -0
  82. data/scripts/translate.rb +71 -15
  83. metadata +31 -2
@@ -9,69 +9,124 @@
9
9
  Purpose: Multi-step Bootstrap form that builds a complete _config.yml
10
10
  and lets the user download or copy it.
11
11
 
12
- Dependencies: Bootstrap 5.3, assets/js/setup-wizard.js
12
+ Dependencies: Bootstrap 5.3, assets/js/setup-wizard.js,
13
+ _sass/components/_setup-wizard.scss
13
14
  Environment: Development only (guarded by calling page)
15
+
16
+ Layout (T-040 / #408) — three columns at lg+, stacked below:
17
+ 1. Left — vertical stepper (#wizardTabs). Replaces the nav-pills row.
18
+ Steps carry .is-done / .is-active / .is-locked; an upcoming
19
+ step is `disabled` until every earlier step validates, and
20
+ going BACK is always allowed.
21
+ 2. Middle— the step panes (#wizardTabContent). The container gets a
22
+ min-height equal to the TALLEST pane (measured in JS, since
23
+ CSS cannot know it), and each pane's nav row is pinned to the
24
+ bottom with mt-auto — together that is what stops Back/Next
25
+ moving vertically between steps.
26
+ 3. Right — the live YAML preview. Sticky, present at EVERY step (it used
27
+ to exist only inside step 5), regenerated on every input, with
28
+ Copy and Download always enabled.
29
+
30
+ Draft persistence: every field is mirrored to localStorage under
31
+ `zer0-setup-draft` (debounced 300ms) and restored on load; the "Draft
32
+ saved" chip acknowledges it, and downloading the file clears it.
33
+
34
+ NOTE: T-040 also asked to "remove the N% complete math". There is no such
35
+ code in this component and there never was — see the analysis on #408.
36
+ Nothing was removed for that clause, and no percentage indicator should be
37
+ added in order to remove one.
14
38
  ===================================================================
15
39
  -->
16
40
 
17
41
  <div class="container py-4" id="setup-wizard">
18
- <div class="row justify-content-center">
19
- <div class="col-lg-10">
20
-
21
- <h1 class="mb-2"><i class="bi bi-gear-wide-connected"></i> Site Setup Wizard</h1>
22
- <p class="lead text-muted mb-4">
23
- Fill in the form to generate a personalised <code>_config.yml</code>.
24
- Download it and replace the one in your project root.
25
- </p>
26
-
27
- <!-- ============================================================ -->
28
- <!-- Progress Tabs -->
29
- <!-- ============================================================ -->
30
- <ul class="nav nav-pills nav-fill mb-4" id="wizardTabs" role="tablist">
31
- <li class="nav-item" role="presentation">
32
- <button class="nav-link active" id="tab-identity"
42
+
43
+ <h1 class="mb-2"><i class="bi bi-gear-wide-connected"></i> Site Setup Wizard</h1>
44
+ <p class="lead text-muted mb-4">
45
+ Fill in the form to generate a personalised <code>_config.yml</code>.
46
+ Download it and replace the one in your project root.
47
+ </p>
48
+
49
+ <div class="row g-4 wizard-grid">
50
+
51
+ <!-- ============================================================ -->
52
+ <!-- 1. Vertical stepper -->
53
+ <!-- ============================================================ -->
54
+ <div class="col-lg-3">
55
+ <div class="wizard-stepper-wrap sticky-lg-top">
56
+ <div class="nav flex-column wizard-stepper" id="wizardTabs" role="tablist"
57
+ aria-orientation="vertical" aria-label="Setup steps">
58
+
59
+ <button class="wizard-step is-active" id="tab-identity"
33
60
  data-bs-toggle="pill" data-bs-target="#step-identity"
34
- type="button" role="tab">
35
- <i class="bi bi-person-badge"></i> Identity
61
+ type="button" role="tab" aria-controls="step-identity"
62
+ aria-selected="true" data-step="1">
63
+ <span class="wizard-step-marker" aria-hidden="true"><i class="bi bi-person-badge"></i></span>
64
+ <span class="wizard-step-text">
65
+ <span class="wizard-step-label">Identity</span>
66
+ <span class="wizard-step-state visually-hidden">current step</span>
67
+ </span>
36
68
  </button>
37
- </li>
38
- <li class="nav-item" role="presentation">
39
- <button class="nav-link" id="tab-urls"
69
+
70
+ <button class="wizard-step" id="tab-urls"
40
71
  data-bs-toggle="pill" data-bs-target="#step-urls"
41
- type="button" role="tab">
42
- <i class="bi bi-link-45deg"></i> URLs
72
+ type="button" role="tab" aria-controls="step-urls"
73
+ aria-selected="false" data-step="2">
74
+ <span class="wizard-step-marker" aria-hidden="true"><i class="bi bi-link-45deg"></i></span>
75
+ <span class="wizard-step-text">
76
+ <span class="wizard-step-label">URLs</span>
77
+ <span class="wizard-step-state visually-hidden"></span>
78
+ </span>
43
79
  </button>
44
- </li>
45
- <li class="nav-item" role="presentation">
46
- <button class="nav-link" id="tab-collections"
80
+
81
+ <button class="wizard-step" id="tab-collections"
47
82
  data-bs-toggle="pill" data-bs-target="#step-collections"
48
- type="button" role="tab">
49
- <i class="bi bi-collection"></i> Collections
83
+ type="button" role="tab" aria-controls="step-collections"
84
+ aria-selected="false" data-step="3">
85
+ <span class="wizard-step-marker" aria-hidden="true"><i class="bi bi-collection"></i></span>
86
+ <span class="wizard-step-text">
87
+ <span class="wizard-step-label">Collections</span>
88
+ <span class="wizard-step-state visually-hidden"></span>
89
+ </span>
50
90
  </button>
51
- </li>
52
- <li class="nav-item" role="presentation">
53
- <button class="nav-link" id="tab-analytics"
91
+
92
+ <button class="wizard-step" id="tab-analytics"
54
93
  data-bs-toggle="pill" data-bs-target="#step-analytics"
55
- type="button" role="tab">
56
- <i class="bi bi-graph-up"></i> Analytics
94
+ type="button" role="tab" aria-controls="step-analytics"
95
+ aria-selected="false" data-step="4">
96
+ <span class="wizard-step-marker" aria-hidden="true"><i class="bi bi-graph-up"></i></span>
97
+ <span class="wizard-step-text">
98
+ <span class="wizard-step-label">Analytics</span>
99
+ <span class="wizard-step-state visually-hidden"></span>
100
+ </span>
57
101
  </button>
58
- </li>
59
- <li class="nav-item" role="presentation">
60
- <button class="nav-link" id="tab-review"
102
+
103
+ <button class="wizard-step" id="tab-review"
61
104
  data-bs-toggle="pill" data-bs-target="#step-review"
62
- type="button" role="tab">
63
- <i class="bi bi-file-earmark-code"></i> Review
105
+ type="button" role="tab" aria-controls="step-review"
106
+ aria-selected="false" data-step="5">
107
+ <span class="wizard-step-marker" aria-hidden="true"><i class="bi bi-file-earmark-code"></i></span>
108
+ <span class="wizard-step-text">
109
+ <span class="wizard-step-label">Review</span>
110
+ <span class="wizard-step-state visually-hidden"></span>
111
+ </span>
64
112
  </button>
65
- </li>
66
- </ul>
113
+ </div>
114
+
115
+ <!-- Draft acknowledgement. Hidden until the first save. -->
116
+ <p class="wizard-draft-chip" id="wizard-draft-chip" role="status" aria-live="polite" hidden>
117
+ <i class="bi bi-check2-circle" aria-hidden="true"></i> Draft saved
118
+ </p>
119
+ </div>
120
+ </div>
67
121
 
68
- <!-- ============================================================ -->
69
- <!-- Step Panels -->
70
- <!-- ============================================================ -->
71
- <div class="tab-content" id="wizardTabContent">
122
+ <!-- ============================================================ -->
123
+ <!-- 2. Step panels -->
124
+ <!-- ============================================================ -->
125
+ <div class="col-lg-5">
126
+ <div class="tab-content wizard-panes" id="wizardTabContent">
72
127
 
73
128
  <!-- ───── Step 1: Identity ───── -->
74
- <div class="tab-pane fade show active" id="step-identity" role="tabpanel">
129
+ <div class="tab-pane fade show active" id="step-identity" role="tabpanel" aria-labelledby="tab-identity">
75
130
  <div class="card">
76
131
  <div class="card-body">
77
132
  <h4 class="card-title mb-3">Site Identity</h4>
@@ -79,7 +134,7 @@
79
134
  <div class="mb-3">
80
135
  <label for="cfg-title" class="form-label">Site Title</label>
81
136
  <input type="text" class="form-control cfg-field" id="cfg-title"
82
- placeholder="My Awesome Site" data-key="title">
137
+ placeholder="My Awesome Site" data-key="title" data-recommended="Site Title">
83
138
  </div>
84
139
 
85
140
  <div class="mb-3">
@@ -91,7 +146,7 @@
91
146
  <div class="mb-3">
92
147
  <label for="cfg-description" class="form-label">Description <small class="text-muted">(SEO, ≤ 160 chars)</small></label>
93
148
  <textarea class="form-control cfg-field" id="cfg-description" rows="2"
94
- maxlength="160" data-key="description"
149
+ maxlength="160" data-key="description" data-recommended="Description"
95
150
  placeholder="Brief description of your site for search engines"></textarea>
96
151
  <div class="form-text"><span id="desc-count">0</span>/160</div>
97
152
  </div>
@@ -100,29 +155,31 @@
100
155
  <div class="col-md-6 mb-3">
101
156
  <label for="cfg-author" class="form-label">Author / Owner</label>
102
157
  <input type="text" class="form-control cfg-field" id="cfg-author"
103
- placeholder="Your Name" data-key="founder">
158
+ placeholder="Your Name" data-key="founder" data-recommended="Author / Owner">
104
159
  </div>
105
160
  <div class="col-md-6 mb-3">
106
161
  <label for="cfg-email" class="form-label">Email</label>
107
162
  <input type="email" class="form-control cfg-field" id="cfg-email"
108
- placeholder="you@example.com" data-key="email">
163
+ placeholder="you@example.com" data-key="email" data-recommended="Email"
164
+ aria-describedby="cfg-email-feedback">
165
+ <div class="invalid-feedback" id="cfg-email-feedback"></div>
109
166
  </div>
110
167
  </div>
111
168
 
112
169
  <div class="mb-3">
113
170
  <label for="cfg-github-user" class="form-label">GitHub Username</label>
114
171
  <input type="text" class="form-control cfg-field" id="cfg-github-user"
115
- placeholder="your-username" data-key="github_user">
172
+ placeholder="your-username" data-key="github_user" data-recommended="GitHub Username">
116
173
  </div>
117
174
 
118
175
  <div class="mb-3">
119
176
  <label for="cfg-repo-name" class="form-label">Repository Name</label>
120
177
  <input type="text" class="form-control cfg-field" id="cfg-repo-name"
121
- placeholder="my-site" data-key="repository_name">
178
+ placeholder="my-site" data-key="repository_name" data-recommended="Repository Name">
122
179
  <div class="form-text">The GitHub repository name for this site (e.g. <code>my-site</code>).</div>
123
180
  </div>
124
181
 
125
- <div class="text-end">
182
+ <div class="wizard-nav text-end">
126
183
  <button class="btn btn-primary btn-next" data-next="tab-urls">
127
184
  Next: URLs <i class="bi bi-arrow-right"></i>
128
185
  </button>
@@ -132,7 +189,7 @@
132
189
  </div>
133
190
 
134
191
  <!-- ───── Step 2: URLs ───── -->
135
- <div class="tab-pane fade" id="step-urls" role="tabpanel">
192
+ <div class="tab-pane fade" id="step-urls" role="tabpanel" aria-labelledby="tab-urls">
136
193
  <div class="card">
137
194
  <div class="card-body">
138
195
  <h4 class="card-title mb-3">URLs &amp; Deployment</h4>
@@ -140,7 +197,9 @@
140
197
  <div class="mb-3">
141
198
  <label for="cfg-url" class="form-label">Site URL</label>
142
199
  <input type="url" class="form-control cfg-field" id="cfg-url"
143
- placeholder="https://yourdomain.com" data-key="url">
200
+ placeholder="https://yourdomain.com" data-key="url" data-recommended="Site URL"
201
+ aria-describedby="cfg-url-feedback">
202
+ <div class="invalid-feedback" id="cfg-url-feedback"></div>
144
203
  <div class="form-text">Your production URL. For GitHub Pages: <code>https://USERNAME.github.io</code></div>
145
204
  </div>
146
205
 
@@ -168,7 +227,7 @@
168
227
  </select>
169
228
  </div>
170
229
 
171
- <div class="d-flex justify-content-between">
230
+ <div class="wizard-nav d-flex justify-content-between">
172
231
  <button class="btn btn-outline-secondary btn-prev" data-prev="tab-identity">
173
232
  <i class="bi bi-arrow-left"></i> Back
174
233
  </button>
@@ -181,7 +240,7 @@
181
240
  </div>
182
241
 
183
242
  <!-- ───── Step 3: Collections ───── -->
184
- <div class="tab-pane fade" id="step-collections" role="tabpanel">
243
+ <div class="tab-pane fade" id="step-collections" role="tabpanel" aria-labelledby="tab-collections">
185
244
  <div class="card">
186
245
  <div class="card-body">
187
246
  <h4 class="card-title mb-3">Content Collections</h4>
@@ -222,7 +281,7 @@
222
281
  </label>
223
282
  </div>
224
283
 
225
- <div class="d-flex justify-content-between">
284
+ <div class="wizard-nav d-flex justify-content-between">
226
285
  <button class="btn btn-outline-secondary btn-prev" data-prev="tab-urls">
227
286
  <i class="bi bi-arrow-left"></i> Back
228
287
  </button>
@@ -235,7 +294,7 @@
235
294
  </div>
236
295
 
237
296
  <!-- ───── Step 4: Analytics ───── -->
238
- <div class="tab-pane fade" id="step-analytics" role="tabpanel">
297
+ <div class="tab-pane fade" id="step-analytics" role="tabpanel" aria-labelledby="tab-analytics">
239
298
  <div class="card">
240
299
  <div class="card-body">
241
300
  <h4 class="card-title mb-3">Analytics &amp; Integrations</h4>
@@ -253,7 +312,8 @@
253
312
  <label class="form-check-label" for="cfg-posthog-enabled">Enable PostHog</label>
254
313
  </div>
255
314
  <input type="text" class="form-control cfg-field" id="cfg-posthog-key"
256
- placeholder="phc_your_key_here" data-key="posthog.api_key">
315
+ placeholder="phc_your_key_here" data-key="posthog.api_key"
316
+ aria-label="PostHog API key">
257
317
  </div>
258
318
 
259
319
  <div class="mb-3">
@@ -262,19 +322,21 @@
262
322
  <div class="col-md-6">
263
323
  <div class="input-group">
264
324
  <span class="input-group-text"><i class="bi bi-twitter-x"></i></span>
265
- <input type="text" class="form-control cfg-field" placeholder="username" data-key="twitter_username">
325
+ <input type="text" class="form-control cfg-field" id="cfg-twitter"
326
+ placeholder="username" data-key="twitter_username" aria-label="X / Twitter username">
266
327
  </div>
267
328
  </div>
268
329
  <div class="col-md-6">
269
330
  <div class="input-group">
270
331
  <span class="input-group-text"><i class="bi bi-linkedin"></i></span>
271
- <input type="text" class="form-control cfg-field" placeholder="username" data-key="linkedin_username">
332
+ <input type="text" class="form-control cfg-field" id="cfg-linkedin"
333
+ placeholder="username" data-key="linkedin_username" aria-label="LinkedIn username">
272
334
  </div>
273
335
  </div>
274
336
  </div>
275
337
  </div>
276
338
 
277
- <div class="d-flex justify-content-between">
339
+ <div class="wizard-nav d-flex justify-content-between">
278
340
  <button class="btn btn-outline-secondary btn-prev" data-prev="tab-collections">
279
341
  <i class="bi bi-arrow-left"></i> Back
280
342
  </button>
@@ -286,38 +348,30 @@
286
348
  </div>
287
349
  </div>
288
350
 
289
- <!-- ───── Step 5: Review & Download ───── -->
290
- <div class="tab-pane fade" id="step-review" role="tabpanel">
351
+ <!-- ───── Step 5: Review ───── -->
352
+ <!-- The YAML itself now lives in the persistent preview panel, so this
353
+ step is the summary: what is still unfilled, and what to do next. -->
354
+ <div class="tab-pane fade" id="step-review" role="tabpanel" aria-labelledby="tab-review">
291
355
  <div class="card">
292
356
  <div class="card-body">
293
357
  <h4 class="card-title mb-3">Review Your Configuration</h4>
294
- <p class="text-muted">This is the <code>_config.yml</code> that will be generated. Review it, then download or copy.</p>
358
+ <p class="text-muted">
359
+ The generated <code>_config.yml</code> is in the preview panel — it has been
360
+ live since step one. Check the notes below, then copy or download it.
361
+ </p>
295
362
 
296
- <div class="position-relative">
297
- <pre class="bg-dark text-light p-3 rounded" style="max-height:450px;overflow:auto"><code id="yaml-preview">Loading…</code></pre>
298
- <button class="btn btn-sm btn-outline-light position-absolute top-0 end-0 m-2" id="btn-copy"
299
- title="Copy to clipboard">
300
- <i class="bi bi-clipboard"></i>
301
- </button>
363
+ <!-- Populated by setup-wizard.js on entering this step. -->
364
+ <div id="wizard-review-warnings" role="status" aria-live="polite"></div>
365
+
366
+ <div class="alert alert-info mt-3" role="alert">
367
+ <i class="bi bi-info-circle"></i>
368
+ <strong>Next step:</strong> Replace the <code>_config.yml</code> in your project root with the downloaded file, then restart your dev server.
302
369
  </div>
303
370
 
304
- <div class="d-flex justify-content-between mt-3">
371
+ <div class="wizard-nav d-flex justify-content-between">
305
372
  <button class="btn btn-outline-secondary btn-prev" data-prev="tab-analytics">
306
373
  <i class="bi bi-arrow-left"></i> Back
307
374
  </button>
308
- <div>
309
- <button class="btn btn-outline-primary me-2" id="btn-copy-full">
310
- <i class="bi bi-clipboard"></i> Copy YAML
311
- </button>
312
- <button class="btn btn-primary" id="btn-download">
313
- <i class="bi bi-download"></i> Download _config.yml
314
- </button>
315
- </div>
316
- </div>
317
-
318
- <div class="alert alert-info mt-4" role="alert">
319
- <i class="bi bi-info-circle"></i>
320
- <strong>Next step:</strong> Replace the <code>_config.yml</code> in your project root with the downloaded file, then restart your dev server.
321
375
  </div>
322
376
  </div>
323
377
  </div>
@@ -325,7 +379,40 @@
325
379
 
326
380
  </div><!-- /tab-content -->
327
381
  </div>
328
- </div>
382
+
383
+ <!-- ============================================================ -->
384
+ <!-- 3. Persistent live YAML preview -->
385
+ <!-- ============================================================ -->
386
+ <div class="col-lg-4">
387
+ <aside class="wizard-preview sticky-lg-top" aria-labelledby="wizard-preview-heading">
388
+ <div class="d-flex align-items-center justify-content-between mb-2">
389
+ <h2 class="h6 mb-0" id="wizard-preview-heading">
390
+ <i class="bi bi-file-earmark-code" aria-hidden="true"></i> <code>_config.yml</code>
391
+ </h2>
392
+ <span class="badge text-bg-secondary wizard-preview-live">live</span>
393
+ </div>
394
+
395
+ <div class="position-relative">
396
+ <pre class="wizard-preview-code bg-dark text-light p-3 rounded"><code id="yaml-preview">Loading…</code></pre>
397
+ <button class="btn btn-sm btn-outline-light position-absolute top-0 end-0 m-2" id="btn-copy"
398
+ type="button" title="Copy to clipboard" aria-label="Copy configuration to clipboard">
399
+ <i class="bi bi-clipboard" aria-hidden="true"></i>
400
+ </button>
401
+ </div>
402
+
403
+ <!-- Always enabled, at every step — the file is valid from the start. -->
404
+ <div class="d-grid gap-2 d-sm-flex mt-3">
405
+ <button class="btn btn-outline-primary flex-sm-fill" id="btn-copy-full" type="button">
406
+ <i class="bi bi-clipboard" aria-hidden="true"></i> Copy YAML
407
+ </button>
408
+ <button class="btn btn-primary flex-sm-fill" id="btn-download" type="button">
409
+ <i class="bi bi-download" aria-hidden="true"></i> Download
410
+ </button>
411
+ </div>
412
+ </aside>
413
+ </div>
414
+
415
+ </div><!-- /row -->
329
416
  </div>
330
417
 
331
418
  <script src="{{ '/assets/js/setup-wizard.js' | relative_url }}"></script>
@@ -187,9 +187,9 @@
187
187
  <div class="modal-dialog">
188
188
  <div class="modal-content">
189
189
  <div class="modal-header bg-body border-bottom">
190
- <h5 class="modal-title" id="helpModalLabel">
190
+ <h2 class="modal-title h5" id="helpModalLabel">
191
191
  <i class="bi bi-question-circle"></i> Statistics Help
192
- </h5>
192
+ </h2>
193
193
  <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
194
194
  </div>
195
195
  <div class="modal-body">
data/_layouts/README.md CHANGED
@@ -18,6 +18,8 @@ root.html (base template)
18
18
  ├── landing.html (landing pages)
19
19
  ├── book.html (book landing page - cover hero + table of contents)
20
20
  ├── book-story.html (immersive picture-book story pages)
21
+ ├── cookbook.html (cookbook landing page - cover + recipe index by course)
22
+ ├── recipe.html (one recipe - fact bar, scaler, ingredients, method, ratio)
21
23
  ├── stats.html (statistics dashboard)
22
24
  └── index.html (search pages)
23
25
  ```
data/_layouts/admin.html CHANGED
@@ -135,9 +135,9 @@ layout: root
135
135
  <div class="offcanvas offcanvas-start d-lg-none" tabindex="-1"
136
136
  id="adminSidebar" aria-labelledby="adminSidebarLabel">
137
137
  <div class="offcanvas-header">
138
- <h5 class="offcanvas-title" id="adminSidebarLabel">
138
+ <h2 class="offcanvas-title h5" id="adminSidebarLabel">
139
139
  <i class="bi bi-sliders me-1"></i> Administration
140
- </h5>
140
+ </h2>
141
141
  <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
142
142
  </div>
143
143
  <div class="offcanvas-body">
@@ -153,6 +153,11 @@ layout: default
153
153
  {{ minutes }} min read
154
154
  </span>
155
155
 
156
+ <!-- Page Views -->
157
+ {% comment %} The badge ships hidden and carries its own leading separator,
158
+ so a page with no count yet leaves no dangling "•" in the meta row. {% endcomment %}
159
+ {% include components/page-views.html separator=true %}
160
+
156
161
  <!-- Last Modified -->
157
162
  {% if page.lastmod %}
158
163
  <span class="mx-2">•</span>
@@ -0,0 +1,88 @@
1
+ ---
2
+ # Feature: ZER0-084
3
+ layout: default
4
+ ---
5
+ {%- comment -%}
6
+ Layout: cookbook.html
7
+ Extends: default.html
8
+ Purpose: Landing page for one cookbook — cover, introduction, a jump-nav of
9
+ its courses, and the full recipe index grouped by course.
10
+ Front matter required: layout, title, cookbook (slug shared with its recipes)
11
+ Front matter optional: subtitle, description, image/preview, author,
12
+ grouped (false for one flat grid)
13
+ Notes: Recipes belong to a cookbook by matching its `cookbook:` slug. Omit
14
+ `cookbook:` here and the page indexes EVERY recipe on the site, which
15
+ is what a single-cookbook site wants.
16
+ {%- endcomment -%}
17
+
18
+ {%- assign _cb_slug = page.cookbook -%}
19
+ {%- if _cb_slug -%}
20
+ {%- assign _cb_recipes = site.recipes | where_exp: "doc", "doc.layout != 'cookbook'" | where: "cookbook", _cb_slug -%}
21
+ {%- else -%}
22
+ {%- assign _cb_recipes = site.recipes | where_exp: "doc", "doc.layout != 'cookbook'" -%}
23
+ {%- endif -%}
24
+ {%- assign _cb_courses = _cb_recipes | map: "course" | compact | uniq -%}
25
+ {%- assign _cb_image = page.image | default: page.preview -%}
26
+
27
+ <div class="cookbook">
28
+ <!-- ================================ -->
29
+ <!-- COVER -->
30
+ <!-- ================================ -->
31
+ <header class="cookbook-header">
32
+ <div class="row g-4 align-items-center">
33
+ {%- if _cb_image -%}
34
+ <div class="col-8 col-sm-5 col-md-4 col-lg-3">
35
+ <div class="cookbook-cover">
36
+ {%- include components/preview-image.html src=_cb_image alt=page.title class="" loading="eager" -%}
37
+ </div>
38
+ </div>
39
+ {%- endif -%}
40
+ <div class="col">
41
+ <h1 id="page-title" class="cookbook-title">{{ page.title }}</h1>
42
+ {%- if page.subtitle -%}
43
+ <p class="lead mb-2">{{ page.subtitle }}</p>
44
+ {%- endif -%}
45
+ {%- if page.author -%}
46
+ <p class="text-body-secondary small mb-2">By {{ page.author }}</p>
47
+ {%- endif -%}
48
+ <p class="text-body-secondary mb-3">
49
+ <i class="bi bi-egg-fried me-1" aria-hidden="true"></i>{{ _cb_recipes.size }} {% if _cb_recipes.size == 1 %}recipe{% else %}recipes{% endif %}
50
+ {%- if _cb_courses.size > 0 %} <span class="mx-1" aria-hidden="true">•</span> {{ _cb_courses.size }} {% if _cb_courses.size == 1 %}course{% else %}courses{% endif %}{% endif -%}
51
+ </p>
52
+ {%- if page.description -%}
53
+ <p class="cookbook-description">{{ page.description }}</p>
54
+ {%- endif -%}
55
+ </div>
56
+ </div>
57
+ </header>
58
+
59
+ <!-- ================================ -->
60
+ <!-- ABOUT (the markdown body) -->
61
+ <!-- ================================ -->
62
+ {%- assign _cb_body = content | strip -%}
63
+ {%- if _cb_body != "" -%}
64
+ <section class="cookbook-about">
65
+ {{ content }}
66
+ </section>
67
+ {%- endif -%}
68
+
69
+ <!-- ================================ -->
70
+ <!-- COURSE JUMP NAV -->
71
+ <!-- ================================ -->
72
+ {%- if _cb_courses.size > 1 and page.grouped != false -%}
73
+ <nav class="cookbook-jump" aria-label="Jump to a course">
74
+ {%- for _cb_pair in site.data.recipe_courses -%}
75
+ {%- if _cb_courses contains _cb_pair[0] -%}
76
+ <a class="btn btn-sm btn-outline-secondary" href="#course-{{ _cb_pair[0] }}">
77
+ <i class="bi {{ _cb_pair[1].icon | default: 'bi-journal-bookmark' }} me-1" aria-hidden="true"></i>{{ _cb_pair[1].title | default: _cb_pair[0] | escape }}
78
+ </a>
79
+ {%- endif -%}
80
+ {%- endfor -%}
81
+ </nav>
82
+ {%- endif -%}
83
+
84
+ <!-- ================================ -->
85
+ <!-- RECIPE INDEX -->
86
+ <!-- ================================ -->
87
+ {%- include components/recipe-index.html cookbook=_cb_slug grouped=page.grouped heading_level="h2" -%}
88
+ </div>
@@ -93,12 +93,12 @@ layout: root
93
93
 
94
94
  {%- comment -%}
95
95
  Page introduction: title, breadcrumbs, metadata.
96
- Skipped on layouts that render their own page header (article, note)
97
- to avoid duplicate H1s. Authors can also opt-out per page via
98
- `hide_intro: true` in front matter.
96
+ Skipped on layouts that render their own page header (article, note,
97
+ recipe, cookbook) to avoid duplicate H1s. Authors can also opt-out
98
+ per page via `hide_intro: true` in front matter.
99
99
  {%- endcomment -%}
100
100
  {% assign _skip_intro = false %}
101
- {% if page.layout == "article" or page.layout == "note" or page.hide_intro %}
101
+ {% if page.layout == "article" or page.layout == "note" or page.layout == "recipe" or page.layout == "cookbook" or page.hide_intro %}
102
102
  {% assign _skip_intro = true %}
103
103
  {% endif %}
104
104
  {% unless _skip_intro %}