jekyll-theme-zer0 0.10.4 → 0.15.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +459 -0
  3. data/README.md +24 -8
  4. data/_data/navigation/about.yml +39 -11
  5. data/_data/navigation/docs.yml +53 -23
  6. data/_data/navigation/home.yml +27 -9
  7. data/_data/navigation/main.yml +27 -8
  8. data/_data/navigation/posts.yml +22 -6
  9. data/_data/navigation/quickstart.yml +8 -3
  10. data/_includes/README.md +2 -0
  11. data/_includes/components/js-cdn.html +4 -1
  12. data/_includes/components/post-card.html +2 -11
  13. data/_includes/components/preview-image.html +32 -0
  14. data/_includes/content/intro.html +5 -6
  15. data/_includes/core/footer.html +5 -3
  16. data/_includes/core/header.html +14 -0
  17. data/_includes/navigation/sidebar-categories.html +20 -9
  18. data/_includes/navigation/sidebar-folders.html +8 -7
  19. data/_includes/navigation/sidebar-right.html +16 -10
  20. data/_layouts/blog.html +15 -45
  21. data/_layouts/category.html +4 -24
  22. data/_layouts/collection.html +2 -12
  23. data/_layouts/default.html +1 -1
  24. data/_layouts/journals.html +2 -12
  25. data/_layouts/notebook.html +296 -0
  26. data/_sass/core/_docs.scss +1 -1
  27. data/_sass/custom.scss +55 -18
  28. data/_sass/notebooks.scss +458 -0
  29. data/assets/images/notebooks/test-notebook_files/test-notebook_4_0.png +0 -0
  30. data/assets/js/sidebar.js +511 -0
  31. data/scripts/README.md +128 -105
  32. data/scripts/analyze-commits.sh +9 -311
  33. data/scripts/bin/build +22 -22
  34. data/scripts/build +7 -111
  35. data/scripts/convert-notebooks.sh +415 -0
  36. data/scripts/features/validate_preview_urls.py +500 -0
  37. data/scripts/fix-markdown-format.sh +8 -262
  38. data/scripts/generate-preview-images.sh +7 -787
  39. data/scripts/install-preview-generator.sh +8 -528
  40. data/scripts/lib/README.md +5 -5
  41. data/scripts/lib/gem.sh +19 -7
  42. data/scripts/release +7 -236
  43. data/scripts/setup.sh +9 -153
  44. data/scripts/test-auto-version.sh +7 -256
  45. data/scripts/test-mermaid.sh +7 -287
  46. data/scripts/test.sh +9 -154
  47. metadata +9 -10
  48. data/scripts/features/preview_generator.py +0 -646
  49. data/scripts/lib/test/run_tests.sh +0 -140
  50. data/scripts/lib/test/test_changelog.sh +0 -87
  51. data/scripts/lib/test/test_gem.sh +0 -68
  52. data/scripts/lib/test/test_git.sh +0 -82
  53. data/scripts/lib/test/test_validation.sh +0 -72
  54. data/scripts/lib/test/test_version.sh +0 -96
  55. data/scripts/version.sh +0 -178
@@ -1,15 +1,33 @@
1
+ # Home Navigation Configuration
2
+ # Used by: Header navigation and homepage quick links
3
+ # Purpose: Quick access links for the home/landing area
4
+
1
5
  - title: Home
2
6
  icon: bi-house
3
- url: /home
7
+ url: /
4
8
  sublinks:
5
- - title: Style
6
- url: /home/style/
7
- - title: Map
8
- icon: bi-map
9
+ - title: Blog
10
+ url: /blog/
11
+ - title: Quick Start
12
+ url: /quickstart/
13
+
14
+ - title: Discover
15
+ icon: bi-compass
9
16
  url: /sitemap/
10
17
  sublinks:
