jekyll-theme-zer0 1.19.1 → 1.20.2

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +395 -0
  3. data/README.md +27 -19
  4. data/_data/authors.yml +154 -5
  5. data/_data/backlog.yml +5 -5
  6. data/_data/content_statistics.yml +273 -297
  7. data/_data/features.yml +4 -25
  8. data/_data/navigation/README.md +24 -0
  9. data/_data/navigation/about.yml +2 -0
  10. data/_data/navigation/main.yml +2 -7
  11. data/_data/roadmap.yml +86 -12
  12. data/_includes/components/author-avatar-url.html +28 -0
  13. data/_includes/components/author-bio.html +86 -0
  14. data/_includes/components/author-card.html +184 -121
  15. data/_includes/components/author-eeat.html +10 -4
  16. data/_includes/components/info-section.html +1 -1
  17. data/_includes/components/mermaid.html +0 -3
  18. data/_includes/components/post-card.html +19 -9
  19. data/_includes/content/giscus.html +3 -2
  20. data/_includes/core/footer-fabs.html +28 -0
  21. data/_includes/core/footer.html +7 -17
  22. data/_includes/core/head.html +2 -2
  23. data/_includes/navigation/breadcrumbs.html +20 -2
  24. data/_includes/navigation/local-graph.html +18 -2
  25. data/_includes/obsidian/full-graph.html +4 -6
  26. data/_layouts/article.html +44 -74
  27. data/_layouts/author.html +274 -0
  28. data/_layouts/authors.html +55 -0
  29. data/_layouts/news.html +3 -3
  30. data/_layouts/note.html +21 -6
  31. data/_layouts/notebook.html +21 -6
  32. data/_layouts/root.html +31 -17
  33. data/_layouts/section.html +3 -3
  34. data/_plugins/author_pages_generator.rb +121 -0
  35. data/_sass/components/_author.scss +219 -0
  36. data/_sass/components/_content-tables.scss +16 -1
  37. data/_sass/components/_notes-index.scss +102 -0
  38. data/_sass/components/_search-modal.scss +40 -0
  39. data/_sass/components/_ui-enhancements.scss +570 -0
  40. data/_sass/core/_docs-code-examples.scss +463 -0
  41. data/_sass/core/_docs-layout.scss +0 -453
  42. data/_sass/core/_navbar.scss +253 -0
  43. data/_sass/core/_sidebar-extras.scss +79 -0
  44. data/_sass/core/_toc.scss +87 -0
  45. data/_sass/core/_variables.scss +7 -142
  46. data/_sass/custom.scss +24 -1122
  47. data/_sass/layouts/_global-chrome.scss +59 -0
  48. data/assets/css/main.scss +19 -2
  49. data/assets/js/author-profile.js +190 -0
  50. data/assets/js/modules/navigation/navbar.js +104 -0
  51. data/assets/js/obsidian-graph.js +2 -2
  52. data/assets/js/obsidian-local-graph.js +11 -5
  53. data/assets/vendor/cytoscape/cytoscape.min.js +32 -0
  54. data/scripts/README.md +39 -0
  55. data/scripts/bin/validate +11 -1
  56. data/scripts/dev/css-diff.sh +49 -0
  57. data/scripts/dev/shot.js +37 -0
  58. data/scripts/features/generate-preview-images +110 -6
  59. data/scripts/features/pixelate-preview-images +126 -0
  60. data/scripts/features/pixelate_images.py +662 -0
  61. data/scripts/github-setup.sh +0 -0
  62. data/scripts/lib/preview_generator.py +47 -3
  63. data/scripts/pixelate-preview-images.sh +12 -0
  64. data/scripts/test/integration/auto-version +10 -8
  65. data/scripts/test/lib/run_tests.sh +2 -0
  66. data/scripts/test/lib/test_content_review.sh +205 -0
  67. data/scripts/test/lib/test_pixelate_images.sh +108 -0
  68. metadata +25 -20
  69. data/_data/hub.yml +0 -68
  70. data/_data/hub_index.yml +0 -203
  71. data/_data/navigation/hub.yml +0 -110
  72. data/assets/vendor/font-awesome/css/all.min.css +0 -9
  73. data/assets/vendor/font-awesome/webfonts/fa-brands-400.ttf +0 -0
  74. data/assets/vendor/font-awesome/webfonts/fa-brands-400.woff2 +0 -0
  75. data/assets/vendor/font-awesome/webfonts/fa-regular-400.ttf +0 -0
  76. data/assets/vendor/font-awesome/webfonts/fa-regular-400.woff2 +0 -0
  77. data/assets/vendor/font-awesome/webfonts/fa-solid-900.ttf +0 -0
  78. data/assets/vendor/font-awesome/webfonts/fa-solid-900.woff2 +0 -0
  79. data/assets/vendor/font-awesome/webfonts/fa-v4compatibility.ttf +0 -0
  80. data/assets/vendor/font-awesome/webfonts/fa-v4compatibility.woff2 +0 -0
  81. data/assets/vendor/jquery/jquery-3.7.1.min.js +0 -2
  82. data/scripts/lib/hub.rb +0 -208
  83. data/scripts/provision-org-sites.rb +0 -252
  84. data/scripts/provision-org-sites.sh +0 -23
  85. data/scripts/sync-hub-metadata.rb +0 -184
  86. data/scripts/sync-hub-metadata.sh +0 -22
