jekyll-theme-zer0 1.27.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +812 -13
- data/_data/backlog.yml +356 -2
- data/_data/consumers.yml +252 -0
- data/_data/features.yml +234 -5
- data/_data/i18n/fr.yml +103 -0
- data/_data/i18n/manifest.yml +1529 -0
- data/_data/ingredient_densities.yml +122 -0
- data/_data/navigation/docs.yml +2 -0
- data/_data/navigation/main.yml +22 -0
- data/_data/recipe_courses.yml +64 -0
- data/_data/series.yml +19 -0
- data/_data/theme-manifest.yml +387 -277
- data/_data/ui-text.yml +8 -0
- data/_includes/README.md +36 -3
- data/_includes/analytics/google-tag-manager-body.html +11 -2
- data/_includes/analytics/google-tag-manager-head.html +16 -6
- data/_includes/analytics/posthog.html +33 -4
- data/_includes/components/abc-letter.html +43 -0
- data/_includes/components/background-customizer.html +2 -2
- data/_includes/components/book-card.html +42 -0
- data/_includes/components/book-nav.html +80 -0
- data/_includes/components/book-plate.html +31 -0
- data/_includes/components/book-toc.html +47 -0
- data/_includes/components/bookshelf.html +68 -0
- data/_includes/components/card-grid.html +60 -0
- data/_includes/components/cookie-consent.html +4 -4
- data/_includes/components/data-card.html +95 -0
- data/_includes/components/halfmoon.html +5 -1
- data/_includes/components/info-section.html +8 -3
- data/_includes/components/language-toggle.html +168 -21
- data/_includes/components/page-feedback.html +65 -2
- data/_includes/components/page-views-init.html +55 -0
- data/_includes/components/page-views.html +33 -0
- data/_includes/components/recipe-card.html +67 -0
- data/_includes/components/recipe-duration.html +50 -0
- data/_includes/components/recipe-grams.html +58 -0
- data/_includes/components/recipe-index.html +96 -0
- data/_includes/components/recipe-ingredients.html +90 -0
- data/_includes/components/recipe-meta.html +96 -0
- data/_includes/components/recipe-nutrition.html +57 -0
- data/_includes/components/recipe-qty.html +73 -0
- data/_includes/components/recipe-ratio.html +151 -0
- data/_includes/components/recipe-scaler.html +73 -0
- data/_includes/components/recipe-steps.html +86 -0
- data/_includes/components/recipe-temp.html +45 -0
- data/_includes/components/search-modal.html +2 -2
- data/_includes/components/shortcuts-modal.html +3 -0
- data/_includes/components/theme-controls-bar.html +10 -2
- data/_includes/components/theme-customizer.html +8 -2
- data/_includes/components/theme-info.html +8 -1
- data/_includes/content/jsonld-software.html +22 -3
- data/_includes/content/seo.html +9 -3
- data/_includes/core/color-mode-init.html +13 -4
- data/_includes/core/favicon.html +46 -0
- data/_includes/core/footer.html +18 -16
- data/_includes/core/head.html +21 -0
- data/_includes/core/header.html +7 -4
- data/_includes/custom/body-end.html +18 -0
- data/_includes/custom/body-start.html +17 -0
- data/_includes/custom/footer.html +18 -0
- data/_includes/custom/head.html +18 -0
- data/_includes/navigation/local-graph.html +28 -2
- data/_includes/navigation/nav-tree.html +3 -3
- data/_includes/navigation/section-sidebar.html +93 -11
- data/_includes/navigation/sidebar-config.html +21 -0
- data/_includes/navigation/sidebar-left.html +2 -1
- data/_includes/navigation/sidebar-nav.html +4 -0
- data/_includes/navigation/sidebar-pagetree.html +150 -0
- data/_includes/navigation/sidebar-right.html +2 -1
- data/_includes/navigation/unified-drawer.html +8 -2
- data/_includes/obsidian/full-graph.html +165 -136
- data/_includes/setup/wizard.html +173 -86
- data/_includes/stats/stats-metrics.html +2 -2
- data/_layouts/404.html +260 -0
- data/_layouts/README.md +4 -0
- data/_layouts/admin.html +2 -2
- data/_layouts/article.html +5 -0
- data/_layouts/book-abc.html +106 -0
- data/_layouts/book-story.html +91 -0
- data/_layouts/book.html +113 -0
- data/_layouts/collection.html +17 -5
- data/_layouts/cookbook.html +88 -0
- data/_layouts/default.html +4 -4
- data/_layouts/home.html +5 -2
- data/_layouts/landing.html +9 -0
- data/_layouts/news.html +155 -31
- data/_layouts/recipe.html +274 -0
- data/_layouts/root.html +38 -5
- data/_layouts/section.html +96 -25
- data/_sass/components/_book.scss +423 -0
- data/_sass/components/_callout.scss +1 -1
- data/_sass/components/_footer.scss +37 -1
- data/_sass/components/_page-views.scss +36 -0
- data/_sass/components/_recipe.scss +506 -0
- data/_sass/components/_setup-wizard.scss +235 -0
- data/_sass/components/_ui-enhancements.scss +6 -6
- data/_sass/core/_navbar.scss +263 -15
- data/_sass/core/_obsidian.scss +286 -6
- data/_sass/layouts/_landing.scss +2 -2
- data/_sass/theme/_backgrounds.scss +21 -8
- data/_sass/tokens/_color.scss +6 -0
- data/_sass/tokens/_index.scss +2 -0
- data/_sass/tokens/_radius.scss +21 -0
- data/_sass/tokens/_typography.scss +4 -0
- data/_sass/utilities/_focus.scss +14 -0
- data/assets/css/main.scss +4 -0
- data/assets/js/auto-hide-nav.js +5 -1
- data/assets/js/halfmoon.js +26 -0
- data/assets/js/modules/navigation/navbar.js +55 -0
- data/assets/js/obsidian-graph.js +702 -264
- data/assets/js/obsidian-local-graph.js +161 -54
- data/assets/js/page-views.js +372 -0
- data/assets/js/recipe-scaler.js +501 -0
- data/assets/js/search-modal.js +14 -1
- data/assets/js/setup-wizard.js +322 -20
- data/scripts/README.md +29 -0
- data/scripts/bin/audit-consumer +39 -7
- data/scripts/bin/giscus-discussions +213 -14
- data/scripts/bin/manifest +66 -16
- data/scripts/bin/validate +5 -1
- data/scripts/ci/agent_review_result.py +164 -0
- data/scripts/ci/test_agent_review_result.py +172 -0
- data/scripts/design-system-check.rb +170 -0
- data/scripts/install/README.md +47 -6
- data/scripts/install/ai/client.sh +302 -93
- data/scripts/install/ai/prompts/spec.schema.json +1 -1
- data/scripts/install/ai/wizard.sh +10 -5
- data/scripts/install/apply.sh +7 -3
- data/scripts/install/cli.sh +54 -4
- data/scripts/install/config.sh +167 -0
- data/scripts/install/doctor.sh +38 -0
- data/scripts/install/plan.sh +10 -0
- data/scripts/install/spec.sh +15 -7
- data/scripts/install/template.sh +4 -0
- data/scripts/lib/audit.sh +42 -2
- data/scripts/lint-liquid-raw.rb +137 -0
- data/scripts/propagate.rb +277 -0
- data/scripts/test/lib/run_tests.sh +2 -1
- data/scripts/test/lib/test_agent_review_result.sh +27 -0
- data/scripts/translate.rb +161 -15
- metadata +56 -2
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
===================================================================
|
|
3
|
+
DATA-CARD — one generic, data-driven Bootstrap card
|
|
4
|
+
===================================================================
|
|
5
|
+
|
|
6
|
+
Path: _includes/components/data-card.html
|
|
7
|
+
Purpose: Render ONE card from a plain data hash so hub dashboards,
|
|
8
|
+
fleet registries, service grids, and link boards can be
|
|
9
|
+
data-driven instead of hand-written Bootstrap markup.
|
|
10
|
+
Usually invoked via components/card-grid.html.
|
|
11
|
+
|
|
12
|
+
Parameters:
|
|
13
|
+
item (hash, required) — the card data (see schema below)
|
|
14
|
+
heading_level (string) — heading tag for the title, default "h3"
|
|
15
|
+
heading_class (string) — classes for the heading, default "h5"
|
|
16
|
+
card_class (string) — extra classes on the .card element
|
|
17
|
+
|
|
18
|
+
Item schema (every key optional except title):
|
|
19
|
+
title card heading text
|
|
20
|
+
url makes the title a link
|
|
21
|
+
external true → the title link opens in a new tab
|
|
22
|
+
description body copy (plain text or inline HTML)
|
|
23
|
+
icon Bootstrap Icons class shown before the title (e.g. "bi-robot")
|
|
24
|
+
image image path/URL rendered via components/preview-image.html
|
|
25
|
+
image_alt alt text for the image (defaults to title)
|
|
26
|
+
badge short status label rendered next to the title
|
|
27
|
+
badge_variant Bootstrap color suffix for the badge (default "secondary")
|
|
28
|
+
badge_title tooltip for the badge
|
|
29
|
+
meta array of { icon, text } pills rendered under the body
|
|
30
|
+
buttons array of { label, url, icon, variant, external } footer actions
|
|
31
|
+
footer small muted footer text (rendered when no buttons)
|
|
32
|
+
|
|
33
|
+
Example (_data/projects.yml + a page):
|
|
34
|
+
{% for p in site.data.projects %}
|
|
35
|
+
{% include components/data-card.html item=p %}
|
|
36
|
+
{% endfor %}
|
|
37
|
+
===================================================================
|
|
38
|
+
{%- endcomment -%}
|
|
39
|
+
{%- assign _dc = include.item -%}
|
|
40
|
+
{%- assign _dc_heading = include.heading_level | default: "h3" -%}
|
|
41
|
+
{%- assign _dc_heading_class = include.heading_class | default: "h5" -%}
|
|
42
|
+
<div class="card h-100 zer0-data-card {{ include.card_class }}">
|
|
43
|
+
{%- if _dc.image -%}
|
|
44
|
+
{%- assign _dc_img_alt = _dc.image_alt | default: _dc.title -%}
|
|
45
|
+
{%- if _dc.url -%}<a href="{{ _dc.url | relative_url }}" tabindex="-1" aria-hidden="true">{%- endif -%}
|
|
46
|
+
{%- include components/preview-image.html src=_dc.image alt=_dc_img_alt class="card-img-top" style="height: 160px; object-fit: cover;" -%}
|
|
47
|
+
{%- if _dc.url -%}</a>{%- endif -%}
|
|
48
|
+
{%- endif -%}
|
|
49
|
+
<div class="card-body d-flex flex-column">
|
|
50
|
+
<{{ _dc_heading }} class="card-title {{ _dc_heading_class }} mb-2 d-flex align-items-center">
|
|
51
|
+
{%- if _dc.icon %}<i class="bi {{ _dc.icon }} me-2" aria-hidden="true"></i>{% endif -%}
|
|
52
|
+
<span class="flex-grow-1">
|
|
53
|
+
{%- if _dc.url -%}
|
|
54
|
+
<a href="{{ _dc.url | relative_url }}" class="text-decoration-none text-body-emphasis"
|
|
55
|
+
{%- if _dc.external %} target="_blank" rel="noopener"{% endif %}>{{ _dc.title }}</a>
|
|
56
|
+
{%- else -%}
|
|
57
|
+
{{ _dc.title }}
|
|
58
|
+
{%- endif -%}
|
|
59
|
+
</span>
|
|
60
|
+
{%- if _dc.badge %}
|
|
61
|
+
<span class="badge text-bg-{{ _dc.badge_variant | default: 'secondary' }} ms-2"
|
|
62
|
+
{%- if _dc.badge_title %} title="{{ _dc.badge_title | escape }}"{% endif %}>{{ _dc.badge }}</span>
|
|
63
|
+
{%- endif %}
|
|
64
|
+
</{{ _dc_heading }}>
|
|
65
|
+
{%- if _dc.description %}
|
|
66
|
+
<p class="card-text text-body-secondary mb-2">{{ _dc.description }}</p>
|
|
67
|
+
{%- endif %}
|
|
68
|
+
{%- if _dc.meta and _dc.meta.size > 0 %}
|
|
69
|
+
<div class="d-flex flex-wrap gap-2 mt-auto pt-2">
|
|
70
|
+
{%- for m in _dc.meta %}
|
|
71
|
+
<span class="badge rounded-pill text-bg-light border text-body-secondary fw-normal">
|
|
72
|
+
{%- if m.icon %}<i class="bi {{ m.icon }} me-1" aria-hidden="true"></i>{% endif -%}
|
|
73
|
+
{{ m.text }}
|
|
74
|
+
</span>
|
|
75
|
+
{%- endfor %}
|
|
76
|
+
</div>
|
|
77
|
+
{%- endif %}
|
|
78
|
+
</div>
|
|
79
|
+
{%- if _dc.buttons and _dc.buttons.size > 0 %}
|
|
80
|
+
<div class="card-footer bg-transparent d-flex flex-wrap gap-2">
|
|
81
|
+
{%- for b in _dc.buttons %}
|
|
82
|
+
<a class="btn btn-sm btn-{{ b.variant | default: 'outline-primary' }}"
|
|
83
|
+
href="{{ b.url | relative_url }}"
|
|
84
|
+
{%- if b.external %} target="_blank" rel="noopener"{% endif %}>
|
|
85
|
+
{%- if b.icon %}<i class="bi {{ b.icon }} me-1" aria-hidden="true"></i>{% endif -%}
|
|
86
|
+
{{ b.label }}
|
|
87
|
+
</a>
|
|
88
|
+
{%- endfor %}
|
|
89
|
+
</div>
|
|
90
|
+
{%- elsif _dc.footer %}
|
|
91
|
+
<div class="card-footer bg-transparent">
|
|
92
|
+
<small class="text-body-secondary">{{ _dc.footer }}</small>
|
|
93
|
+
</div>
|
|
94
|
+
{%- endif %}
|
|
95
|
+
</div>
|
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
page (settings offcanvas + theme-controls-bar).
|
|
10
10
|
params: class (string, optional, default "") — extra btn-group classes,
|
|
11
11
|
e.g. "w-100" to fill the parent.
|
|
12
|
+
config: site.color_mode_lock: true removes the control entirely — the
|
|
13
|
+
site pins one mode and visitor switching is disabled
|
|
14
|
+
(halfmoon.js + core/color-mode-init.html enforce the same).
|
|
12
15
|
-->
|
|
13
|
-
|
|
16
|
+
{% unless site.color_mode_lock %}
|
|
14
17
|
<div class="btn-group bd-theme-switch {{ include.class | default: '' }}" role="group" aria-label="Color mode">
|
|
15
18
|
<button type="button" class="btn btn-outline-secondary btn-sm d-flex align-items-center justify-content-center gap-1" data-bs-theme-value="light" aria-pressed="false">
|
|
16
19
|
<svg class="bi flex-shrink-0" width="1em" height="1em" fill="currentColor" aria-hidden="true"><use href="#sun-fill"></use></svg>
|
|
@@ -25,3 +28,4 @@
|
|
|
25
28
|
<span>Auto</span>
|
|
26
29
|
</button>
|
|
27
30
|
</div>
|
|
31
|
+
{% endunless %}
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
File: info-section.html
|
|
8
8
|
Path: _includes/components/info-section.html
|
|
9
9
|
Purpose: Unified settings offcanvas panel with three tabs —
|
|
10
|
-
Appearance (color mode, skin, backgrounds, primary color),
|
|
10
|
+
Appearance (language, color mode, skin, backgrounds, primary color),
|
|
11
11
|
Site (environment, theme & build info, admin links) and
|
|
12
12
|
Developer (page location, metadata, source shortcuts).
|
|
13
13
|
|
|
14
14
|
Dependencies:
|
|
15
15
|
- Bootstrap 5 Offcanvas, Tabs
|
|
16
16
|
- _includes/components/env-detect.html (sets is_production/build_env)
|
|
17
|
+
- _includes/components/language-toggle.html (panel variant — Language)
|
|
17
18
|
- _includes/components/halfmoon.html (color-mode segmented control)
|
|
18
19
|
- _includes/components/background-settings.html (skin + backgrounds)
|
|
19
20
|
- _includes/components/env-switcher.html
|
|
@@ -44,9 +45,9 @@
|
|
|
44
45
|
<!-- Offcanvas Header -->
|
|
45
46
|
<div class="offcanvas-header border-bottom pb-0 d-block">
|
|
46
47
|
<div class="d-flex align-items-center justify-content-between mb-2">
|
|
47
|
-
<
|
|
48
|
+
<h2 class="offcanvas-title h5" id="infoSectionLabel">
|
|
48
49
|
<i class="bi bi-gear-wide-connected me-2" aria-hidden="true"></i>Settings
|
|
49
|
-
</
|
|
50
|
+
</h2>
|
|
50
51
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
51
52
|
</div>
|
|
52
53
|
|
|
@@ -82,6 +83,10 @@
|
|
|
82
83
|
<!-- Appearance Tab — everything that changes how the site looks -->
|
|
83
84
|
<div class="tab-pane fade show active" id="appearance-pane" role="tabpanel" aria-labelledby="appearance-tab" tabindex="0">
|
|
84
85
|
|
|
86
|
+
<!-- Language — renders its own heading, and nothing at all when
|
|
87
|
+
site.translation is disabled. NOT cached: page-dependent. -->
|
|
88
|
+
{% include components/language-toggle.html variant="panel" %}
|
|
89
|
+
|
|
85
90
|
<!-- Color Mode -->
|
|
86
91
|
<div class="mb-4">
|
|
87
92
|
<h6 class="text-body-secondary small text-uppercase fw-semibold mb-2">
|
|
@@ -2,24 +2,61 @@
|
|
|
2
2
|
{%- comment -%}
|
|
3
3
|
Component: language-toggle
|
|
4
4
|
Path: _includes/components/language-toggle.html
|
|
5
|
-
Purpose:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
Purpose: Switches between the English original and its machine-generated
|
|
6
|
+
translations (scripts/translate.rb). Shows the source language
|
|
7
|
+
plus every configured target language. Every entry is a live
|
|
8
|
+
link: a target without a generated translation for the current
|
|
9
|
+
page falls back to the source page and is explained by one
|
|
10
|
+
footnote (it used to render as a disabled entry — see T-038).
|
|
11
|
+
Params: variant (string, optional, default "navbar")
|
|
12
|
+
- "navbar" — compact dropdown for the header utility cluster
|
|
13
|
+
- "panel" — self-contained "Language" section for the
|
|
14
|
+
Settings offcanvas (components/info-section.html),
|
|
15
|
+
rendered as an always-visible list of choices
|
|
16
|
+
with its own <h6> heading
|
|
17
|
+
Reads: page.lang / page.url / page.translation_source_url and the
|
|
18
|
+
generated _data/i18n/manifest.yml.
|
|
12
19
|
Depends on: site.translation.* config, site.data.i18n.manifest,
|
|
13
20
|
_data/i18n/languages.yml (display names), core/i18n.html,
|
|
14
|
-
Bootstrap 5 dropdown, Bootstrap Icons
|
|
21
|
+
Bootstrap 5 dropdown (navbar variant only), Bootstrap Icons
|
|
22
|
+
(bi-translate)
|
|
15
23
|
Notes: Renders nothing when translation is disabled or no target
|
|
16
24
|
languages are configured. The chosen language is remembered in
|
|
17
25
|
localStorage("zer0-lang") for future UX (no auto-redirect).
|
|
18
26
|
Must NOT be include_cached — the menu is page-dependent.
|
|
27
|
+
The theme ships the "panel" variant (the navbar is reserved for
|
|
28
|
+
main navigation); "navbar" stays for consumers that prefer the
|
|
29
|
+
header dropdown.
|
|
30
|
+
|
|
31
|
+
MENU CONTRACT (T-038 / #406) — shared by both variants:
|
|
32
|
+
* No DISABLED rows. A target language without a generated
|
|
33
|
+
translation is still a link; it goes to the source-language
|
|
34
|
+
page and still records the preference in zer0-lang, so the
|
|
35
|
+
menu never contains a dead end.
|
|
36
|
+
* The current language is marked with a check icon and a
|
|
37
|
+
subtle tint — NOT Bootstrap's `.active` primary fill, which
|
|
38
|
+
read as a selected nav item rather than "you are here".
|
|
39
|
+
* Machine-generated translations carry a small "auto" chip.
|
|
40
|
+
The source language never does (a human wrote it), and
|
|
41
|
+
neither do untranslated rows (nothing was generated).
|
|
42
|
+
* The per-row "(Not yet translated)" text is replaced by ONE
|
|
43
|
+
footnote under a divider, referenced from each untranslated
|
|
44
|
+
row via aria-describedby. Per-row text was the single
|
|
45
|
+
biggest contributor to menu width.
|
|
46
|
+
Trigger changes (icon-only, no caret) apply to the navbar
|
|
47
|
+
variant only — the panel has an <h6> heading, not a button.
|
|
48
|
+
|
|
49
|
+
New ui keys (lang_machine_translated, lang_machine_translated_title,
|
|
50
|
+
lang_untranslated_note) live in _data/ui-text.yml `en`. core/i18n.html
|
|
51
|
+
REPLACES the whole `ui` map on a translated page rather than merging
|
|
52
|
+
it, so until scripts/translate.rb regenerates _data/i18n/<lang>.yml
|
|
53
|
+
these keys are absent there — every use below therefore carries a
|
|
54
|
+
literal `| default:` fallback. Do not remove them.
|
|
19
55
|
{%- endcomment -%}
|
|
20
56
|
{%- assign _lt_langs = site.translation.languages -%}
|
|
21
57
|
{%- if site.translation.enabled and _lt_langs and _lt_langs.size > 0 -%}
|
|
22
58
|
{%- include core/i18n.html -%}
|
|
59
|
+
{%- assign _lt_variant = include.variant | default: 'navbar' -%}
|
|
23
60
|
{%- assign _lt_source = site.translation.source_lang | default: 'en' -%}
|
|
24
61
|
{%- assign _lt_current = page.lang | default: _lt_source -%}
|
|
25
62
|
{%- if _lt_current == _lt_source -%}
|
|
@@ -30,25 +67,117 @@
|
|
|
30
67
|
{%- assign _lt_entry = site.data.i18n.manifest.pages[_lt_src_url] -%}
|
|
31
68
|
{%- assign _lt_names = site.data.i18n.languages -%}
|
|
32
69
|
{%- assign _lt_source_name = _lt_names[_lt_source].native | default: _lt_source -%}
|
|
70
|
+
{%- comment -%}
|
|
71
|
+
Display name of the language being read, for the trigger's title/aria — the
|
|
72
|
+
icon-only button has no visible text, so the accessible name has to carry it.
|
|
73
|
+
{%- endcomment -%}
|
|
74
|
+
{%- if _lt_current == _lt_source -%}
|
|
75
|
+
{%- assign _lt_current_name = _lt_source_name -%}
|
|
76
|
+
{%- else -%}
|
|
77
|
+
{%- assign _lt_current_name = _lt_names[_lt_current].native | default: _lt_current -%}
|
|
78
|
+
{%- endif -%}
|
|
79
|
+
{%- comment -%}
|
|
80
|
+
Does ANY target language lack a translation of this page? Drives whether the
|
|
81
|
+
footnote + divider render at all — an all-translated menu shows neither.
|
|
82
|
+
A target we are currently reading is by definition translated.
|
|
83
|
+
{%- endcomment -%}
|
|
84
|
+
{%- assign _lt_untranslated = 0 -%}
|
|
85
|
+
{%- for _lt_l in _lt_langs -%}
|
|
86
|
+
{%- unless _lt_l == _lt_current or _lt_entry[_lt_l] -%}
|
|
87
|
+
{%- assign _lt_untranslated = _lt_untranslated | plus: 1 -%}
|
|
88
|
+
{%- endunless -%}
|
|
89
|
+
{%- endfor -%}
|
|
90
|
+
{%- assign _lt_note_id = 'zer0-lang-untranslated-note' -%}
|
|
91
|
+
{%- assign _lt_auto_label = ui.lang_machine_translated | default: 'auto' -%}
|
|
92
|
+
{%- assign _lt_auto_title = ui.lang_machine_translated_title | default: 'Machine-generated translation' -%}
|
|
93
|
+
{%- assign _lt_note_text = ui.lang_untranslated_note | default: 'Marked languages are not yet translated — those links open the original.' -%}
|
|
94
|
+
{%- assign _lt_fallback_url = _lt_src_url | default: '/' | relative_url -%}
|
|
95
|
+
{%- if _lt_variant == 'panel' -%}
|
|
96
|
+
<div class="mb-4 nav-lang-panel" id="zer0-lang-toggle">
|
|
97
|
+
<h6 class="text-body-secondary small text-uppercase fw-semibold mb-2">
|
|
98
|
+
<i class="bi bi-translate me-1" aria-hidden="true"></i>{{ ui.lang_toggle_label | default: "Language" }}
|
|
99
|
+
</h6>
|
|
100
|
+
<div class="list-group nav-lang-options" role="group" aria-label="{{ ui.lang_toggle_aria | default: 'Change language' }}">
|
|
101
|
+
{%- if _lt_current == _lt_source %}
|
|
102
|
+
<span class="list-group-item nav-lang-item is-current" aria-current="true">
|
|
103
|
+
<span class="nav-lang-code">{{ _lt_source | slice: 0, 2 | upcase }}</span>
|
|
104
|
+
<span class="nav-lang-name">{{ _lt_source_name }}</span>
|
|
105
|
+
<i class="bi bi-check2 nav-lang-check" aria-hidden="true"></i>
|
|
106
|
+
</span>
|
|
107
|
+
{%- else %}
|
|
108
|
+
<a class="list-group-item list-group-item-action nav-lang-item" data-lang="{{ _lt_source }}" hreflang="{{ _lt_source }}" href="{{ _lt_fallback_url }}">
|
|
109
|
+
<span class="nav-lang-code">{{ _lt_source | slice: 0, 2 | upcase }}</span>
|
|
110
|
+
<span class="nav-lang-name">{{ _lt_source_name }}</span>
|
|
111
|
+
</a>
|
|
112
|
+
{%- endif %}
|
|
113
|
+
{%- for _lt_lang in _lt_langs -%}
|
|
114
|
+
{%- assign _lt_name = _lt_names[_lt_lang].native | default: _lt_lang -%}
|
|
115
|
+
{%- assign _lt_t = _lt_entry[_lt_lang] %}
|
|
116
|
+
{%- if _lt_current == _lt_lang %}
|
|
117
|
+
<span class="list-group-item nav-lang-item is-current" aria-current="true">
|
|
118
|
+
<span class="nav-lang-code">{{ _lt_lang | slice: 0, 2 | upcase }}</span>
|
|
119
|
+
<span class="nav-lang-name">{{ _lt_name }}</span>
|
|
120
|
+
<span class="nav-lang-auto" title="{{ _lt_auto_title }}">{{ _lt_auto_label }}</span>
|
|
121
|
+
<i class="bi bi-check2 nav-lang-check" aria-hidden="true"></i>
|
|
122
|
+
</span>
|
|
123
|
+
{%- elsif _lt_t %}
|
|
124
|
+
<a class="list-group-item list-group-item-action nav-lang-item" data-lang="{{ _lt_lang }}" hreflang="{{ _lt_lang }}" href="{{ _lt_t.url | relative_url }}">
|
|
125
|
+
<span class="nav-lang-code">{{ _lt_lang | slice: 0, 2 | upcase }}</span>
|
|
126
|
+
<span class="nav-lang-name">{{ _lt_name }}</span>
|
|
127
|
+
<span class="nav-lang-auto" title="{{ _lt_auto_title }}">{{ _lt_auto_label }}</span>
|
|
128
|
+
</a>
|
|
129
|
+
{%- else %}
|
|
130
|
+
{%- comment -%}
|
|
131
|
+
No translation yet — still a LINK, not a disabled row. It goes to the
|
|
132
|
+
source page and records the preference, so the reader lands on real
|
|
133
|
+
content instead of a dead entry. hreflang describes the DESTINATION,
|
|
134
|
+
which is the source language, not the one that was clicked.
|
|
135
|
+
{%- endcomment -%}
|
|
136
|
+
<a class="list-group-item list-group-item-action nav-lang-item is-untranslated" data-lang="{{ _lt_lang }}" hreflang="{{ _lt_source }}" href="{{ _lt_fallback_url }}" aria-describedby="{{ _lt_note_id }}-panel">
|
|
137
|
+
<span class="nav-lang-code">{{ _lt_lang | slice: 0, 2 | upcase }}</span>
|
|
138
|
+
<span class="nav-lang-name">{{ _lt_name }}</span>
|
|
139
|
+
<span class="nav-lang-mark" aria-hidden="true">*</span>
|
|
140
|
+
</a>
|
|
141
|
+
{%- endif %}
|
|
142
|
+
{%- endfor %}
|
|
143
|
+
</div>
|
|
144
|
+
{%- if _lt_untranslated > 0 %}
|
|
145
|
+
<hr class="nav-lang-divider">
|
|
146
|
+
<p class="nav-lang-note" id="{{ _lt_note_id }}-panel">{{ _lt_note_text }}</p>
|
|
147
|
+
{%- endif %}
|
|
148
|
+
</div>
|
|
149
|
+
{%- else -%}
|
|
33
150
|
<div class="dropdown nav-lang-toggle" id="zer0-lang-toggle">
|
|
151
|
+
{%- comment -%}
|
|
152
|
+
Icon-only, square, and caret-free. `dropdown-toggle` is what draws the
|
|
153
|
+
Bootstrap caret; it is NOT what wires the dropdown — Bootstrap binds on
|
|
154
|
+
[data-bs-toggle="dropdown"], so dropping the class costs no behaviour.
|
|
155
|
+
The visible EN/FR label is gone at every width (it was already hidden
|
|
156
|
+
below xl), so title + aria-label are the only accessible name and both
|
|
157
|
+
name the language being read.
|
|
158
|
+
{%- endcomment -%}
|
|
34
159
|
<button
|
|
35
|
-
class="btn nav-lang-button
|
|
160
|
+
class="btn nav-lang-button"
|
|
36
161
|
type="button"
|
|
37
162
|
id="langToggleButton"
|
|
38
163
|
data-bs-toggle="dropdown"
|
|
39
164
|
aria-expanded="false"
|
|
40
165
|
aria-label="{{ ui.lang_toggle_aria | default: 'Change language' }}"
|
|
41
|
-
title="{{ ui.lang_toggle_label | default: 'Language' }}"
|
|
166
|
+
title="{{ ui.lang_toggle_label | default: 'Language' }}: {{ _lt_current_name }}"
|
|
42
167
|
>
|
|
43
168
|
<i class="{{ site.default_icon }} bi-translate" aria-hidden="true"></i>
|
|
44
|
-
<span class="nav-link-text d-none d-xl-inline ms-1 text-uppercase">{{ _lt_current | slice: 0, 2 }}</span>
|
|
45
169
|
</button>
|
|
46
|
-
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="langToggleButton">
|
|
170
|
+
<ul class="dropdown-menu dropdown-menu-end nav-lang-menu" aria-labelledby="langToggleButton">
|
|
47
171
|
<li>
|
|
48
172
|
{%- if _lt_current == _lt_source %}
|
|
49
|
-
<span class="dropdown-item
|
|
173
|
+
<span class="dropdown-item nav-lang-item is-current" aria-current="true">
|
|
174
|
+
<span class="nav-lang-name">{{ _lt_source_name }}</span>
|
|
175
|
+
<i class="bi bi-check2 nav-lang-check" aria-hidden="true"></i>
|
|
176
|
+
</span>
|
|
50
177
|
{%- else %}
|
|
51
|
-
<a class="dropdown-item" data-lang="{{ _lt_source }}" hreflang="{{ _lt_source }}" href="{{
|
|
178
|
+
<a class="dropdown-item nav-lang-item" data-lang="{{ _lt_source }}" hreflang="{{ _lt_source }}" href="{{ _lt_fallback_url }}">
|
|
179
|
+
<span class="nav-lang-name">{{ _lt_source_name }}</span>
|
|
180
|
+
</a>
|
|
52
181
|
{%- endif %}
|
|
53
182
|
</li>
|
|
54
183
|
{%- for _lt_lang in _lt_langs -%}
|
|
@@ -56,23 +185,41 @@
|
|
|
56
185
|
{%- assign _lt_t = _lt_entry[_lt_lang] %}
|
|
57
186
|
<li>
|
|
58
187
|
{%- if _lt_current == _lt_lang %}
|
|
59
|
-
<span class="dropdown-item
|
|
188
|
+
<span class="dropdown-item nav-lang-item is-current" aria-current="true">
|
|
189
|
+
<span class="nav-lang-name">{{ _lt_name }}</span>
|
|
190
|
+
<span class="nav-lang-auto" title="{{ _lt_auto_title }}">{{ _lt_auto_label }}</span>
|
|
191
|
+
<i class="bi bi-check2 nav-lang-check" aria-hidden="true"></i>
|
|
192
|
+
</span>
|
|
60
193
|
{%- elsif _lt_t %}
|
|
61
|
-
<a class="dropdown-item" data-lang="{{ _lt_lang }}" hreflang="{{ _lt_lang }}" href="{{ _lt_t.url | relative_url }}">
|
|
194
|
+
<a class="dropdown-item nav-lang-item" data-lang="{{ _lt_lang }}" hreflang="{{ _lt_lang }}" href="{{ _lt_t.url | relative_url }}">
|
|
195
|
+
<span class="nav-lang-name">{{ _lt_name }}</span>
|
|
196
|
+
<span class="nav-lang-auto" title="{{ _lt_auto_title }}">{{ _lt_auto_label }}</span>
|
|
197
|
+
</a>
|
|
62
198
|
{%- else %}
|
|
63
|
-
|
|
199
|
+
{%- comment -%} See the panel variant above — link, never a dead row. {%- endcomment -%}
|
|
200
|
+
<a class="dropdown-item nav-lang-item is-untranslated" data-lang="{{ _lt_lang }}" hreflang="{{ _lt_source }}" href="{{ _lt_fallback_url }}" aria-describedby="{{ _lt_note_id }}">
|
|
201
|
+
<span class="nav-lang-name">{{ _lt_name }}</span>
|
|
202
|
+
<span class="nav-lang-mark" aria-hidden="true">*</span>
|
|
203
|
+
</a>
|
|
64
204
|
{%- endif %}
|
|
65
205
|
</li>
|
|
66
206
|
{%- endfor %}
|
|
207
|
+
{%- if _lt_untranslated > 0 %}
|
|
208
|
+
<li><hr class="dropdown-divider"></li>
|
|
209
|
+
<li><p class="nav-lang-note" id="{{ _lt_note_id }}">{{ _lt_note_text }}</p></li>
|
|
210
|
+
{%- endif %}
|
|
67
211
|
</ul>
|
|
68
212
|
</div>
|
|
213
|
+
{%- endif -%}
|
|
69
214
|
<script>
|
|
70
215
|
// Remember the visitor's language pick (no auto-redirect — just preference).
|
|
216
|
+
// Delegated at document level so it covers both variants and stays correct
|
|
217
|
+
// if a consumer renders the component more than once per page.
|
|
71
218
|
(function () {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
var item = event.target.closest('a
|
|
219
|
+
if (window.__zer0LangPrefWired) return;
|
|
220
|
+
window.__zer0LangPrefWired = true;
|
|
221
|
+
document.addEventListener('click', function (event) {
|
|
222
|
+
var item = event.target.closest('#zer0-lang-toggle a[data-lang]');
|
|
76
223
|
if (!item) return;
|
|
77
224
|
try { localStorage.setItem('zer0-lang', item.getAttribute('data-lang')); } catch (e) { /* private mode */ }
|
|
78
225
|
});
|
|
@@ -103,6 +103,14 @@
|
|
|
103
103
|
<!-- Step 1 — request type -->
|
|
104
104
|
<fieldset class="pf-field pf-types">
|
|
105
105
|
<legend class="pf-legend">What kind of feedback?</legend>
|
|
106
|
+
{%- comment -%}
|
|
107
|
+
_data/feedback_types.yml is optional for consumers (remote_theme
|
|
108
|
+
cannot ship _data). Without it the widget falls back to a minimal
|
|
109
|
+
generic taxonomy (see pf_fallback_types below) instead of rendering
|
|
110
|
+
an empty selector. The fallback entries carry no GitHub labels and
|
|
111
|
+
no agent directive — labels must exist in the target repo or GitHub
|
|
112
|
+
silently drops them from prefilled issue URLs.
|
|
113
|
+
{%- endcomment -%}
|
|
106
114
|
{%- assign pf_group = "" -%}
|
|
107
115
|
{%- for t in site.data.feedback_types -%}
|
|
108
116
|
{%- if t.group != pf_group -%}
|
|
@@ -124,6 +132,54 @@
|
|
|
124
132
|
</span>
|
|
125
133
|
</button>
|
|
126
134
|
{%- if forloop.last -%}</div>{%- endif -%}
|
|
135
|
+
{%- else -%}
|
|
136
|
+
{%- comment -%}
|
|
137
|
+
_data/feedback_types.yml is optional for consumers (remote_theme
|
|
138
|
+
cannot ship _data). Without it, render this minimal generic
|
|
139
|
+
taxonomy instead of an empty selector. Ids/markup must stay in
|
|
140
|
+
sync with the fallback JSON in the #pageFeedbackTypes script
|
|
141
|
+
below. Fallback entries carry no GitHub labels (labels must exist
|
|
142
|
+
in the target repo or GitHub silently drops them) and no agent
|
|
143
|
+
directive.
|
|
144
|
+
{%- endcomment -%}
|
|
145
|
+
<p class="pf-types-group">This page</p>
|
|
146
|
+
<div class="pf-types-grid" role="radiogroup" aria-label="This page">
|
|
147
|
+
<button type="button" class="pf-type" role="radio" aria-checked="false"
|
|
148
|
+
data-pf-type-id="fix-page" tabindex="0">
|
|
149
|
+
<i class="bi bi-bug pf-type-icon" aria-hidden="true"></i>
|
|
150
|
+
<span class="pf-type-text">
|
|
151
|
+
<span class="pf-type-label">Report a problem</span>
|
|
152
|
+
<span class="pf-type-desc">A typo, broken link, wrong information, or something rendering badly</span>
|
|
153
|
+
</span>
|
|
154
|
+
</button>
|
|
155
|
+
<button type="button" class="pf-type" role="radio" aria-checked="false"
|
|
156
|
+
data-pf-type-id="improve-page" tabindex="-1">
|
|
157
|
+
<i class="bi bi-stars pf-type-icon" aria-hidden="true"></i>
|
|
158
|
+
<span class="pf-type-text">
|
|
159
|
+
<span class="pf-type-label">Improve this page</span>
|
|
160
|
+
<span class="pf-type-desc">Polish the copy, structure, or presentation</span>
|
|
161
|
+
</span>
|
|
162
|
+
</button>
|
|
163
|
+
</div>
|
|
164
|
+
<p class="pf-types-group">The site</p>
|
|
165
|
+
<div class="pf-types-grid" role="radiogroup" aria-label="The site">
|
|
166
|
+
<button type="button" class="pf-type" role="radio" aria-checked="false"
|
|
167
|
+
data-pf-type-id="feature" tabindex="-1">
|
|
168
|
+
<i class="bi bi-lightbulb pf-type-icon" aria-hidden="true"></i>
|
|
169
|
+
<span class="pf-type-text">
|
|
170
|
+
<span class="pf-type-label">Suggest a feature</span>
|
|
171
|
+
<span class="pf-type-desc">An idea that would make the site more useful</span>
|
|
172
|
+
</span>
|
|
173
|
+
</button>
|
|
174
|
+
<button type="button" class="pf-type" role="radio" aria-checked="false"
|
|
175
|
+
data-pf-type-id="question" tabindex="-1">
|
|
176
|
+
<i class="bi bi-question-circle pf-type-icon" aria-hidden="true"></i>
|
|
177
|
+
<span class="pf-type-text">
|
|
178
|
+
<span class="pf-type-label">Ask a question</span>
|
|
179
|
+
<span class="pf-type-desc">Something unclear that is not exactly a bug</span>
|
|
180
|
+
</span>
|
|
181
|
+
</button>
|
|
182
|
+
</div>
|
|
127
183
|
{%- endfor -%}
|
|
128
184
|
</fieldset>
|
|
129
185
|
|
|
@@ -219,9 +275,16 @@
|
|
|
219
275
|
}
|
|
220
276
|
</script>
|
|
221
277
|
|
|
222
|
-
<!-- Request-type taxonomy (from _data/feedback_types.yml
|
|
278
|
+
<!-- Request-type taxonomy (from _data/feedback_types.yml; generic fallback
|
|
279
|
+
when the site ships no data file — keep in sync with the fallback markup
|
|
280
|
+
in the pf-types fieldset above) -->
|
|
223
281
|
<script id="pageFeedbackTypes" type="application/json">
|
|
224
|
-
{{ site.data.feedback_types | jsonify }}
|
|
282
|
+
{% if site.data.feedback_types %}{{ site.data.feedback_types | jsonify }}{% else %}[
|
|
283
|
+
{"id":"fix-page","label":"Report a problem","icon":"bi-bug","group":"This page","scope":"page","description":"A typo, broken link, wrong information, or something rendering badly","labels":[],"agent":false,"placeholder":"What is wrong, where on the page, and what did you expect instead?"},
|
|
284
|
+
{"id":"improve-page","label":"Improve this page","icon":"bi-stars","group":"This page","scope":"page","description":"Polish the copy, structure, or presentation","labels":[],"agent":false,"placeholder":"What would make this page clearer, tighter, or more useful?"},
|
|
285
|
+
{"id":"feature","label":"Suggest a feature","icon":"bi-lightbulb","group":"The site","scope":"site","description":"An idea that would make the site more useful","labels":[],"agent":false,"placeholder":"Describe the feature and the problem it would solve."},
|
|
286
|
+
{"id":"question","label":"Ask a question","icon":"bi-question-circle","group":"The site","scope":"site","description":"Something unclear that is not exactly a bug","labels":[],"agent":false,"placeholder":"What would you like to know?"}
|
|
287
|
+
]{% endif %}
|
|
225
288
|
</script>
|
|
226
289
|
|
|
227
290
|
<!-- Live page context (build-time front matter; runtime detail added by JS) -->
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{% comment %} Feature: ZER0-083 {% endcomment %}
|
|
2
|
+
{%- comment -%}
|
|
3
|
+
Component: page-views-init
|
|
4
|
+
Path: _includes/components/page-views-init.html
|
|
5
|
+
Purpose: Bootstrap the page-view counter — inject its configuration and
|
|
6
|
+
load the behaviour. Renders no visible markup; the badge lives
|
|
7
|
+
in components/page-views.html.
|
|
8
|
+
Params: none — configured via site.page_views
|
|
9
|
+
Depends on: assets/js/page-views.js
|
|
10
|
+
Included by: _includes/core/head.html (once per page)
|
|
11
|
+
Notes: Split from the badge include on purpose: the badge may appear
|
|
12
|
+
many times on a page, this must appear exactly once.
|
|
13
|
+
{%- endcomment -%}
|
|
14
|
+
|
|
15
|
+
{%- unless site.page_views.enabled -%}{%- comment -%} counter disabled {%- endcomment -%}
|
|
16
|
+
{%- else -%}
|
|
17
|
+
{%- comment -%}
|
|
18
|
+
`respect_dnt` / `respect_gpc` / `require_consent` default to the PostHog
|
|
19
|
+
privacy block so a site configures its privacy posture once. Liquid's
|
|
20
|
+
`default` filter treats `false` as "missing", so each flag is resolved with
|
|
21
|
+
an if-tag instead — otherwise an explicit `false` would silently flip true.
|
|
22
|
+
{%- endcomment -%}
|
|
23
|
+
{%- assign pv_dnt = true -%}
|
|
24
|
+
{%- if site.page_views.respect_dnt == false -%}{%- assign pv_dnt = false -%}{%- endif -%}
|
|
25
|
+
{%- assign pv_gpc = true -%}
|
|
26
|
+
{%- if site.page_views.respect_gpc == false -%}{%- assign pv_gpc = false -%}{%- endif -%}
|
|
27
|
+
{%- assign pv_consent = false -%}
|
|
28
|
+
{%- if site.page_views.require_consent -%}{%- assign pv_consent = true -%}{%- endif -%}
|
|
29
|
+
{%- assign pv_track = true -%}
|
|
30
|
+
{%- if site.page_views.track == false -%}{%- assign pv_track = false -%}{%- endif -%}
|
|
31
|
+
{%- assign pv_fallback = false -%}
|
|
32
|
+
{%- if site.page_views.fallback_local -%}{%- assign pv_fallback = true -%}{%- endif -%}
|
|
33
|
+
|
|
34
|
+
<!-- Page-view counter configuration consumed by assets/js/page-views.js -->
|
|
35
|
+
<script id="pageViewsConfig" type="application/json">
|
|
36
|
+
{
|
|
37
|
+
"enabled": true,
|
|
38
|
+
"provider": {{ site.page_views.provider | default: "local" | jsonify }},
|
|
39
|
+
"endpoint": {{ site.page_views.endpoint | default: "" | jsonify }},
|
|
40
|
+
"method": {{ site.page_views.method | default: "POST" | jsonify }},
|
|
41
|
+
"countKey": {{ site.page_views.count_key | default: "views" | jsonify }},
|
|
42
|
+
"dedupe": {{ site.page_views.dedupe | default: "session" | jsonify }},
|
|
43
|
+
"maxEntries": {{ site.page_views.max_entries | default: 500 }},
|
|
44
|
+
"track": {{ pv_track | jsonify }},
|
|
45
|
+
"respectDnt": {{ pv_dnt | jsonify }},
|
|
46
|
+
"respectGpc": {{ pv_gpc | jsonify }},
|
|
47
|
+
"requireConsent": {{ pv_consent | jsonify }},
|
|
48
|
+
"fallbackLocal": {{ pv_fallback | jsonify }},
|
|
49
|
+
"baseurl": {{ site.baseurl | default: "" | jsonify }},
|
|
50
|
+
"labelOne": {{ site.page_views.label_one | default: "view" | jsonify }},
|
|
51
|
+
"labelOther": {{ site.page_views.label_other | default: "views" | jsonify }}
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
<script defer src="{{ '/assets/js/page-views.js' | relative_url }}"></script>
|
|
55
|
+
{%- endunless -%}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{% comment %} Feature: ZER0-083 {% endcomment %}
|
|
2
|
+
{%- comment -%}
|
|
3
|
+
Component: page-views
|
|
4
|
+
Path: _includes/components/page-views.html
|
|
5
|
+
Purpose: Inline "N views" badge for a page. Ships hidden and is revealed
|
|
6
|
+
by assets/js/page-views.js once a count is known, so a page
|
|
7
|
+
with no data never flashes "0 views".
|
|
8
|
+
Params: url (string, optional, default: page.url) — page to show
|
|
9
|
+
class (string, optional, default: "") — extra classes
|
|
10
|
+
icon (string, optional, default: "bi-eye")
|
|
11
|
+
separator (bool, optional, default: false) — render a
|
|
12
|
+
leading "•" INSIDE the badge, so the separator hides
|
|
13
|
+
with it in a meta row
|
|
14
|
+
Depends on: components/page-views-init.html (config + behaviour, loaded once
|
|
15
|
+
from core/head.html), _sass/components/_page-views.scss
|
|
16
|
+
Notes: Safe to include more than once per page — each badge resolves
|
|
17
|
+
its own `data-page-views` path. Paths are site-relative and
|
|
18
|
+
baseurl-free (page.url); page-views.js normalizes the runtime
|
|
19
|
+
location the same way.
|
|
20
|
+
{%- endcomment -%}
|
|
21
|
+
|
|
22
|
+
{%- unless site.page_views.enabled -%}{%- comment -%} counter disabled {%- endcomment -%}
|
|
23
|
+
{%- else -%}
|
|
24
|
+
{%- assign pv_path = include.url | default: page.url -%}
|
|
25
|
+
{%- assign pv_class = include.class | default: "" -%}
|
|
26
|
+
{%- assign pv_icon = include.icon | default: "bi-eye" -%}
|
|
27
|
+
<span class="page-views{% if pv_class != "" %} {{ pv_class }}{% endif %}" data-page-views="{{ pv_path | escape }}" hidden>
|
|
28
|
+
{%- if include.separator -%}<span class="page-views__sep mx-2" aria-hidden="true">•</span>{%- endif -%}
|
|
29
|
+
<i class="bi {{ pv_icon }} me-1" aria-hidden="true"></i>
|
|
30
|
+
<span class="page-views__count" data-page-views-count>0</span>
|
|
31
|
+
<span class="page-views__label" data-page-views-label>{{ site.page_views.label_other | default: "views" }}</span>
|
|
32
|
+
</span>
|
|
33
|
+
{%- endunless -%}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{% comment %} Feature: ZER0-084 {% endcomment %}
|
|
2
|
+
{%- comment -%}
|
|
3
|
+
Component: recipe-card
|
|
4
|
+
Path: _includes/components/recipe-card.html
|
|
5
|
+
Purpose: Index card for one recipe — photo, title, one-line description,
|
|
6
|
+
and the three facts a cook scans for: total time, what it makes,
|
|
7
|
+
and how hard it is.
|
|
8
|
+
Params: recipe (document, required) — a `recipes` collection doc
|
|
9
|
+
heading_level (number, optional, default: 3) — the title's
|
|
10
|
+
heading level, so a card never skips a level under whatever
|
|
11
|
+
heading precedes it
|
|
12
|
+
class (string, optional, default: "")
|
|
13
|
+
Depends on: components/preview-image.html, components/recipe-duration.html,
|
|
14
|
+
_sass/components/_recipe.scss (.recipe-card)
|
|
15
|
+
Notes: Total time comes from `times.total` or prep + cook + rest, so
|
|
16
|
+
the card never disagrees with the recipe page. The whole card is
|
|
17
|
+
one link for a large tap target; the facts row is plain text so
|
|
18
|
+
it stays readable when images fail to load.
|
|
19
|
+
{%- endcomment -%}
|
|
20
|
+
{%- if include.recipe == nil -%}
|
|
21
|
+
{%- comment -%} ERROR: recipe-card requires `recipe` {%- endcomment -%}
|
|
22
|
+
{%- else -%}
|
|
23
|
+
{%- assign _rc_r = include.recipe -%}
|
|
24
|
+
{%- assign _rc_img = _rc_r.image | default: _rc_r.preview | default: site.teaser -%}
|
|
25
|
+
{%- assign _rc_prep = _rc_r.times.prep | default: 0 -%}
|
|
26
|
+
{%- assign _rc_cook = _rc_r.times.cook | default: 0 -%}
|
|
27
|
+
{%- assign _rc_rest = _rc_r.times.rest | default: 0 -%}
|
|
28
|
+
{%- if _rc_r.times.total -%}
|
|
29
|
+
{%- assign _rc_total = _rc_r.times.total -%}
|
|
30
|
+
{%- else -%}
|
|
31
|
+
{%- assign _rc_total = _rc_prep | plus: _rc_cook | plus: _rc_rest -%}
|
|
32
|
+
{%- endif -%}
|
|
33
|
+
{%- capture _rc_total_h -%}{%- include components/recipe-duration.html minutes=_rc_total -%}{%- endcapture -%}
|
|
34
|
+
{%- assign _rc_total_h = _rc_total_h | strip -%}
|
|
35
|
+
<article class="recipe-card h-100 {{ include.class | default: '' }}">
|
|
36
|
+
<a href="{{ _rc_r.url | relative_url }}" class="recipe-card-link d-flex flex-column h-100 text-decoration-none text-body">
|
|
37
|
+
{%- if _rc_img -%}
|
|
38
|
+
<div class="recipe-card-media">
|
|
39
|
+
{%- include components/preview-image.html src=_rc_img alt=_rc_r.title class="" -%}
|
|
40
|
+
</div>
|
|
41
|
+
{%- endif -%}
|
|
42
|
+
<div class="recipe-card-body">
|
|
43
|
+
{%- assign _rc_hl = include.heading_level | default: 3 -%}
|
|
44
|
+
<h{{ _rc_hl }} class="h5 recipe-card-title">{{ _rc_r.title }}</h{{ _rc_hl }}>
|
|
45
|
+
{%- if _rc_r.description -%}
|
|
46
|
+
<p class="recipe-card-description small text-body-secondary">{{ _rc_r.description | strip_html | truncate: 120 }}</p>
|
|
47
|
+
{%- endif -%}
|
|
48
|
+
<p class="recipe-card-facts small text-body-secondary mb-0">
|
|
49
|
+
{%- assign _rc_sep = false -%}
|
|
50
|
+
{%- if _rc_total_h != "" -%}
|
|
51
|
+
<span class="recipe-card-fact"><i class="bi bi-clock-history me-1" aria-hidden="true"></i>{{ _rc_total_h }}</span>
|
|
52
|
+
{%- assign _rc_sep = true -%}
|
|
53
|
+
{%- endif -%}
|
|
54
|
+
{%- if _rc_r.yield.amount -%}
|
|
55
|
+
{%- if _rc_sep %}<span class="mx-1" aria-hidden="true">•</span>{% endif -%}
|
|
56
|
+
<span class="recipe-card-fact"><i class="bi bi-pie-chart me-1" aria-hidden="true"></i>{{ _rc_r.yield.amount }} {{ _rc_r.yield.unit | default: "servings" | escape }}</span>
|
|
57
|
+
{%- assign _rc_sep = true -%}
|
|
58
|
+
{%- endif -%}
|
|
59
|
+
{%- if _rc_r.difficulty -%}
|
|
60
|
+
{%- if _rc_sep %}<span class="mx-1" aria-hidden="true">•</span>{% endif -%}
|
|
61
|
+
<span class="recipe-difficulty recipe-difficulty--{{ _rc_r.difficulty | downcase | strip }}">{{ _rc_r.difficulty | capitalize }}</span>
|
|
62
|
+
{%- endif -%}
|
|
63
|
+
</p>
|
|
64
|
+
</div>
|
|
65
|
+
</a>
|
|
66
|
+
</article>
|
|
67
|
+
{%- endif -%}
|