11
- - title: Search
12
- url: /search/
13
- - title: Roadmap
14
- url: /roadmap/
18
+ - title: Site Map
19
+ url: /sitemap/
20
+ - title: Categories
21
+ url: /categories/
22
+ - title: Tags
23
+ url: /tags/
24
+
25
+ - title: Connect
26
+ icon: bi-envelope
27
+ url: /contact/
28
+ sublinks:
29
+ - title: Contact
30
+ url: /contact/
31
+ - title: About
32
+ url: /about/
15
33
 
@@ -1,30 +1,49 @@
1
+ # Main Navigation Configuration
2
+ # Used by: _includes/navigation/navbar.html
3
+ # Purpose: Primary site navigation with dropdown sublinks
4
+
1
5
  - title: Quick Start
6
+ icon: bi-rocket-takeoff
2
7
  url: /quickstart/
3
8
  sublinks:
4
- - title: zer0
5
- url: /zer0/
6
9
  - title: Machine Setup
7
10
  url: /quickstart/machine-setup/
8
- - title: "Jekyll Setup"
11
+ - title: Jekyll Setup
9
12
  url: /quickstart/jekyll-setup/
10
- - title: "GitHub Setup"
13
+ - title: GitHub Setup
11
14
  url: /quickstart/github-setup/
15
+
12
16
  - title: Blog
17
+ icon: bi-journal-text
13
18
  url: /blog/
14
19
  sublinks:
15
20
  - title: All Posts
16
21
  url: /posts/
22
+ - title: Categories
23
+ url: /categories/
24
+ - title: Tags
25
+ url: /tags/
26
+
17
27
  - title: Docs
28
+ icon: bi-journal-bookmark
18
29
  url: /docs/
19
30
  sublinks:
20
- - title: Jekyll
31
+ - title: Jekyll Guide
21
32
  url: /docs/jekyll/
33
+ - title: Features
34
+ url: /about/features/
35
+
22
36
  - title: About
37
+ icon: bi-info-circle
23
38
  url: /about/
24
39
  sublinks:
40
+ - title: Features
41
+ url: /about/features/
42
+ - title: Statistics
43
+ url: /about/stats/
25
44
  - title: Config
26
45
  url: /about/config/
27
- - title: Theme
28
- url: /about/theme/
29
46
  - title: Site Map
30
- url: /sitemap/
47
+ url: /sitemap/
48
+ - title: Contact
49
+ url: /contact/
@@ -1,18 +1,34 @@
1
+ # Posts/Blog Category Navigation Configuration
2
+ # Used by: Blog layout, category pages, and post navigation
3
+ # Purpose: Category-based navigation for blog posts
4
+ # Note: Categories match folders in pages/_posts/
5
+
1
6
  - title: Development
2
- icon: code-slash
7
+ icon: bi-code-slash
3
8
  url: /posts/development/
9
+ description: "Software development, coding practices, and programming tutorials"
10
+
4
11
  - title: Technology
5
- icon: cpu
12
+ icon: bi-cpu
6
13
  url: /posts/technology/
14
+ description: "Tech news, tools, and emerging technologies"
15
+
7
16
  - title: Tutorial
8
- icon: journal-code
17
+ icon: bi-journal-code
9
18
  url: /posts/tutorial/
19
+ description: "Step-by-step guides and how-to articles"
20
+
10
21
  - title: World
11
- icon: globe
22
+ icon: bi-globe
12
23
  url: /posts/world/
24
+ description: "Global trends, remote work, and industry insights"
25
+
13
26
  - title: Business
14
- icon: briefcase
27
+ icon: bi-briefcase
15
28
  url: /posts/business/
29
+ description: "Business strategies, startups, and entrepreneurship"
30
+
16
31
  - title: Science
17
- icon: mortarboard
32
+ icon: bi-mortarboard
18
33
  url: /posts/science/
34
+ description: "Scientific discoveries, research, and innovation"
@@ -1,12 +1,17 @@
1
+ # Quick Start Navigation Configuration
2
+ # Used by: Quick Start sidebar and related pages
3
+ # Purpose: Step-by-step guide navigation for new users
4
+
1
5
  - title: "Quick Start"