@@ -466,10 +466,42 @@ class PreviewGenerator:
466
466
 
467
467
  # Rate limiter for API calls
468
468
  self.rate_limiter = RateLimiter(requests_per_minute=rate_limit)
469
-
469
+
470
+ # Author data (for per-author art-style overrides). Loaded once and read
471
+ # (never mutated) per file, so it is safe to share across worker threads.
472
+ self.authors = self._load_authors()
473
+
470
474
  # Ensure output directory exists
471
475
  if not dry_run:
472
476
  self.output_dir.mkdir(parents=True, exist_ok=True)
477
+
478
+ def _load_authors(self) -> Dict[str, Any]:
479
+ """Load _data/authors.yml so posts can override the art style per author."""
480
+ authors_file = self.project_root / '_data' / 'authors.yml'
481
+ try:
482
+ data = yaml.safe_load(authors_file.read_text(encoding='utf-8'))
483
+ return data if isinstance(data, dict) else {}
484
+ except Exception:
485
+ return {}
486
+
487
+ def author_preview_overrides(self, author_key: Optional[str]) -> Dict[str, Any]:
488
+ """Return the `preview:` override block for an author key (or {}).
489
+
490
+ A post whose `author:` references an entry in _data/authors.yml that
491
+ carries a `preview:` block gets that block's settings — they win over the
492
+ site-wide style for that post's banner (e.g. AI personas cassandra/vega).
493
+ """
494
+ # Jekyll allows `author:` as a string, a list (multi-author), or a mapping
495
+ # (jekyll-seo-tag E-E-A-T). Only a hashable string can index authors.yml;
496
+ # anything else simply has no per-author override (and must not crash the
497
+ # run — keeping this path as resilient as the rest of the file).
498
+ if not isinstance(author_key, str) or not author_key:
499
+ return {}
500
+ author = self.authors.get(author_key)
501
+ if not isinstance(author, dict):
502
+ return {}
503
+ preview = author.get('preview')
504
+ return preview if isinstance(preview, dict) else {}
473
505
 
474
506
  def debug(self, msg: str):
475
507
  """Print debug message if verbose mode is enabled."""
@@ -584,14 +616,26 @@ class PreviewGenerator:
584
616
  clean_content = re.sub(r'\n+', ' ', clean_content)
585
617
  prompt_parts.append(f"Key themes: {clean_content}")
586
618
 
619
+ # Per-author art-style override (e.g. AI personas) wins over the
620
+ # configured style for this post's banner. Computed per-call from the
621
+ # document's own front matter, so it is thread-safe under parallel workers.
622
+ effective_style = self.image_style
623
+ style_modifiers = ""
624
+ overrides = self.author_preview_overrides(content.front_matter.get('author'))
625
+ if overrides.get('style'):
626
+ effective_style = str(overrides['style']).strip()
627
+ self.debug(f"Author '{content.front_matter.get('author')}' style override applied")
628
+ if overrides.get('style_modifiers'):
629
+ style_modifiers = f" Additional style: {str(overrides['style_modifiers']).strip()}."
630
+
587
631
  # Add style instructions
588
632
  prompt_parts.extend([
589
- f"Style: {self.image_style}.",
633
+ f"Style: {effective_style}.{style_modifiers}",
590
634
  "The image should be suitable as a blog header/preview image.",
591
635
  "Clean composition, professional look, visually appealing.",
592
636
  "No text or letters in the image.",
593
637
  ])
594
-
638
+
595
639
  return ' '.join(prompt_parts)
596
640
 
597
641
  def generate_filename(self, title: str) -> str:
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+
3
+ # ============================================================================
4
+ # WRAPPER: This script forwards to scripts/features/pixelate-preview-images
5
+ #
6
+ # The canonical location is scripts/features/pixelate-preview-images. This
7
+ # wrapper exists for backward compatibility and discoverability alongside the
8
+ # other scripts/*.sh entry points.
9
+ # ============================================================================
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ exec "$SCRIPT_DIR/features/pixelate-preview-images" "$@"
@@ -21,12 +21,14 @@ log_warning() { warn "$@"; }
21
21
  log_error() { error "$@"; }
22
22
  log_test() { step "$@"; }
23
23
 
24
- # Paths under test (the workflow calls the wrapper; the implementation
25
- # lives in scripts/utils/; releases go through scripts/bin/release)
24
+ # Paths under test (commit analysis lives in scripts/utils/ with the
25
+ # analyze-commits.sh wrapper; releases go through scripts/bin/release). The CI
26
+ # release path is the release-please pipeline in .github/workflows/release.yml,
27
+ # which replaced the retired version-bump.yml.
26
28
  ANALYZE_WRAPPER="$SCRIPTS_ROOT/analyze-commits.sh"
