jekyll-theme-zer0 0.22.0 → 0.22.19

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +236 -0
  3. data/README.md +66 -19
  4. data/_data/navigation/admin.yml +53 -0
  5. data/_data/theme_backgrounds.yml +121 -0
  6. data/_includes/components/admin-tabs.html +59 -0
  7. data/_includes/components/analytics-dashboard.html +232 -0
  8. data/_includes/components/background-customizer.html +159 -0
  9. data/_includes/components/background-settings.html +137 -0
  10. data/_includes/components/collection-manager.html +151 -0
  11. data/_includes/components/component-showcase.html +452 -0
  12. data/_includes/components/config-editor.html +207 -0
  13. data/_includes/components/config-viewer.html +479 -0
  14. data/_includes/components/env-dashboard.html +154 -0
  15. data/_includes/components/feature-card.html +94 -0
  16. data/_includes/components/info-section.html +172 -149
  17. data/_includes/components/js-cdn.html +4 -1
  18. data/_includes/components/nav-editor.html +99 -0
  19. data/_includes/components/setup-banner.html +28 -0
  20. data/_includes/components/setup-check.html +53 -0
  21. data/_includes/components/svg-background.html +42 -0
  22. data/_includes/components/theme-customizer.html +46 -0
  23. data/_includes/content/seo.html +68 -135
  24. data/_includes/core/footer.html +1 -1
  25. data/_includes/core/head.html +3 -2
  26. data/_includes/core/header.html +14 -7
  27. data/_includes/landing/landing-install-cards.html +18 -7
  28. data/_includes/navigation/admin-nav.html +95 -0
  29. data/_includes/navigation/navbar.html +43 -5
  30. data/_includes/navigation/sidebar-left.html +1 -1
  31. data/_includes/setup/wizard.html +330 -0
  32. data/_layouts/admin.html +166 -0
  33. data/_layouts/landing.html +23 -9
  34. data/_layouts/root.html +12 -6
  35. data/_layouts/setup.html +73 -0
  36. data/_plugins/preview_image_generator.rb +26 -12
  37. data/_sass/core/_navbar.scss +2 -2
  38. data/_sass/custom.scss +28 -6
  39. data/_sass/theme/_background-mixins.scss +95 -0
  40. data/_sass/theme/_backgrounds.scss +156 -0
  41. data/_sass/theme/_color-modes.scss +2 -1
  42. data/assets/backgrounds/gradients/air.svg +15 -0
  43. data/assets/backgrounds/gradients/aqua.svg +15 -0
  44. data/assets/backgrounds/gradients/contrast.svg +15 -0
  45. data/assets/backgrounds/gradients/dark.svg +15 -0
  46. data/assets/backgrounds/gradients/dirt.svg +15 -0
  47. data/assets/backgrounds/gradients/mint.svg +15 -0
  48. data/assets/backgrounds/gradients/neon.svg +15 -0
  49. data/assets/backgrounds/gradients/plum.svg +15 -0
  50. data/assets/backgrounds/gradients/sunrise.svg +15 -0
  51. data/assets/backgrounds/noise/air.svg +8 -0
  52. data/assets/backgrounds/noise/aqua.svg +8 -0
  53. data/assets/backgrounds/noise/contrast.svg +8 -0
  54. data/assets/backgrounds/noise/dark.svg +8 -0
  55. data/assets/backgrounds/noise/dirt.svg +8 -0
  56. data/assets/backgrounds/noise/mint.svg +8 -0
  57. data/assets/backgrounds/noise/neon.svg +8 -0
  58. data/assets/backgrounds/noise/plum.svg +8 -0
  59. data/assets/backgrounds/noise/sunrise.svg +8 -0
  60. data/assets/backgrounds/patterns/air.svg +7 -0
  61. data/assets/backgrounds/patterns/aqua.svg +7 -0
  62. data/assets/backgrounds/patterns/contrast.svg +4 -0
  63. data/assets/backgrounds/patterns/dark.svg +5 -0
  64. data/assets/backgrounds/patterns/dirt.svg +5 -0
  65. data/assets/backgrounds/patterns/mint.svg +6 -0
  66. data/assets/backgrounds/patterns/neon.svg +6 -0
  67. data/assets/backgrounds/patterns/plum.svg +6 -0
  68. data/assets/backgrounds/patterns/sunrise.svg +5 -0
  69. data/assets/js/background-customizer.js +73 -0
  70. data/assets/js/code-copy.js +18 -47
  71. data/assets/js/config-utility.js +307 -0
  72. data/assets/js/nav-editor.js +39 -0
  73. data/assets/js/palette-generator.js +415 -0
  74. data/assets/js/search-modal.js +31 -11
  75. data/assets/js/setup-wizard.js +306 -0
  76. data/assets/js/skin-editor.js +645 -0
  77. data/assets/js/theme-customizer.js +102 -0
  78. data/assets/js/ui-enhancements.js +15 -24
  79. data/assets/vendor/bootstrap/css/bootstrap.min.css +1 -0
  80. data/assets/vendor/bootstrap/js/bootstrap.bundle.min.js +1 -0
  81. data/scripts/README.md +45 -0
  82. data/scripts/features/generate-preview-images +297 -7
  83. data/scripts/features/install-preview-generator +51 -33
  84. data/scripts/fork-cleanup.sh +92 -19
  85. data/scripts/github-setup.sh +284 -0
  86. data/scripts/init_setup.sh +0 -1
  87. data/scripts/lib/frontmatter.sh +543 -0
  88. data/scripts/lib/migrate.sh +265 -0
  89. data/scripts/lib/preview_generator.py +607 -32
  90. data/scripts/lint-pages +505 -0
  91. data/scripts/migrate.sh +201 -0
  92. data/scripts/platform/setup-linux.sh +244 -0
  93. data/scripts/platform/setup-macos.sh +187 -0
  94. data/scripts/platform/setup-wsl.sh +196 -0
  95. metadata +71 -6
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
2
+ <filter id="n1">
3
+ <feTurbulence type="fractalNoise" baseFrequency="0.6" numOctaves="4" stitchTiles="stitch"/>
4
+ <feColorMatrix type="saturate" values="0"/>
5
+ </filter>
6
+ <rect width="100%" height="100%" fill="transparent"/>
7
+ <rect width="100%" height="100%" filter="url(#n1)" opacity="0.06"/>
8
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
2
+ <filter id="n1">
3
+ <feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/>
4
+ <feColorMatrix type="saturate" values="0"/>
5
+ </filter>
6
+ <rect width="100%" height="100%" fill="transparent"/>
7
+ <rect width="100%" height="100%" filter="url(#n1)" opacity="0.04"/>
8
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
2
+ <filter id="n1">
3
+ <feTurbulence type="fractalNoise" baseFrequency="0.6" numOctaves="4" stitchTiles="stitch"/>
4
+ <feColorMatrix type="saturate" values="0"/>
5
+ </filter>
6
+ <rect width="100%" height="100%" fill="transparent"/>
7
+ <rect width="100%" height="100%" filter="url(#n1)" opacity="0.06"/>
8
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
2
+ <filter id="n1">
3
+ <feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/>
4
+ <feColorMatrix type="saturate" values="0"/>
5
+ </filter>
6
+ <rect width="100%" height="100%" fill="transparent"/>
7
+ <rect width="100%" height="100%" filter="url(#n1)" opacity="0.04"/>
8
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
2
+ <filter id="n1">
3
+ <feTurbulence type="fractalNoise" baseFrequency="0.7" numOctaves="3" stitchTiles="stitch"/>
4
+ <feColorMatrix type="saturate" values="0"/>
5
+ </filter>
6
+ <rect width="100%" height="100%" fill="transparent"/>
7
+ <rect width="100%" height="100%" filter="url(#n1)" opacity="0.05"/>
8
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
2
+ <filter id="n1">
3
+ <feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/>
4
+ <feColorMatrix type="saturate" values="0"/>
5
+ </filter>
6
+ <rect width="100%" height="100%" fill="transparent"/>
7
+ <rect width="100%" height="100%" filter="url(#n1)" opacity="0.05"/>
8
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
2
+ <filter id="n1">
3
+ <feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/>
4
+ <feColorMatrix type="saturate" values="0"/>
5
+ </filter>
6
+ <rect width="100%" height="100%" fill="transparent"/>
7
+ <rect width="100%" height="100%" filter="url(#n1)" opacity="0.05"/>
8
+ </svg>
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60">
2
+ <circle cx="30" cy="30" r="8" fill="none" stroke="#6fa8dc" stroke-width="0.5" opacity="0.3"/>
3
+ <circle cx="0" cy="0" r="5" fill="none" stroke="#b8d4e3" stroke-width="0.5" opacity="0.2"/>
4
+ <circle cx="60" cy="60" r="5" fill="none" stroke="#b8d4e3" stroke-width="0.5" opacity="0.2"/>
5
+ <circle cx="60" cy="0" r="5" fill="none" stroke="#b8d4e3" stroke-width="0.5" opacity="0.2"/>
6
+ <circle cx="0" cy="60" r="5" fill="none" stroke="#b8d4e3" stroke-width="0.5" opacity="0.2"/>
7
+ </svg>
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60">
2
+ <circle cx="30" cy="30" r="8" fill="none" stroke="#00b4d8" stroke-width="0.5" opacity="0.3"/>
3
+ <circle cx="0" cy="0" r="5" fill="none" stroke="#90e0ef" stroke-width="0.5" opacity="0.2"/>
4
+ <circle cx="60" cy="60" r="5" fill="none" stroke="#90e0ef" stroke-width="0.5" opacity="0.2"/>
5
+ <circle cx="60" cy="0" r="5" fill="none" stroke="#90e0ef" stroke-width="0.5" opacity="0.2"/>
6
+ <circle cx="0" cy="60" r="5" fill="none" stroke="#90e0ef" stroke-width="0.5" opacity="0.2"/>
7
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
2
+ <line x1="0" y1="0" x2="40" y2="40" stroke="#ffcc00" stroke-width="0.5" opacity="0.15"/>
3
+ <line x1="40" y1="0" x2="0" y2="40" stroke="#ffffff" stroke-width="0.3" opacity="0.08"/>
4
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50">
2
+ <rect x="22" y="22" width="6" height="6" rx="1" fill="none" stroke="#0f3460" stroke-width="0.5" opacity="0.25"/>
3
+ <rect x="-3" y="-3" width="6" height="6" rx="1" fill="none" stroke="#16213e" stroke-width="0.5" opacity="0.15"/>
4
+ <rect x="47" y="47" width="6" height="6" rx="1" fill="none" stroke="#16213e" stroke-width="0.5" opacity="0.15"/>
5
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50">
2
+ <polygon points="25,10 35,35 15,35" fill="none" stroke="#d4a574" stroke-width="0.5" opacity="0.2"/>
3
+ <circle cx="8" cy="8" r="3" fill="none" stroke="#8b6914" stroke-width="0.4" opacity="0.15"/>
4
+ <circle cx="42" cy="42" r="3" fill="none" stroke="#8b6914" stroke-width="0.4" opacity="0.15"/>
5
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50">
2
+ <circle cx="25" cy="25" r="10" fill="none" stroke="#52b788" stroke-width="0.5" opacity="0.2"/>
3
+ <circle cx="25" cy="25" r="6" fill="none" stroke="#95d5b2" stroke-width="0.4" opacity="0.15"/>
4
+ <circle cx="5" cy="5" r="3" fill="none" stroke="#2d6a4f" stroke-width="0.3" opacity="0.1"/>
5
+ <circle cx="45" cy="45" r="3" fill="none" stroke="#2d6a4f" stroke-width="0.3" opacity="0.1"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60">
2
+ <polygon points="30,5 55,50 5,50" fill="none" stroke="#ff006e" stroke-width="0.6" opacity="0.25"/>
3
+ <circle cx="30" cy="35" r="4" fill="none" stroke="#3a86ff" stroke-width="0.5" opacity="0.2"/>
4
+ <circle cx="10" cy="10" r="3" fill="none" stroke="#8338ec" stroke-width="0.5" opacity="0.15"/>
5
+ <circle cx="50" cy="10" r="3" fill="none" stroke="#8338ec" stroke-width="0.5" opacity="0.15"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60">
2
+ <circle cx="30" cy="30" r="12" fill="none" stroke="#c77dff" stroke-width="0.5" opacity="0.2"/>
3
+ <circle cx="30" cy="30" r="6" fill="none" stroke="#812f85" stroke-width="0.4" opacity="0.15"/>
4
+ <line x1="10" y1="10" x2="50" y2="50" stroke="#4a0e4e" stroke-width="0.3" opacity="0.1"/>
5
+ <line x1="50" y1="10" x2="10" y2="50" stroke="#4a0e4e" stroke-width="0.3" opacity="0.1"/>
6
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50">
2
+ <circle cx="25" cy="25" r="10" fill="none" stroke="#ff6b35" stroke-width="0.5" opacity="0.2"/>
3
+ <line x1="5" y1="25" x2="45" y2="25" stroke="#f7c59f" stroke-width="0.3" opacity="0.12"/>
4
+ <line x1="25" y1="5" x2="25" y2="45" stroke="#f7c59f" stroke-width="0.3" opacity="0.12"/>
5
+ </svg>
@@ -0,0 +1,73 @@
1
+ /**
2
+ * background-customizer.js
3
+ * Runtime switching of theme skin and fffuel-style SVG backgrounds.
4
+ *
5
+ * API:
6
+ * zer0Bg.setSkin('aqua') — change skin (persists to localStorage)
7
+ * zer0Bg.toggle() — enable / disable backgrounds
8
+ * zer0Bg.setOpacity('gradient', 0.5) — adjust a layer opacity at runtime
9
+ * zer0Bg.currentSkin() — returns current skin name
10
+ */
11
+ (() => {
12
+ "use strict";
13
+
14
+ const STORAGE_KEY_SKIN = "zer0-theme-skin";
15
+ const STORAGE_KEY_BG = "zer0-bg-enabled";
16
+
17
+ const html = document.documentElement;
18
+
19
+ /** Read stored skin or fall back to the server-rendered attribute. */
20
+ function getStoredSkin() {
21
+ return localStorage.getItem(STORAGE_KEY_SKIN) || html.getAttribute("data-theme-skin") || "dark";
22
+ }
23
+
24
+ /** Read stored background on/off preference. */
25
+ function isBgEnabled() {
26
+ const stored = localStorage.getItem(STORAGE_KEY_BG);
27
+ if (stored !== null) return stored === "true";
28
+ return html.getAttribute("data-zer0-bg") !== "false";
29
+ }
30
+
31
+ /** Apply skin to <html> and persist. */
32
+ function setSkin(name) {
33
+ html.setAttribute("data-theme-skin", name);
34
+ localStorage.setItem(STORAGE_KEY_SKIN, name);
35
+ document.dispatchEvent(new CustomEvent("zer0:skin-change", { detail: { skin: name } }));
36
+ }
37
+
38
+ /** Toggle backgrounds on/off. */
39
+ function toggle(force) {
40
+ const enabled = typeof force === "boolean" ? force : !isBgEnabled();
41
+ html.setAttribute("data-zer0-bg", enabled ? "on" : "off");
42
+ localStorage.setItem(STORAGE_KEY_BG, String(enabled));
43
+ document.dispatchEvent(new CustomEvent("zer0:bg-toggle", { detail: { enabled } }));
44
+ }
45
+
46
+ /** Adjust a specific layer opacity at runtime via CSS custom property. */
47
+ function setOpacity(layer, value) {
48
+ const map = {
49
+ gradient: "--zer0-bg-gradient-opacity",
50
+ texture: "--zer0-bg-texture-opacity",
51
+ pattern: "--zer0-bg-pattern-opacity"
52
+ };
53
+ const prop = map[layer];
54
+ if (prop) html.style.setProperty(prop, value);
55
+ }
56
+
57
+ /** Return current skin name. */
58
+ function currentSkin() {
59
+ return html.getAttribute("data-theme-skin") || "dark";
60
+ }
61
+
62
+ // Restore on page load
63
+ const storedSkin = getStoredSkin();
64
+ if (storedSkin !== html.getAttribute("data-theme-skin")) {
65
+ html.setAttribute("data-theme-skin", storedSkin);
66
+ }
67
+ if (!isBgEnabled()) {
68
+ html.setAttribute("data-zer0-bg", "off");
69
+ }
70
+
71
+ // Public API
72
+ window.zer0Bg = { setSkin, toggle, setOpacity, currentSkin };
73
+ })();
@@ -20,7 +20,7 @@ document.addEventListener('DOMContentLoaded', function () {
20
20
  button.innerHTML = '<i class="bi bi-clipboard me-1"></i>' + copyText;
21
21
  button.tabIndex = 0;
22
22
 
23
- // Enhanced click handler with better feedback
23
+ // Click handler using modern Clipboard API
24
24
  button.addEventListener('click', function (e) {
25
25
  e.preventDefault();
26
26
  e.stopPropagation();
@@ -34,24 +34,23 @@ document.addEventListener('DOMContentLoaded', function () {
34
34
  .join('\n')
35
35
  .trim();
36
36
 
37
- // Use modern Clipboard API with fallback
38
- if (navigator.clipboard && navigator.clipboard.writeText) {
39
- navigator.clipboard.writeText(code).then(function() {
40
- // Success feedback
41
- button.innerHTML = '<i class="bi bi-check-circle me-1"></i>' + copiedText;
42
- button.classList.add('copied');
43
-
44
- setTimeout(function () {
45
- button.innerHTML = '<i class="bi bi-clipboard me-1"></i>' + copyText;
46
- button.classList.remove('copied');
47
- }, 2000);
48
- }).catch(function(err) {
49
- console.error('Failed to copy:', err);
50
- fallbackCopy(code, button, copyText);
51
- });
52
- } else {
53
- fallbackCopy(code, button, copyText);
54
- }
37
+ // Modern Clipboard API (supported in all current browsers)
38
+ navigator.clipboard.writeText(code).then(function() {
39
+ // Success feedback
40
+ button.innerHTML = '<i class="bi bi-check-circle me-1"></i>' + copiedText;
41
+ button.classList.add('copied');
42
+
43
+ setTimeout(function () {
44
+ button.innerHTML = '<i class="bi bi-clipboard me-1"></i>' + copyText;
45
+ button.classList.remove('copied');
46
+ }, 2000);
47
+ }).catch(function(err) {
48
+ console.error('Failed to copy:', err);
49
+ button.innerHTML = '<i class="bi bi-x-circle me-1"></i>Copy failed';
50
+ setTimeout(function () {
51
+ button.innerHTML = '<i class="bi bi-clipboard me-1"></i>' + copyText;
52
+ }, 2000);
53
+ });
55
54
  });
56
55
 
57
56
  // Ensure pre has position relative for absolute positioning
@@ -62,32 +61,4 @@ document.addEventListener('DOMContentLoaded', function () {
62
61
  preElement.appendChild(button);
63
62
  preElement.classList.add('has-copy-button');
64
63
  });
65
-
66
- // Fallback copy method for older browsers
67
- function fallbackCopy(text, button, copyText) {
68
- var textArea = document.createElement('textarea');
69
- textArea.value = text;
70
- textArea.style.position = 'fixed';
71
- textArea.style.opacity = '0';
72
- document.body.appendChild(textArea);
73
- textArea.select();
74
-
75
- try {
76
- document.execCommand('copy');
77
- button.innerHTML = '<i class="bi bi-check-circle me-1"></i>Copied!';
78
- button.classList.add('copied');
79
- setTimeout(function () {
80
- button.innerHTML = '<i class="bi bi-clipboard me-1"></i>' + copyText;
81
- button.classList.remove('copied');
82
- }, 2000);
83
- } catch (err) {
84
- console.error('Fallback copy failed:', err);
85
- button.innerHTML = '<i class="bi bi-x-circle me-1"></i>Failed';
86
- setTimeout(function () {
87
- button.innerHTML = '<i class="bi bi-clipboard me-1"></i>' + copyText;
88
- }, 2000);
89
- }
90
-
91
- document.body.removeChild(textArea);
92
- }
93
64
  });
@@ -0,0 +1,307 @@
1
+ /**
2
+ * ===================================================================
3
+ * Configuration Utility — Interactive Config Viewer & Editor
4
+ * ===================================================================
5
+ *
6
+ * Powers the /about/config/ page with:
7
+ * - Search / filter across all config keys
8
+ * - Copy individual values or entire sections
9
+ * - Live YAML preview from the editor form
10
+ * - Import / export configuration
11
+ *
12
+ * Dependencies: Bootstrap 5 (already on page)
13
+ * ===================================================================
14
+ */
15
+
16
+ (function () {
17
+ 'use strict';
18
+
19
+ /* ── helpers ──────────────────────────────────────────────────────── */
20
+
21
+ /** Safe clipboard write with visual feedback on a button. */
22
+ function copyToClipboard(text, btn) {
23
+ if (!btn) return;
24
+ var orig = btn.innerHTML;
25
+ navigator.clipboard.writeText(text).then(function () {
26
+ btn.innerHTML = '<i class="bi bi-check-lg"></i> Copied!';
27
+ btn.classList.replace('btn-outline-secondary', 'btn-success');
28
+ setTimeout(function () {
29
+ btn.innerHTML = orig;
30
+ btn.classList.replace('btn-success', 'btn-outline-secondary');
31
+ }, 1800);
32
+ }).catch(function () {
33
+ /* fallback */
34
+ var ta = document.createElement('textarea');
35
+ ta.value = text;
36
+ ta.style.cssText = 'position:fixed;opacity:0';
37
+ document.body.appendChild(ta);
38
+ ta.select();
39
+ try { document.execCommand('copy'); } catch (_) { /* noop */ }
40
+ document.body.removeChild(ta);
41
+ btn.innerHTML = '<i class="bi bi-check-lg"></i> Copied!';
42
+ setTimeout(function () { btn.innerHTML = orig; }, 1800);
43
+ });
44
+ }
45
+
46
+ /* ── search / filter ─────────────────────────────────────────────── */
47
+
48
+ function initSearch() {
49
+ var input = document.getElementById('cfg-search');
50
+ if (!input) return;
51
+
52
+ input.addEventListener('input', function () {
53
+ var q = input.value.toLowerCase().trim();
54
+ var rows = document.querySelectorAll('.cfg-row');
55
+ var sections = document.querySelectorAll('.cfg-section');
56
+
57
+ if (!q) {
58
+ rows.forEach(function (r) { r.classList.remove('d-none'); });
59
+ sections.forEach(function (s) { s.classList.remove('d-none'); });
60
+ return;
61
+ }
62
+
63
+ rows.forEach(function (r) {
64
+ var key = (r.getAttribute('data-key') || '').toLowerCase();
65
+ var val = (r.getAttribute('data-value') || '').toLowerCase();
66
+ var match = key.indexOf(q) !== -1 || val.indexOf(q) !== -1;
67
+ r.classList.toggle('d-none', !match);
68
+ });
69
+
70
+ /* hide sections that have zero visible rows */
71
+ sections.forEach(function (s) {
72
+ var visible = s.querySelectorAll('.cfg-row:not(.d-none)');
73
+ s.classList.toggle('d-none', !visible.length);
74
+ });
75
+ });
76
+
77
+ /* clear button */
78
+ var clearBtn = document.getElementById('cfg-search-clear');
79
+ if (clearBtn) {
80
+ clearBtn.addEventListener('click', function () {
81
+ input.value = '';
82
+ input.dispatchEvent(new Event('input'));
83
+ input.focus();
84
+ });
85
+ }
86
+ }
87
+
88
+ /* ── copy individual value buttons ───────────────────────────────── */
89
+
90
+ function initCopyButtons() {
91
+ document.querySelectorAll('.cfg-copy-val').forEach(function (btn) {
92
+ btn.addEventListener('click', function () {
93
+ var val = btn.getAttribute('data-value') || '';
94
+ copyToClipboard(val, btn);
95
+ });
96
+ });
97
+ }
98
+
99
+ /* ── copy section YAML ───────────────────────────────────────────── */
100
+
101
+ function initSectionCopy() {
102
+ document.querySelectorAll('.cfg-copy-section').forEach(function (btn) {
103
+ btn.addEventListener('click', function () {
104
+ var sectionId = btn.getAttribute('data-section');
105
+ var section = document.getElementById(sectionId);
106
+ if (!section) return;
107
+ var rows = section.querySelectorAll('.cfg-row');
108
+ var lines = [];
109
+ rows.forEach(function (r) {
110
+ var key = r.getAttribute('data-key') || '';
111
+ var val = r.getAttribute('data-value') || '';
112
+ lines.push(key + ': ' + val);
113
+ });
114
+ copyToClipboard(lines.join('\n'), btn);
115
+ });
116
+ });
117
+ }
118
+
119
+ /* ── copy full config ────────────────────────────────────────────── */
120
+
121
+ function initFullCopy() {
122
+ var btn = document.getElementById('cfg-copy-full');
123
+ if (!btn) return;
124
+ btn.addEventListener('click', function () {
125
+ var pre = document.getElementById('cfg-full-yaml');
126
+ if (pre) copyToClipboard(pre.textContent, btn);
127
+ });
128
+ }
129
+
130
+ /* ── expand / collapse all ───────────────────────────────────────── */
131
+
132
+ function initExpandCollapse() {
133
+ var expandBtn = document.getElementById('cfg-expand-all');
134
+ var collapseBtn = document.getElementById('cfg-collapse-all');
135
+
136
+ if (expandBtn) {
137
+ expandBtn.addEventListener('click', function () {
138
+ document.querySelectorAll('#configAccordion .accordion-collapse').forEach(function (el) {
139
+ el.classList.add('show');
140
+ });
141
+ document.querySelectorAll('#configAccordion .accordion-button').forEach(function (el) {
142
+ el.classList.remove('collapsed');
143
+ el.setAttribute('aria-expanded', 'true');
144
+ });
145
+ });
146
+ }
147
+
148
+ if (collapseBtn) {
149
+ collapseBtn.addEventListener('click', function () {
150
+ document.querySelectorAll('#configAccordion .accordion-collapse').forEach(function (el) {
151
+ el.classList.remove('show');
152
+ });
153
+ document.querySelectorAll('#configAccordion .accordion-button').forEach(function (el) {
154
+ el.classList.add('collapsed');
155
+ el.setAttribute('aria-expanded', 'false');
156
+ });
157
+ });
158
+ }
159
+ }
160
+
161
+ /* ── editor: YAML builder ────────────────────────────────────────── */
162
+
163
+ function yamlEscape(val) {
164
+ if (val === '' || val === null || val === undefined) return '""';
165
+ if (val === true || val === false) return String(val);
166
+ var s = String(val).replace(/\r\n|\r|\n/g, ' ').replace(/\s{2,}/g, ' ').trim();
167
+ if (s === '') return '""';
168
+ if (s === 'true' || s === 'false') return s;
169
+ if (/^-?[0-9]+(\.[0-9]+)?$/.test(s)) return s;
170
+ if (/[:#{}[\],&*?|>!%@`'"]/.test(s)) {
171
+ return '"' + s.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
172
+ }
173
+ return '"' + s + '"';
174
+ }
175
+
176
+ function pad(k, w) { w = w || 26; return (k + ' '.repeat(w)).slice(0, w); }
177
+
178
+ function buildEditorYAML() {
179
+ var f = {};
180
+ document.querySelectorAll('#configEditor .edit-field').forEach(function (el) {
181
+ var key = el.getAttribute('data-key');
182
+ if (!key) return;
183
+ f[key] = el.type === 'checkbox' ? el.checked : el.value.trim();
184
+ });
185
+
186
+ var lines = [];
187
+ lines.push('# ===========================================================================');
188
+ lines.push('# _config.yml — generated by Configuration Utility');
189
+ lines.push('# ===========================================================================');
190
+ lines.push('');
191
+
192
+ lines.push('# ── Site Identity ─────────────────────────────────────────────────');
193
+ lines.push(pad('title') + ': ' + yamlEscape(f.title || ''));
194
+ lines.push(pad('founder') + ': ' + yamlEscape(f.founder || ''));
195
+ lines.push(pad('email') + ': ' + yamlEscape(f.email || ''));
196
+ lines.push(pad('description') + ': ' + yamlEscape(f.description || ''));
197
+ lines.push('');
198
+
199
+ lines.push('# ── GitHub ────────────────────────────────────────────────────────');
200
+ lines.push(pad('github_user') + ': ' + yamlEscape(f.github_user || ''));
201
+ lines.push(pad('repository_name') + ': ' + yamlEscape(f.repository_name || ''));
202
+ lines.push(pad('branch') + ': ' + yamlEscape(f.branch || 'main'));
203
+ lines.push('');
204
+
205
+ lines.push('# ── URLs & Deployment ─────────────────────────────────────────────');
206
+ lines.push(pad('url') + ': ' + yamlEscape(f.url || ''));
207
+ lines.push(pad('baseurl') + ': ' + yamlEscape(f.baseurl || ''));
208
+ lines.push(pad('remote_theme') + ': ' + yamlEscape(f.remote_theme || 'bamr87/zer0-mistakes'));
209
+ lines.push(pad('port') + ': ' + (f.port || '4000'));
210
+ lines.push(pad('permalink') + ': ' + (f.permalink || 'pretty'));
211
+ lines.push('');
212
+
213
+ lines.push('# ── Personalization ───────────────────────────────────────────────');
214
+ lines.push(pad('locale') + ': ' + yamlEscape(f.locale || 'en-US'));
215
+ lines.push(pad('theme_skin') + ': ' + yamlEscape(f.theme_skin || 'dark'));
216
+ if (f.logo) lines.push(pad('logo') + ': ' + yamlEscape(f.logo));
217
+ lines.push('');
218
+
219
+ lines.push('# ── Analytics ─────────────────────────────────────────────────────');
220
+ if (f.google_analytics) {
221
+ lines.push(pad('google_analytics') + ': ' + yamlEscape(f.google_analytics));
222
+ }
223
+ lines.push('posthog:');
224
+ lines.push(' enabled: ' + (f['posthog.enabled'] ? 'true' : 'false'));
225
+ if (f['posthog.api_key']) {
226
+ lines.push(' api_key: ' + yamlEscape(f['posthog.api_key']));
227
+ }
228
+ lines.push('');
229
+
230
+ lines.push('# ── Build ─────────────────────────────────────────────────────────');
231
+ lines.push(pad('markdown') + ': kramdown');
232
+ lines.push(pad('highlighter') + ': rouge');
233
+ lines.push('');
234
+
235
+ lines.push('# ── Plugins ───────────────────────────────────────────────────────');
236
+ lines.push('plugins:');
237
+ lines.push(' - github-pages');
238
+ lines.push(' - jekyll-remote-theme');
239
+ lines.push(' - jekyll-feed');
240
+ lines.push(' - jekyll-sitemap');
241
+ lines.push(' - jekyll-seo-tag');
242
+
243
+ return lines.join('\n');
244
+ }
245
+
246
+ function initEditor() {
247
+ var preview = document.getElementById('editor-yaml-preview');
248
+ if (!preview) return;
249
+
250
+ function update() { preview.textContent = buildEditorYAML(); }
251
+
252
+ document.querySelectorAll('#configEditor .edit-field').forEach(function (el) {
253
+ el.addEventListener('input', update);
254
+ el.addEventListener('change', update);
255
+ });
256
+
257
+ /* download */
258
+ var dlBtn = document.getElementById('editor-download');
259
+ if (dlBtn) {
260
+ dlBtn.addEventListener('click', function () {
261
+ var yaml = buildEditorYAML();
262
+ var blob = new Blob([yaml], { type: 'text/yaml;charset=utf-8' });
263
+ var a = document.createElement('a');
264
+ a.href = URL.createObjectURL(blob);
265
+ a.download = '_config.yml';
266
+ document.body.appendChild(a);
267
+ a.click();
268
+ document.body.removeChild(a);
269
+ });
270
+ }
271
+
272
+ /* copy */
273
+ var cpBtn = document.getElementById('editor-copy');
274
+ if (cpBtn) {
275
+ cpBtn.addEventListener('click', function () {
276
+ copyToClipboard(buildEditorYAML(), cpBtn);
277
+ });
278
+ }
279
+
280
+ /* initial render */
281
+ update();
282
+ }
283
+
284
+ /* ── description char counter ────────────────────────────────────── */
285
+
286
+ function initCharCounters() {
287
+ document.querySelectorAll('[data-char-counter]').forEach(function (el) {
288
+ var counter = document.getElementById(el.getAttribute('data-char-counter'));
289
+ if (!counter) return;
290
+ function upd() { counter.textContent = el.value.length; }
291
+ el.addEventListener('input', upd);
292
+ upd();
293
+ });
294
+ }
295
+
296
+ /* ── wiring ──────────────────────────────────────────────────────── */
297
+
298
+ document.addEventListener('DOMContentLoaded', function () {
299
+ initSearch();
300
+ initCopyButtons();
301
+ initSectionCopy();
302
+ initFullCopy();
303
+ initExpandCollapse();
304
+ initEditor();
305
+ initCharCounters();
306
+ });
307
+ })();
@@ -0,0 +1,39 @@
1
+ /**
2
+ * nav-editor.js
3
+ * Powers the Navigation Editor admin page.
4
+ * - Copy YAML output from nav data rendered in the overview tab
5
+ */
6
+
7
+ document.addEventListener('DOMContentLoaded', function () {
8
+ 'use strict';
9
+
10
+ var copyBtn = document.getElementById('nav-copy-yaml');
11
+ if (copyBtn) {
12
+ copyBtn.addEventListener('click', function () {
13
+ var text = document.getElementById('nav-yaml-output').textContent;
14
+ navigator.clipboard.writeText(text).then(function () {
15
+ copyBtn.innerHTML = '<i class="bi bi-check me-1"></i> Copied';
16
+ setTimeout(function () { copyBtn.innerHTML = '<i class="bi bi-clipboard me-1"></i> Copy'; }, 2000);
17
+ });
18
+ });
19
+ }
20
+
21
+ /* Populate YAML preview when nav file selector changes */
22
+ var fileSelect = document.getElementById('nav-file-select');
23
+ if (fileSelect) {
24
+ fileSelect.addEventListener('change', function () {
25
+ var selected = this.value;
26
+ var output = document.getElementById('nav-yaml-output');
27
+ if (output) {
28
+ output.textContent = '# _data/navigation/' + selected + '.yml\n# View the Overview tab for the rendered tree structure.\n# Edit the YAML file directly in your repository.';
29
+ }
30
+
31
+ // Expand the matching accordion section
32
+ var accBtn = document.querySelector('#navAcc-' + selected);
33
+ if (accBtn) {
34
+ var bsCollapse = bootstrap.Collapse.getOrCreateInstance(accBtn, { toggle: false });
35
+ bsCollapse.show();
36
+ }
37
+ });
38
+ }
39
+ });