6
+ icon: bi-flag
2
7
  url: /quickstart/
3
8
  sublinks:
4
9
  - title: "Machine Setup"
5
10
  url: /quickstart/machine-setup/
6
- description: "Platform-specific setup and Docker installation"
11
+ icon: bi-laptop
7
12
  - title: "Jekyll Setup"
8
13
  url: /quickstart/jekyll-setup/
9
- description: "Jekyll theme configuration and development environment"
14
+ icon: bi-code-square
10
15
  - title: "GitHub Setup"
11
16
  url: /quickstart/github-setup/
12
- description: "Version control, GitHub integration, and deployment"
17
+ icon: bi-github
data/_includes/README.md CHANGED
@@ -46,6 +46,8 @@ Reusable UI components and widgets:
46
46
  - `zer0-env-var.html` - Environment variable configuration
47
47
  - `svg.html` - SVG icon definitions
48
48
  - `js-cdn.html` - CDN JavaScript libraries
49
+ - `preview-image.html` - Consistent preview image rendering with lazy loading
50
+ - `post-card.html` - Reusable post card component for listings
49
51
 
50
52
  ### `content/`
51
53
 
@@ -26,4 +26,7 @@
26
26
  <!-- Bootstrap 5.3.3 Bundle (includes Popper.js) -->
27
27
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
28
28
  integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
29
- crossorigin="anonymous"></script>
29
+ crossorigin="anonymous"></script>
30
+
31
+ <!-- Sidebar Navigation Enhancements - Deferred for performance -->
32
+ <script defer src="{{ '/assets/js/sidebar.js' | relative_url }}"></script>
@@ -78,17 +78,8 @@
78
78
 
79
79
  <!-- Preview Image -->
80
80
  <a href="{{ include.post.url | relative_url }}" class="text-decoration-none">
81
- {% if include.post.preview %}
82
- <img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ include.post.preview }}"
83
- class="card-img-top"
84
- alt="Preview image for {{ include.post.title }}"
85
- loading="lazy">
86
- {% else %}
87
- <img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ site.teaser }}"
88
- class="card-img-top"
89
- alt="Default preview image"
90
- loading="lazy">
91
- {% endif %}
81
+ {% assign card_img = include.post.preview | default: site.teaser %}
82
+ {% include components/preview-image.html src=card_img alt=include.post.title class="card-img-top" %}
92
83
  </a>
93
84
  </div>
94
85
 