27
29
  ANALYZE_IMPL="$SCRIPTS_ROOT/utils/analyze-commits"
28
30
  RELEASE_BIN="$SCRIPTS_ROOT/bin/release"
29
- WORKFLOW_FILE="$PROJECT_ROOT/.github/workflows/version-bump.yml"
31
+ WORKFLOW_FILE="$PROJECT_ROOT/.github/workflows/release.yml"
30
32
 
31
33
  # Test counter
32
34
  TESTS_RUN=0
@@ -114,14 +116,14 @@ test_release_command() {
114
116
  test_workflow_syntax() {
115
117
  log_info "Testing GitHub Actions workflow syntax..."
116
118
 
117
- run_test "version-bump workflow exists" "test -f '$WORKFLOW_FILE'"
119
+ run_test "release workflow exists" "test -f '$WORKFLOW_FILE'"
118
120
 
119
121
  # Check if yamllint is available
120
122
  if command -v yamllint >/dev/null 2>&1; then
121
- run_test "version-bump workflow syntax" "yamllint -c '$PROJECT_ROOT/.github/config/.yamllint.yml' '$WORKFLOW_FILE'"
123
+ run_test "release workflow syntax" "yamllint -c '$PROJECT_ROOT/.github/config/.yamllint.yml' '$WORKFLOW_FILE'"
122
124
  else
123
125
  # Basic YAML syntax check with Ruby (always present for this repo)
124
- run_test "version-bump workflow syntax" "ruby -ryaml -e 'YAML.safe_load_file(\"$WORKFLOW_FILE\", aliases: true)'"
126
+ run_test "release workflow syntax" "ruby -ryaml -e 'YAML.safe_load_file(\"$WORKFLOW_FILE\", aliases: true)'"
125
127
  fi
126
128
  }
127
129
 
@@ -129,7 +131,7 @@ test_workflow_syntax() {
129
131
  test_integration() {
130
132
  log_info "Testing integration between components..."
131
133
 
132
- # The workflow invokes ./scripts/analyze-commits.sh and validates its output
134
+ # Validate that the commit-analysis wrapper returns a usable bump type
133
135
  if git rev-list --count HEAD >/dev/null 2>&1; then
134
136
  local commit_count=$(git rev-list --count HEAD)
135
137
  if [[ $commit_count -gt 0 ]]; then
@@ -195,7 +197,7 @@ DESCRIPTION:
195
197
  - Commit analysis functionality (scripts/analyze-commits.sh wrapper
196
198
  and scripts/utils/analyze-commits implementation)
197
199
  - Release command compatibility (scripts/bin/release)
198
- - Workflow file syntax validation (.github/workflows/version-bump.yml)
200
+ - Workflow file syntax validation (.github/workflows/release.yml)
199
201
  - Integration between components
200
202
  - Conventional commit detection
201
203
 
@@ -129,6 +129,8 @@ main() {
129
129
  source "$TEST_DIR/test_analyze_commits.sh"
130
130
  source "$TEST_DIR/test_locale_independence.sh"
131
131
  source "$TEST_DIR/test_migrate.sh"
132
+ source "$TEST_DIR/test_pixelate_images.sh"
133
+ source "$TEST_DIR/test_content_review.sh"
132
134
 
133
135
  # Summary
134
136
  echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
@@ -0,0 +1,205 @@
1
+ #!/bin/bash
2
+
3
+ # Unit tests for the scripts/content-review.rb scoring engine (issue #166).
4
+ #
5
+ # content-review.rb is the deterministic (no-API) tier of the AI content
6
+ # reviewer. It had zero direct unit coverage — it only ran as a side effect of
7
+ # the ai-content-review workflow on PRs touching pages/**. A scoring regression
8
+ # (code-fence false-positive) shipped in v1.18 and was fixed in v1.18.1 (PR
9
+ # #155), which is exactly the class of bug a unit test catches.
10
+ #
11
+ # These tests drive the real production config + schema
12
+ # (.github/config/content_review.yml, frontmatter_schema.yml) against synthetic
13
+ # Markdown fixtures, so they exercise the actual thresholds rather than a copy
14
+ # that can drift. Fixtures live under a temp `pages/_docs/**` tree so the
15
+ # production scope/path-pattern globs match without polluting the content dirs.
16
+ #
17
+ # Run under LC_ALL=C LANG=C for locale-independence parity with the T-015 guard.
18
+
19
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
20
+ CR_SCRIPT="$REPO_ROOT/scripts/content-review.rb"
21
+ CR_CONFIG="$REPO_ROOT/.github/config/content_review.yml"
22
+ CR_SCHEMA="$REPO_ROOT/.github/config/frontmatter_schema.yml"
23
+
24
+ echo "Testing content-review.rb scoring engine..."
25
+
26
+ # --- Fixture workspace -------------------------------------------------------
27
+ CR_WORK="$(mktemp -d)"
28
+ mkdir -p "$CR_WORK/pages/_docs/test"
29
+
30
+ # A well-formed docs page: complete front matter, in-range title/description,
31
+ # a keyword list, an H2, a language-tagged code fence, and an image with alt
32
+ # text. Should score high (no errors; at most cosmetic info items).
33
+ cat > "$CR_WORK/pages/_docs/test/good.md" <<'EOF'
34
+ ---
35
+ title: Getting Started with the Zer0 Mistakes Theme
36
+ description: A friendly, complete walkthrough that takes you from an empty repository all the way to a deployed Zer0 Mistakes site in roughly ten short minutes.
37
+ lastmod: 2026-06-23T00:00:00.000Z
38
+ layout: default
39
+ categories:
40
+ - docs
41
+ tags:
42
+ - jekyll
43
+ - setup
44
+ keywords:
45
+ - jekyll
46
+ - theme
47
+ - setup
48
+ ---
49
+
50
+ ## Overview
51
+
52
+ This guide walks you through installing the theme, configuring your site, and
53
+ publishing it to GitHub Pages. Each step is short and copy-pasteable so you can
54
+ get a working site quickly without guessing at the details. We cover the local
55
+ development loop first, then the production deployment, and finally a few
56
+ verification checks so you know everything is wired up correctly before you
57
+ share the link with anyone else who might be reviewing your brand new site.
58
+
59
+ ## Install
60
+
61
+ Run the bundled installer and start the development server with these commands:
62
+
63
+ ```bash
64
+ bundle install
65
+ bundle exec jekyll serve
66
+ ```
67
+
68
+ ![Screenshot of the running theme](/assets/images/example.png)
69
+ EOF
70
+
71
+ # Same page with the required `description` removed: one frontmatter error
72
+ # (required field) plus one SEO warning (no meta description). Must score
73
+ # strictly lower than the well-formed page.
74
+ cat > "$CR_WORK/pages/_docs/test/missing-desc.md" <<'EOF'
75
+ ---
76
+ title: Getting Started with the Zer0 Mistakes Theme
77
+ lastmod: 2026-06-23T00:00:00.000Z
78
+ layout: default
79
+ categories:
80
+ - docs
81
+ tags:
82
+ - jekyll
83
+ - setup
84
+ keywords:
85
+ - jekyll
86
+ - theme
87
+ - setup
88
+ ---
89
+
90
+ ## Overview
91
+
92
+ This guide walks you through installing the theme, configuring your site, and
93
+ publishing it to GitHub Pages. Each step is short and copy-pasteable so you can
94
+ get a working site quickly without guessing at the details. We cover the local
95
+ development loop first, then the production deployment, and finally a few
96
+ verification checks so you know everything is wired up correctly before you
97
+ share the link with anyone else.
98
+ EOF
99
+
100
+ # A page whose only code fence is properly language-tagged. The closing bare
101
+ # ``` must NOT be flagged as a fence without a language (the v1.18.1 regression).
102
+ cat > "$CR_WORK/pages/_docs/test/codefence.md" <<'EOF'
103
+ ---
104
+ title: Configuring the Zer0 Mistakes Theme Options
105
+ description: A focused reference covering the handful of configuration options you will set most often when tailoring the Zer0 Mistakes theme to your own project.
106
+ lastmod: 2026-06-23T00:00:00.000Z
107
+ layout: default
108
+ categories:
109
+ - docs
110
+ tags:
111
+ - jekyll
112
+ - config
113
+ keywords:
114
+ - jekyll
115
+ - config
116
+ - options
117
+ ---
118
+
119
+ ## Configuration
120
+
121
+ Set the options you need in your config file, then restart the server so the
122
+ changes take effect. The example below shows the most common starting point for
123
+ a new site and is safe to copy verbatim into your own configuration before you
124
+ begin customizing anything else about the project.
125
+
126
+ ```yaml
127
+ title: My Site
128
+ description: A site built with the theme
129
+ ```
130
+ EOF
131
+
132
+ # A clearly failing page: missing description, layout, categories, and tags
133
+ # (four required-field errors) — well below the fail threshold.
134
+ cat > "$CR_WORK/pages/_docs/test/failing.md" <<'EOF'
135
+ ---
136
+ title: Bad
137
+ lastmod: 2026-06-23T00:00:00.000Z
138
+ ---
139
+
140
+ Too short.
141
+ EOF
142
+
143
+ # --- Helpers -----------------------------------------------------------------
144
+ # Run the reviewer against one fixture (path relative to CR_WORK), writing JSON.
145
+ # Echoes nothing; returns the script's exit code.
146
+ cr_run() { # $1 = relative md path, $2 = json out path, $3..= extra args
147
+ local rel="$1" out="$2"; shift 2
148
+ ( cd "$CR_WORK" && LC_ALL=C LANG=C ruby "$CR_SCRIPT" \
149
+ --files "$rel" --config "$CR_CONFIG" --schema "$CR_SCHEMA" \
150
+ --json "$out" --quiet "$@" >/dev/null 2>&1 )
151
+ }
152
+
153
+ cr_score() { # $1 = json path -> prints first file's score
154
+ ruby -E UTF-8 -rjson -e 'puts JSON.parse(File.read(ARGV[0]))["files"][0]["score"]' "$1"
155
+ }
156
+
157
+ cr_collection() { # $1 = json path -> prints detected collection
158
+ ruby -E UTF-8 -rjson -e 'puts JSON.parse(File.read(ARGV[0]))["files"][0]["collection"]' "$1"
159
+ }
160
+
161
+ cr_has_message() { # $1 = json path, $2 = substring -> exit 0 if any issue contains it
162
+ ruby -E UTF-8 -rjson -e 'd=JSON.parse(File.read(ARGV[0]))["files"][0]; exit(d["issues"].any?{|i| i["message"].include?(ARGV[1])} ? 0 : 1)' "$1" "$2"
163
+ }
164
+
165
+ # --- Run + assert ------------------------------------------------------------
166
+ cr_run "pages/_docs/test/good.md" "$CR_WORK/good.json"
167
+ cr_run "pages/_docs/test/missing-desc.md" "$CR_WORK/missing.json"
168
+ cr_run "pages/_docs/test/codefence.md" "$CR_WORK/codefence.json"
169
+
170
+ GOOD_SCORE="$(cr_score "$CR_WORK/good.json")"
171
+ MISSING_SCORE="$(cr_score "$CR_WORK/missing.json")"
172
+
173
+ assert_equals "docs" "$(cr_collection "$CR_WORK/good.json")" \
174
+ "fixture under pages/_docs/** is detected as the 'docs' collection"
175
+
176
+ assert_true "[ '${GOOD_SCORE:-0}' -ge 80 ]" \
177
+ "a well-formed docs page scores >= 80 (got ${GOOD_SCORE})"
178
+
179
+ assert_true "[ '${MISSING_SCORE:-100}' -lt '${GOOD_SCORE:-0}' ]" \
180
+ "removing the required description lowers the score (${MISSING_SCORE} < ${GOOD_SCORE})"
181
+
182
+ assert_true "cr_has_message '$CR_WORK/missing.json' 'description'" \
183
+ "the missing-description page reports a description issue"
184
+
185
+ assert_false "cr_has_message '$CR_WORK/codefence.json' 'Code fence without a language'" \
186
+ "a closing bare \`\`\` after a tagged fence is not flagged (v1.18.1 regression guard)"
187
+
188
+ # --strict exit behaviour: a failing page exits 0 in warn mode, non-zero strict.
189
+ cr_run "pages/_docs/test/failing.md" "$CR_WORK/failing.json"
190
+ FAILING_SCORE="$(cr_score "$CR_WORK/failing.json")"
191
+ assert_true "[ '${FAILING_SCORE:-100}' -lt 70 ]" \
192
+ "the failing fixture scores below the 70 fail threshold (got ${FAILING_SCORE})"
193
+
194
+ cr_run "pages/_docs/test/failing.md" "$CR_WORK/failing-warn.json"
195
+ assert_equals "0" "$?" \
196
+ "without --strict, a failing page still exits 0 (advisory mode)"
197
+
198
+ cr_run "pages/_docs/test/failing.md" "$CR_WORK/failing-strict.json" --strict
199
+ assert_equals "1" "$?" \
200
+ "with --strict, a failing page exits non-zero (1)"
201
+
202
+ # --- Cleanup -----------------------------------------------------------------
203
+ rm -rf "$CR_WORK"
204
+
205
+ echo -e "\n${GREEN}content-review.rb scoring tests complete${NC}"
@@ -0,0 +1,108 @@
1
+ #!/bin/bash
2
+
3
+ # Unit tests for scripts/features/pixelate_images.py (preview-image pixelator).
4
+ #
5
+ # These drive the dependency-free Python engine as a subprocess and assert on
6
+ # its behaviour: the internal self-test, a real PNG round-trip that shrinks the
7
+ # file, dry-run leaving files untouched, and graceful handling of non-PNG input.
8
+ #
9
+ # Sourced by scripts/test/lib/run_tests.sh (uses its exported assert helpers),
10
+ # but also runnable directly for local iteration.
11
+
12
+ TEST_SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ REPO_ROOT="$(cd "$TEST_SELF_DIR/../../.." && pwd)"
14
+ ENGINE="$REPO_ROOT/scripts/features/pixelate_images.py"
15
+
16
+ # Fallback helpers so the file also runs standalone (when not sourced by the
17
+ # library runner that exports these).
18
+ if ! declare -F assert_true >/dev/null 2>&1; then
19
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
20
+ TESTS_RUN=0; TESTS_PASSED=0; TESTS_FAILED=0; declare -a FAILED_TESTS=()
21
+ assert_true() { ((TESTS_RUN++)); if eval "$1"; then ((TESTS_PASSED++)); echo -e "${GREEN}✓${NC} $2"; else ((TESTS_FAILED++)); echo -e "${RED}✗${NC} $2"; FAILED_TESTS+=("$2"); fi; }
22
+ assert_equals() { ((TESTS_RUN++)); if [[ "$1" == "$2" ]]; then ((TESTS_PASSED++)); echo -e "${GREEN}✓${NC} $3"; else ((TESTS_FAILED++)); echo -e "${RED}✗${NC} $3 (expected '$1' got '$2')"; FAILED_TESTS+=("$3"); fi; }
23
+ print_suite_header() { echo -e "\n${BLUE}=== $1 ===${NC}"; }
24
+ STANDALONE=true
25
+ fi
26
+
27
+ set +e
28
+
29
+ print_suite_header "pixelate_images.py"
30
+
31
+ if ! command -v python3 >/dev/null 2>&1; then
32
+ echo -e "${YELLOW}⚠ python3 not available — skipping pixelate_images tests${NC}"
33
+ else
34
+ # ---- Test: internal self-test ----------------------------------------
35
+ if python3 "$ENGINE" --selftest >/dev/null 2>&1; then
36
+ assert_true "true" "engine --selftest passes"
37
+ else
38
+ assert_true "false" "engine --selftest passes"
39
+ fi
40
+
41
+ # ---- Set up a temp workspace with a synthetic gradient PNG -----------
42
+ PX_TMP="$(mktemp -d)"
43
+ trap '[[ -n "${PX_TMP:-}" ]] && rm -rf "$PX_TMP"' EXIT
44
+
45
+ # Build the input PNG independently of the engine's encoder so the
46
+ # round-trip genuinely exercises decode + re-encode.
47
+ python3 - "$PX_TMP/in.png" <<'PY'
48
+ import sys, struct, zlib
49
+ path = sys.argv[1]
50
+ w = h = 256
51
+ body = bytearray()
52
+ for y in range(h):
53
+ body.append(0) # filter: none
54
+ for x in range(w):
55
+ body += bytes(((x + y) % 256, (x * 2) % 256, (y * 2) % 256))
56
+ def chunk(t, d):
57
+ return struct.pack(">I", len(d)) + t + d + struct.pack(">I", zlib.crc32(t + d) & 0xffffffff)
58
+ png = (b"\x89PNG\r\n\x1a\n"
59
+ + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0))
60
+ + chunk(b"IDAT", zlib.compress(bytes(body), 9))
61
+ + chunk(b"IEND", b""))
62
+ open(path, "wb").write(png)
63
+ PY
64
+ assert_true "[[ -s '$PX_TMP/in.png' ]]" "synthetic input PNG created"
65
+ ORIG_SIZE=$(wc -c < "$PX_TMP/in.png" | tr -d ' ')
66
+
67
+ # ---- Test: dry-run does not modify the input -------------------------
68
+ BEFORE=$(wc -c < "$PX_TMP/in.png" | tr -d ' ')
69
+ python3 "$ENGINE" --dry-run "$PX_TMP/in.png" >/dev/null 2>&1
70
+ AFTER=$(wc -c < "$PX_TMP/in.png" | tr -d ' ')
71
+ assert_equals "$BEFORE" "$AFTER" "dry-run leaves the file unchanged"
72
+
73
+ # ---- Test: real run writes a smaller, valid indexed PNG --------------
74
+ python3 "$ENGINE" --colors 64 --block 4 --output-dir "$PX_TMP/out" "$PX_TMP/in.png" >/dev/null 2>&1
75
+ OUT="$PX_TMP/out/in.png"
76
+ assert_true "[[ -s '$OUT' ]]" "optimized output written"
77
+ NEW_SIZE=$(wc -c < "$OUT" 2>/dev/null | tr -d ' ')
78
+ assert_true "[[ '${NEW_SIZE:-0}' -lt '$ORIG_SIZE' ]]" "output is smaller than original ($ORIG_SIZE -> ${NEW_SIZE:-0} bytes)"
79
+
80
+ # Output must be a valid PNG-8 (colour type 3) that decodes back.
81
+ if python3 - "$ENGINE" "$OUT" <<'PY'
82
+ import sys, importlib.util
83
+ spec = importlib.util.spec_from_file_location("pixelate_images", sys.argv[1])
84
+ P = importlib.util.module_from_spec(spec); spec.loader.exec_module(P)
85
+ img = P.decode_png(open(sys.argv[2], "rb").read())
86
+ assert img.width == 64 and img.height == 64, (img.width, img.height)
87
+ PY
88
+ then
89
+ assert_true "true" "output decodes back to expected 64x64 dimensions"
90
+ else
91
+ assert_true "false" "output decodes back to expected 64x64 dimensions"
92
+ fi
93
+
94
+ # ---- Test: non-PNG input is skipped gracefully (no crash) ------------
95
+ printf 'not a png at all' > "$PX_TMP/fake.png"
96
+ python3 "$ENGINE" --dry-run "$PX_TMP/fake.png" >/dev/null 2>&1
97
+ assert_equals "0" "$?" "non-PNG input is handled gracefully (exit 0)"
98
+
99
+ rm -rf "$PX_TMP"; PX_TMP=""
100
+ fi
101
+
102
+ # When run standalone, print a summary and exit with the right code.
103
+ if [[ "${STANDALONE:-false}" == "true" ]]; then
104
+ echo ""
105
+ echo -e "Total: $TESTS_RUN ${GREEN}Passed: $TESTS_PASSED${NC} ${RED}Failed: $TESTS_FAILED${NC}"
106
+ [[ $TESTS_FAILED -eq 0 ]]
107
+ exit $?
108
+ fi
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-theme-zer0
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.19.1
4
+ version: 1.20.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amr Abdel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-16 00:00:00.000000000 Z
11
+ date: 2026-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -86,15 +86,12 @@ files:
86
86
  - _data/generate_statistics.sh
87
87
  - _data/github-actions-example.yml
88
88
  - _data/glossary.yml
89
- - _data/hub.yml
90
- - _data/hub_index.yml
91
89
  - _data/landing.yml
92
90
  - _data/navigation/README.md
93
91
  - _data/navigation/about.yml
94
92
  - _data/navigation/admin.yml
95
93
  - _data/navigation/docs.yml
96
94
  - _data/navigation/home.yml
97
- - _data/navigation/hub.yml
98
95
  - _data/navigation/main.yml
99
96
  - _data/navigation/posts.yml
100
97
  - _data/navigation/quickstart.yml
@@ -117,6 +114,8 @@ files:
117
114
  - _includes/components/admin-tabs.html
118
115
  - _includes/components/ai-chat.html
119
116
  - _includes/components/analytics-dashboard.html
117
+ - _includes/components/author-avatar-url.html
118
+ - _includes/components/author-bio.html
120
119
  - _includes/components/author-card.html
121
120
  - _includes/components/author-eeat.html
122
121
  - _includes/components/background-customizer.html
@@ -170,6 +169,7 @@ files:
170
169
  - _includes/content/toc.html
171
170
  - _includes/content/transclude.html
172
171
  - _includes/core/branding.html
172
+ - _includes/core/footer-fabs.html
173
173
  - _includes/core/footer.html
174
174
  - _includes/core/head.html
175
175
  - _includes/core/header.html
@@ -205,6 +205,8 @@ files:
205
205
  - _layouts/README.md
206
206
  - _layouts/admin.html
207
207
  - _layouts/article.html
208
+ - _layouts/author.html
209
+ - _layouts/authors.html
208
210
  - _layouts/collection.html
209
211
  - _layouts/default.html
210
212
  - _layouts/home.html
@@ -222,32 +224,41 @@ files:
222
224
  - _layouts/tag.html
223
225
  - _layouts/welcome.html
224
226
  - _plugins/admin_page_urls.rb
227
+ - _plugins/author_pages_generator.rb
225
228
  - _plugins/content_statistics_generator.rb
226
229
  - _plugins/obsidian_links.rb
227
230
  - _plugins/preview_image_generator.rb
228
231
  - _plugins/sanitize_config_filter.rb
229
232
  - _plugins/search_and_sitemap_generator.rb
230
233
  - _plugins/theme_version.rb
234
+ - _sass/components/_author.scss
231
235
  - _sass/components/_back-to-top.scss
232
236
  - _sass/components/_callout.scss
233
237
  - _sass/components/_content-tables.scss
234
238
  - _sass/components/_cookie-banner.scss
235
239
  - _sass/components/_footer.scss
240
+ - _sass/components/_notes-index.scss
236
241
  - _sass/components/_notes.scss
237
242
  - _sass/components/_post-navigation.scss
243
+ - _sass/components/_search-modal.scss
238
244
  - _sass/components/_skeleton.scss
239
245
  - _sass/components/_theme-preview.scss
246
+ - _sass/components/_ui-enhancements.scss
247
+ - _sass/core/_docs-code-examples.scss
240
248
  - _sass/core/_docs-layout.scss
241
249
  - _sass/core/_nav-tree.scss
242
250
  - _sass/core/_navbar.scss
243
251
  - _sass/core/_obsidian.scss
244
252
  - _sass/core/_offcanvas-panels.scss
245
253
  - _sass/core/_sidebar-categories.scss
254
+ - _sass/core/_sidebar-extras.scss
246
255
  - _sass/core/_syntax.scss
247
256
  - _sass/core/_theme.scss
257
+ - _sass/core/_toc.scss
248
258
  - _sass/core/_variables.scss
249
259
  - _sass/core/code-copy.scss
250
260
  - _sass/custom.scss
261
+ - _sass/layouts/_global-chrome.scss
251
262
  - _sass/layouts/_landing.scss
252
263
  - _sass/layouts/_navbar-extras.scss
253
264
  - _sass/layouts/_section.scss
@@ -278,6 +289,7 @@ files:
278
289
  - assets/data/notebooks/weather_data.csv
279
290
  - assets/data/wiki-index.json
280
291
  - assets/js/ai-chat.js
292
+ - assets/js/author-profile.js
281
293
  - assets/js/auto-hide-nav.js
282
294
  - assets/js/back-to-top.js
283
295
  - assets/js/background-customizer.js
@@ -327,18 +339,9 @@ files:
327
339
  - assets/vendor/bootstrap-icons/font/fonts/bootstrap-icons.woff2
328
340
  - assets/vendor/bootstrap/css/bootstrap.min.css
329
341
  - assets/vendor/bootstrap/js/bootstrap.bundle.min.js
330
- - assets/vendor/font-awesome/css/all.min.css
331
- - assets/vendor/font-awesome/webfonts/fa-brands-400.ttf
332
- - assets/vendor/font-awesome/webfonts/fa-brands-400.woff2
333
- - assets/vendor/font-awesome/webfonts/fa-regular-400.ttf
334
- - assets/vendor/font-awesome/webfonts/fa-regular-400.woff2
335
- - assets/vendor/font-awesome/webfonts/fa-solid-900.ttf
336
- - assets/vendor/font-awesome/webfonts/fa-solid-900.woff2
337
- - assets/vendor/font-awesome/webfonts/fa-v4compatibility.ttf
338
- - assets/vendor/font-awesome/webfonts/fa-v4compatibility.woff2
342
+ - assets/vendor/cytoscape/cytoscape.min.js
339
343
  - assets/vendor/github-calendar/github-calendar-responsive.css
340
344
  - assets/vendor/github-calendar/github-calendar.min.js
341
- - assets/vendor/jquery/jquery-3.7.1.min.js
342
345
  - assets/vendor/mathjax/es5/adaptors/liteDOM.js
343
346
  - assets/vendor/mathjax/es5/core.js
344
347
  - assets/vendor/mathjax/es5/output/chtml/fonts/woff-v2/MathJax_AMS-Regular.woff
@@ -380,6 +383,8 @@ files:
380
383
  - scripts/build
381
384
  - scripts/content-review.rb
382
385
  - scripts/convert-notebooks.sh
386
+ - scripts/dev/css-diff.sh
387
+ - scripts/dev/shot.js
383
388
  - scripts/docker-publish
384
389
  - scripts/docs/check-freshness.sh
385
390
  - scripts/docs/check-links.sh
@@ -388,6 +393,8 @@ files:
388
393
  - scripts/example-usage.sh
389
394
  - scripts/features/generate-preview-images
390
395
  - scripts/features/install-preview-generator
396
+ - scripts/features/pixelate-preview-images
397
+ - scripts/features/pixelate_images.py
391
398
  - scripts/features/validate_preview_urls.py
392
399
  - scripts/fix-markdown-format.sh
393
400
  - scripts/fork-cleanup.sh
@@ -446,7 +453,6 @@ files:
446
453
  - scripts/lib/frontmatter.sh
447
454
  - scripts/lib/gem.sh
448
455
  - scripts/lib/git.sh
449
- - scripts/lib/hub.rb
450
456
  - scripts/lib/install/README.md
451
457
  - scripts/lib/install/agents.sh
452
458
  - scripts/lib/install/ai/diagnose.sh
@@ -476,18 +482,15 @@ files:
476
482
  - scripts/lint-pages
477
483
  - scripts/migrate-nav-modes.sh
478
484
  - scripts/migrate.sh
485
+ - scripts/pixelate-preview-images.sh
479
486
  - scripts/platform/setup-linux.sh
480
487
  - scripts/platform/setup-macos.sh
481
488
  - scripts/platform/setup-wsl.sh
482
489
  - scripts/post-template-setup.sh
483
- - scripts/provision-org-sites.rb
484
- - scripts/provision-org-sites.sh
485
490
  - scripts/release
486
491
  - scripts/setup.sh
487
492
  - scripts/sync-backlog.rb
488
493
  - scripts/sync-backlog.sh
489
- - scripts/sync-hub-metadata.rb
490
- - scripts/sync-hub-metadata.sh
491
494
  - scripts/test-auto-version.sh
492
495
  - scripts/test-mermaid.sh
493
496
  - scripts/test-notebook-conversion.sh
@@ -497,10 +500,12 @@ files:
497
500
  - scripts/test/lib/run_tests.sh
498
501
  - scripts/test/lib/test_analyze_commits.sh
499
502
  - scripts/test/lib/test_changelog.sh
503
+ - scripts/test/lib/test_content_review.sh
500
504
  - scripts/test/lib/test_gem.sh
501
505
  - scripts/test/lib/test_git.sh
502
506
  - scripts/test/lib/test_locale_independence.sh
503
507
  - scripts/test/lib/test_migrate.sh
508
+ - scripts/test/lib/test_pixelate_images.sh
504
509
  - scripts/test/lib/test_validation.sh
505
510
  - scripts/test/lib/test_version.sh
506
511
  - scripts/test/theme/validate