@@ -0,0 +1,32 @@
1
+ {% comment %}
2
+ PREVIEW IMAGE - Simple, Consistent Image Rendering
3
+
4
+ Usage:
5
+ {%- include components/preview-image.html
6
+ src=post.preview
7
+ alt=post.title
8
+ class="card-img-top"
9
+ style="height: 180px; object-fit: cover;"
10
+ -%}
11
+
12
+ Convention: All preview paths should be absolute (e.g., /assets/images/preview.png)
13
+ External URLs (https://...) are also supported.
14
+ {% endcomment %}
15
+ {% assign img = include.src | default: site.teaser %}
16
+ {% assign alt = include.alt | default: "Preview image" %}
17
+ {% assign class = include.class | default: "card-img-top" %}
18
+ {% assign style = include.style %}
19
+ {% assign loading = include.loading | default: "lazy" %}
20
+
21
+ {% comment %} External URL or internal path {% endcomment %}
22
+ {% if img contains '://' %}
23
+ {% assign final_src = img %}
24
+ {% else %}
25
+ {% assign final_src = img | relative_url %}
26
+ {% endif %}
27
+
28
+ <img src="{{ final_src }}"
29
+ class="{{ class }}"
30
+ alt="{{ alt | escape }}"
31
+ {% if style %}style="{{ style }}"{% endif %}
32
+ loading="{{ loading }}">
@@ -16,19 +16,18 @@
16
16
  <!-- Intro Section -->
17
17
 
18
18
  {% comment %}
19
- Determine the preview image path. If page.preview starts with '/' or 'http',
20
- use it directly. Otherwise, prepend the public folder path.
19
+ Determine the preview image path. All preview paths should be absolute
20
+ starting with /assets/ or external URLs starting with ://
21
21
  {% endcomment %}
22
22
  {% assign preview_image = page.preview | default: site.info_banner %}
23
- {% assign preview_first_char = preview_image | slice: 0 %}
24
- {% if preview_image contains '://' or preview_first_char == '/' %}
23
+ {% if preview_image contains '://' %}
25
24
  {% assign preview_path = preview_image %}
26
25
  {% else %}
27
- {% assign preview_path = '/' | append: site.public_folder | append: '/' | append: preview_image %}
26
+ {% assign preview_path = preview_image | relative_url %}
28
27
  {% endif %}
29
28
 
30
29
  <div class="bd-intro pt-5 ps-lg-2 position-relative" style="
31
- background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('{{ site.baseurl }}{{ preview_path }}') no-repeat center center / cover;
30
+ background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('{{ preview_path | relative_url }}') no-repeat center center / cover;
32
31
  color: #fff;">
33
32
 
34
33
  <br>
@@ -39,14 +39,15 @@
39
39
  </span>
40
40
  {% for power in site.powered_by %}
41
41
  <li class="btn align-items-end">
42
+ {% capture icon_classes %}{{ site.default_icon }} {{ power.icon | default: site.powered_by.default }}{% endcapture %}
42
43
  {% if power.url %}
43
44
  <a href="{{ power.url }}" rel="nofollow">
44
- <i class="{{ site.default_icon }} {{ power.icon | default: site.powered_by.default }}" aria-hidden="true"></i>
45
+ <i class="{{ icon_classes }}" aria-hidden="true"></i>
45
46
  <span class="d-none d-md-inline">{{ power.name }}</span>
46
47
  </a>
47
48
  {% else %}
48
49
  <span class="text-muted">
49
- <i class="{{ site.default_icon }} {{ power.icon | default: site.powered_by.default }}" aria-hidden="true"></i>
50
+ <i class="{{ icon_classes }}" aria-hidden="true"></i>
50
51
  <span class="d-none d-md-inline">{{ power.name }}</span>
51
52
  </span>
52
53
  {% endif %}
@@ -115,8 +116,9 @@
115
116
  {% for link in site.links %}
116
117
  {% if link.url %}
117
118
  <li class="list-inline-item">
119
+ {% capture link_icon_classes %}{{ site.default_icon }} {{ link.icon | default: site.links.default }}{% endcapture %}
118
120
  <a href="{{ link.url }}" rel="nofollow" class="text-light text-decoration-none">
119
- <i class="{{ site.default_icon }} {{ link.icon | default: site.links.default }}" aria-hidden="true"></i>
121
+ <i class="{{ link_icon_classes }}" aria-hidden="true"></i>
120
122
  <span class="d-none d-md-inline">{{ link.label }}</span>
121
123
  </a>
122
124
  </li>
@@ -13,6 +13,7 @@
13
13
  - Offcanvas sidebars for mobile navigation
14
14
  - Brand logo and home navigation integration
15
15
  - Settings modal and navigation toggles
16
+ - Skip-to-content link for accessibility
16
17
 
17
18
  Dependencies:
18
19
  - branding.html: Site title and logo display
@@ -24,10 +25,23 @@
24
25
  - Mobile: Collapsed navigation with hamburger menu
25
26
  - Offcanvas panels for sidebar and navigation
26
27
 
28
+ Accessibility:
29
+ - Skip-to-content link for keyboard navigation
30
+ - ARIA labels for screen readers
31
+ - Focus management for offcanvas
32
+
27
33
  TODO: Fix Nanobar progress bar positioning and animation
28
34
  ===================================================================
29
35
  -->
30
36
 
37
+ <!-- ================================ -->
38
+ <!-- SKIP TO CONTENT LINK -->
39
+ <!-- ================================ -->
40
+ <!-- Accessibility: allows keyboard users to skip navigation -->
41
+ <a href="#main-content" class="visually-hidden-focusable position-absolute top-0 start-0 z-3 m-3 btn btn-primary">
42
+ Skip to main content
43
+ </a>
44
+
31
45
  <header id="navbar" class="container-fluid text-center fixed-top" role="navigation">
32
46
 
33
47
  <!-- ================================ -->
@@ -1,18 +1,29 @@
1
1
  <!-- sidebar-categories.html -->
2
- <div class="d-flex flex-column flex-shrink-0" style="width: 280px;">
3
- <a href="/" class="d-flex align-items-center text-decoration-none">
2
+ <div class="d-flex flex-column flex-shrink-0 w-100">
3
+ <a href="/" class="d-flex align-items-center text-decoration-none mb-3">
4
+ <i class="bi bi-folder2-open me-2 fs-4"></i>
4
5
  <span class="fs-4">Categories</span>
5
6
  </a>
6
- <hr>
7
+ <hr class="my-2">
7
8
  {% for category in site.categories %}
8
- <div class="">
9
- <button class="btn btn-toggle align-items-center rounded collapsed" data-bs-toggle="collapse" data-bs-target="#{{category[0]}}-list" aria-expanded="false">
10
- {{ category[0] }}
9
+ <div class="mb-1">
10
+ <button class="btn btn-toggle d-flex align-items-center w-100 rounded collapsed"
11
+ data-bs-toggle="collapse"
12
+ data-bs-target="#{{category[0] | slugify}}-list"
13
+ aria-expanded="false"
14
+ aria-controls="{{category[0] | slugify}}-list">
15
+ <i class="bi bi-folder me-2"></i>
16
+ <span>{{ category[0] }}</span>
11
17
  </button>
12
- <div class="collapse" id="{{category[0]}}-list">
13
- <ul class="btn-toggle-nav list-unstyled fw-normal small">
18
+ <div class="collapse" id="{{category[0] | slugify}}-list">
19
+ <ul class="btn-toggle-nav list-unstyled fw-normal small ps-4">
14
20
  {% for post in category[1] %}
15
- <li class="link-light"><a href="{{ post.url }}" class=" rounded">{{ post.title }}</a></li>
21
+ <li class="py-1">
22
+ <a href="{{ post.url | relative_url }}" class="text-decoration-none rounded d-block py-1 px-2{% if page.url == post.url %} active{% endif %}">
23
+ <i class="bi bi-file-earmark-text me-1"></i>
24
+ {{ post.title }}
25
+ </a>
26
+ </li>
16
27
  {% endfor %}
17
28
  </ul>
18
29
  </div>
@@ -11,12 +11,13 @@
11
11
  - Gets the current page's collection
12
12
  - Sorts documents by path to maintain folder hierarchy
13
13
  - Displays folder names and document links
14
- - Uses Bootstrap list-group for styling
14
+ - Uses Bootstrap list-group for styling with Bootstrap Icons
15
15
 
16
16
  Dependencies:
17
17
  - page.collection: Current collection name from page front matter
18
18
  - site.collections: Jekyll collections configuration
19
19
  - Bootstrap 5 list-group component
20
+ - Bootstrap Icons for folder/file indicators
20
21
 
21
22
  Usage:
22
23
  - Include in sidebar when page.sidebar.nav == "dynamic"
@@ -27,8 +28,8 @@
27
28
  {% assign root_folder = site.collections | where: "label", page.collection | first %}
28
29
 
29
30
  {% if root_folder %}
30
- <h2 class="h5 mb-3">
31
- <i class="bi bi-folder me-2"></i>
31
+ <h2 class="h5 mb-3 d-flex align-items-center">
32
+ <i class="bi bi-folder2-open me-2"></i>
32
33
  {{ root_folder.label | capitalize }}
33
34
  </h2>
34
35
 
@@ -42,8 +43,8 @@
42
43
  {% if current_path != prev_path %}
43
44
  {% for folder in current_path %}
44
45
  {% if forloop.index != 1 %}
45
- <li class="folder list-group-item bg-body-tertiary fw-semibold">
46
- <i class="bi bi-folder2 me-1"></i>
46
+ <li class="folder list-group-item bg-body-tertiary fw-semibold d-flex align-items-center">
47
+ <i class="bi bi-folder2 me-2"></i>
47
48
  {{ folder }}
48
49
  </li>
49
50
  {% endif %}
@@ -52,8 +53,8 @@
52
53
  {% endif %}
53
54
 
54
55
  <li class="file list-group-item list-group-item-action{% if page.url == doc.url %} active{% endif %}">
55
- <a href="{{ doc.url | relative_url }}" class="text-decoration-none">
56
- <i class="bi bi-file-text me-1"></i>
56
+ <a href="{{ doc.url | relative_url }}" class="text-decoration-none d-flex align-items-center">
57
+ <i class="bi bi-file-earmark-text me-2"></i>
57
58
  {{ doc.title }}
58
59
  </a>
59
60
  </li>
@@ -4,11 +4,17 @@
4
4
  includes: nav_list.html, content/toc.html
5
5
  -->
6
6
 
7
- <!-- TOC button in mobile view -->
8
- <div class="d-flex justify-content-end position-fixed top-50 end-0 translate-middle-y me-3">
9
-
10
- <button class="btn bg-opacity-2 bd-toc-toggle d-lg-none border z-2" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocContents" aria-controls="tocContents">
11
- <i class="bi bi-list-ul"></i>
7
+ <!-- TOC button in mobile view - Floating Action Button pattern -->
8
+ <!-- Positioned above back-to-top button to prevent overlap -->
9
+ <div class="d-lg-none position-fixed bd-toc-fab" style="bottom: 90px; right: 20px; z-index: 1030;">
10
+ <button class="btn btn-primary rounded-circle shadow-lg bd-toc-toggle"
11
+ type="button"
12
+ data-bs-toggle="offcanvas"
13
+ data-bs-target="#tocContents"
14
+ aria-controls="tocContents"
15
+ aria-label="Toggle Table of Contents"
16
+ style="width: 56px; height: 56px;">
17
+ <i class="bi bi-list-ul fs-5"></i>
12
18
  </button>
13
19
  </div>
14
20
 
@@ -20,11 +26,11 @@
20
26
  </div>
21
27
  <div class="offcanvas-body">
22
28
  <!-- Table of contents -->
23
- <nav id="TableOfContents">
24
- <!-- TOC tile -->
25
- <div>
26
- <strong>
27
- <i class="fas fa-file-alt"></i>
29
+ <nav id="TableOfContents" role="navigation" aria-label="Table of Contents">
30
+ <!-- TOC title -->
31
+ <div class="mb-3">
32
+ <strong class="d-flex align-items-center">
33
+ <i class="bi bi-file-text me-2"></i>
28
34
  On this page
29
35
  </strong>
30
36
  </div>
data/_layouts/blog.html CHANGED
@@ -105,15 +105,13 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
105
105
  </a>
106
106
  </div>
107
107
  <div class="col-lg-6">
108
- {% if hero_post.preview %}
109
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ hero_post.preview }}"
110
- class="w-100 h-100" style="object-fit: cover; min-height: 400px;"
111
- alt="{{ hero_post.title }}" loading="lazy">
112
- {% else %}
113
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ site.og_image }}"
114
- class="w-100 h-100" style="object-fit: cover; min-height: 400px;"
115
- alt="Featured image" loading="lazy">
116
- {% endif %}
108
+ {% assign hero_img = hero_post.preview | default: site.og_image %}
109
+ {% include components/preview-image.html
110
+ src=hero_img
111
+ alt=hero_post.title
112
+ class="w-100 h-100"
113
+ style="object-fit: cover; min-height: 400px;"
114
+ %}
117
115
  </div>
118
116
  </div>
119
117
  </section>
@@ -162,15 +160,8 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
162
160
  <i class="bi bi-star-fill me-1"></i>Featured
163
161
  </span>
164
162
  <a href="{{ main_featured.url | relative_url }}">
165
- {% if main_featured.preview %}
166
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ main_featured.preview }}"
167
- class="card-img-top" style="height: 300px; object-fit: cover;"
168
- alt="{{ main_featured.title }}" loading="lazy">
169
- {% else %}
170
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ site.teaser }}"
171
- class="card-img-top" style="height: 300px; object-fit: cover;"
172
- alt="Preview" loading="lazy">
173
- {% endif %}
163
+ {% assign main_img = main_featured.preview | default: site.teaser %}
164
+ {% include components/preview-image.html src=main_img alt=main_featured.title class="card-img-top" style="height: 300px; object-fit: cover;" %}
174
165
  </a>
175
166
  </div>
176
167
  <div class="card-body">
@@ -206,15 +197,8 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
206
197
  <div class="col">
207
198
  <div class="card h-100 border-0 shadow-sm hover-lift">
208
199
  <a href="{{ fpost.url | relative_url }}">
209
- {% if fpost.preview %}
210
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ fpost.preview }}"
211
- class="card-img-top" style="height: 120px; object-fit: cover;"
212
- alt="{{ fpost.title }}" loading="lazy">
213
- {% else %}
214
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ site.teaser }}"
215
- class="card-img-top" style="height: 120px; object-fit: cover;"
216
- alt="Preview" loading="lazy">
217
- {% endif %}
200
+ {% assign fpost_img = fpost.preview | default: site.teaser %}
201
+ {% include components/preview-image.html src=fpost_img alt=fpost.title class="card-img-top" style="height: 120px; object-fit: cover;" %}
218
202
  </a>
219
203
  <div class="card-body py-3">
220
204
  <h6 class="card-title mb-1">
@@ -254,15 +238,8 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
254
238
  <div class="col-md-6 col-lg-3">
255
239
  <div class="card h-100 border-0 shadow-sm hover-lift">
256
240
  <a href="{{ cpost.url | relative_url }}">
257
- {% if cpost.preview %}
258
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ cpost.preview }}"
259
- class="card-img-top" style="height: 140px; object-fit: cover;"
260
- alt="{{ cpost.title }}" loading="lazy">
261
- {% else %}
262
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ site.teaser }}"
263
- class="card-img-top" style="height: 140px; object-fit: cover;"
264
- alt="Preview" loading="lazy">
265
- {% endif %}
241
+ {% assign cpost_img = cpost.preview | default: site.teaser %}
242
+ {% include components/preview-image.html src=cpost_img alt=cpost.title class="card-img-top" style="height: 140px; object-fit: cover;" %}
266
243
  </a>
267
244
  <div class="card-body">
268
245
  <h6 class="card-title">
@@ -305,15 +282,8 @@ source: "https://getbootstrap.com/docs/5.3/examples/blog/#"
305
282
  <div class="row g-0">
306
283
  <div class="col-4">
307
284
  <a href="{{ lpost.url | relative_url }}">
308
- {% if lpost.preview %}
309
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ lpost.preview }}"
310
- class="rounded-start h-100 w-100" style="object-fit: cover; min-height: 120px;"
311
- alt="{{ lpost.title }}" loading="lazy">
312
- {% else %}
313
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ site.teaser }}"
314
- class="rounded-start h-100 w-100" style="object-fit: cover; min-height: 120px;"
315
- alt="Preview" loading="lazy">
316
- {% endif %}
285
+ {% assign lpost_img = lpost.preview | default: site.teaser %}
286
+ {% include components/preview-image.html src=lpost_img alt=lpost.title class="rounded-start h-100 w-100" style="object-fit: cover; min-height: 120px;" %}
317
287
  </a>
318
288
  </div>
319
289
  <div class="col-8">
@@ -111,18 +111,8 @@ layout: default
111
111
  </span>
112
112
  {% endif %}
113
113
  <a href="{{ fpost.url | relative_url }}" class="text-decoration-none">
114
- {% if fpost.preview %}
115
- {% if fpost.preview contains '/assets' %}
116
- <img src="{{ site.baseurl }}{{ fpost.preview }}"
117
- class="card-img-top" alt="{{ fpost.title }}" loading="lazy">
118
- {% else %}
119
- <img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ fpost.preview }}"
120
- class="card-img-top" alt="{{ fpost.title }}" loading="lazy">
121
- {% endif %}
122
- {% else %}
123
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ site.teaser }}"
124
- class="card-img-top" alt="Preview" loading="lazy">
125
- {% endif %}
114
+ {% assign fpost_img = fpost.preview | default: site.teaser %}
115
+ {% include components/preview-image.html src=fpost_img alt=fpost.title class="card-img-top" %}
126
116
  </a>
127
117
  </div>
128
118
  <div class="card-body">
@@ -171,18 +161,8 @@ layout: default
171
161
  </span>
172
162
  {% endif %}
173
163
  <a href="{{ cpost.url | relative_url }}" class="text-decoration-none">
174
- {% if cpost.preview %}
175
- {% if cpost.preview contains '/assets' %}
176
- <img src="{{ site.baseurl }}{{ cpost.preview }}"
177
- class="card-img-top" alt="{{ cpost.title }}" loading="lazy">
178
- {% else %}
179
- <img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ cpost.preview }}"
180
- class="card-img-top" alt="{{ cpost.title }}" loading="lazy">
181
- {% endif %}
182
- {% else %}
183
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ site.teaser }}"
184
- class="card-img-top" alt="Preview" loading="lazy">
185
- {% endif %}
164
+ {% assign cpost_img = cpost.preview | default: site.teaser %}
165
+ {% include components/preview-image.html src=cpost_img alt=cpost.title class="card-img-top" %}
186
166
  </a>
187
167
  </div>
188
168
  <div class="card-body d-flex flex-column">
@@ -83,18 +83,8 @@ layout: default
83
83
  <!-- CARD IMAGE SECTION -->
84
84
  <!-- ====================== -->
85
85
  <!-- Display preview image with fallback to site teaser -->
86
- {% if post.preview %}
87
- {% if post.preview contains '/assets' %}
88
- <img src="{{ site.baseurl }}{{ post.preview }}"
89
- class="card-img-top" alt="Preview image for {{ post.title }}">
90
- {% else %}
91
- <img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ post.preview }}"
92
- class="card-img-top" alt="Preview image for {{ post.title }}">
93
- {% endif %}
94
- {% else %}
95
- <img src="{{ site.baseurl }}/{{ site.public_folder }}{{ site.teaser }}"
96
- class="card-img-top" alt="Default preview image">
97
- {% endif %}
86
+ {% assign coll_img = post.preview | default: site.teaser %}
87
+ {% include components/preview-image.html src=coll_img alt=post.title class="card-img-top" %}
98
88
 
99
89
  <!-- ====================== -->
100
90
  <!-- CARD CONTENT SECTION -->
@@ -61,7 +61,7 @@ layout: root
61
61
  <!-- MAIN CONTENT AREA -->
62
62
  <!-- ================================ -->
63
63
  <!-- Primary content section with scroll spy for table of contents navigation -->
64
- <main class="bd-main order-1" data-spy="scroll" data-bs-target="toc-content" data-offset="0">
64
+ <main class="bd-main order-1" data-bs-spy="scroll" data-bs-target="#TableOfContents" data-bs-offset="100" data-bs-smooth-scroll="true">
65
65
 
66
66
  <!-- Page introduction: title, breadcrumbs, metadata -->
67
67
  {% include content/intro.html %}