7676 description: "${DESC}"
7777 permalink: pretty
7878 baseurl: "${BASE_URL}"
79+ github_repo: "https://github.com/${REPO_FULL}"
7980
8081 defaults:
8182 - scope:
@@ -97,45 +98,68 @@ jobs:
9798 <link rel="preconnect" href="https://fonts.googleapis.com">
9899 <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
99100 <style>
101+ :root{
102+ --bg-page:#f6f8fa;--bg-content:#fff;--bg-header:#fff;--bg-code:#f6f8fa;
103+ --text-primary:#24292f;--text-secondary:#57606a;--text-muted:#8b949e;
104+ --border:#d0d7de;--link:#0969da;--accent:#fd8c73;
105+ --badge-bg:#dafbe1;--badge-text:#1a7f37;
106+ }
107+ @media(prefers-color-scheme:dark){
108+ :root{
109+ --bg-page:#0d1117;--bg-content:#161b22;--bg-header:#161b22;--bg-code:#1c2129;
110+ --text-primary:#e6edf3;--text-secondary:#8b949e;--text-muted:#6e7681;
111+ --border:#30363d;--link:#58a6ff;--accent:#f78166;
112+ --badge-bg:#1b3826;--badge-text:#3fb950;
113+ }
114+ }
100115 *{margin:0;padding:0;box-sizing:border-box}
101- body{font-family:'IBM Plex Sans',-apple-system,sans-serif;color:#24292f ;background:#f6f8fa ;line-height:1.7}
102- a{color:#0969da ;text-decoration:none}
116+ body{font-family:'IBM Plex Sans',-apple-system,sans-serif;color:var(--text-primary) ;background:var(--bg-page) ;line-height:1.7}
117+ a{color:var(--link) ;text-decoration:none}
103118 a:hover{text-decoration:underline}
104- .site-header{background:#fff ;border-bottom:1px solid #d0d7de ;padding:16px 24px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px}
105- .site-title{font-family:'IBM Plex Mono',monospace;font- size:16px;font-weight:500;color:#24292f ;text-decoration:none}
119+ .site-header{background:var(--bg-header) ;border-bottom:1px solid var(--border) ;padding:16px 24px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px}
120+ .site-title{font-size:16px;font-weight:500;color:var(--text-primary) ;text-decoration:none}
106121 .site-nav{display:flex;gap:20px;flex-wrap:wrap}
107- .site-nav a{font-size:14px;color:#57606a ;text-decoration:none;padding:4px 0}
108- .site-nav a:hover{color:#24292f }
109- .site-nav a.active{color:#24292f ;font-weight:500;border-bottom:2px solid #fd8c73 }
122+ .site-nav a{font-size:14px;color:var(--text-secondary) ;text-decoration:none;padding:4px 0}
123+ .site-nav a:hover{color:var(--text-primary) }
124+ .site-nav a.active{color:var(--text-primary) ;font-weight:500;border-bottom:2px solid var(--accent) }
110125 .container{max-width:820px;margin:32px auto;padding:0 24px}
111- .content{background:#fff ;border:1px solid #d0d7de ;border-radius:6px;padding:32px}
112- .content h1{font-size:24px;font-weight:500;margin:0 0 16px;border-bottom:1px solid #d0d7de ;padding-bottom:8px}
113- .content h2{font-size:20px;font-weight:500;margin:24px 0 12px;border-bottom:1px solid #d0d7de ;padding-bottom:6px}
126+ .content{background:var(--bg-content) ;border:1px solid var(--border) ;border-radius:6px;padding:32px}
127+ .content h1{font-size:24px;font-weight:500;margin:0 0 16px;border-bottom:1px solid var(--border) ;padding-bottom:8px}
128+ .content h2{font-size:20px;font-weight:500;margin:24px 0 12px;border-bottom:1px solid var(--border) ;padding-bottom:6px}
114129 .content h3{font-size:16px;font-weight:500;margin:20px 0 8px}
115130 .content h4{font-size:14px;font-weight:500;margin:16px 0 6px}
116- .content p{font-size:14px;color:#57606a ;margin:0 0 16px}
117- .content ul,.content ol{font-size:14px;color:#57606a ;margin:0 0 16px;padding-left:24px}
131+ .content p{font-size:14px;color:var(--text-secondary) ;margin:0 0 16px}
132+ .content ul,.content ol{font-size:14px;color:var(--text-secondary) ;margin:0 0 16px;padding-left:24px}
118133 .content li{margin-bottom:4px}
119- .content code{font-family:'IBM Plex Mono',monospace;font-size:13px;background:#f6f8fa ;padding:2px 6px;border-radius:4px}
120- .content pre{background:#f6f8fa ;border:1px solid #d0d7de ;border-radius:6px;padding:16px;overflow-x:auto;margin:8px 0 16px;line-height:1.5}
134+ .content code{font-family:'IBM Plex Mono',monospace;font-size:13px;background:var(--bg-code) ;padding:2px 6px;border-radius:4px}
135+ .content pre{background:var(--bg-code) ;border:1px solid var(--border) ;border-radius:6px;padding:16px;overflow-x:auto;margin:8px 0 16px;line-height:1.5}
121136 .content pre code{background:transparent;padding:0}
122137 .content table{width:100%;border-collapse:collapse;margin:8px 0 16px;font-size:14px}
123- .content th,.content td{border:1px solid #d0d7de ;padding:8px 12px;text-align:left}
124- .content th{background:#f6f8fa ;font-weight:500}
138+ .content th,.content td{border:1px solid var(--border) ;padding:8px 12px;text-align:left}
139+ .content th{background:var(--bg-code) ;font-weight:500}
125140 .content img{max-width:100%}
126- #releases-container .release{border-bottom:1px solid #d0d7de;padding:20px 0}
127- #releases-container .release:last-child{border-bottom:none}
128- .release-tag{font-family:'IBM Plex Mono',monospace;font-size:16px;font-weight:500;color:#0969da}
129- .release-date{font-size:13px;color:#8b949e;margin-left:8px}
130- .release-latest{font-size:11px;padding:2px 8px;border-radius:16px;background:#dafbe1;color:#1a7f37;font-weight:500;margin-left:8px}
131- .release-body{font-size:14px;color:#57606a;margin-top:8px;line-height:1.7}
132- .release-body h2,.release-body h3{font-size:14px;font-weight:500;color:#24292f;margin:12px 0 4px;border:none;padding:0}
141+ .content strong{color:var(--text-primary)}
142+ .release{border:1px solid var(--border);border-radius:6px;margin-bottom:8px}
143+ .release summary{padding:12px 16px;cursor:pointer;display:flex;align-items:center;gap:8px;font-size:14px;list-style:none}
144+ .release summary::-webkit-details-marker{display:none}
145+ .release summary::before{content:'';display:inline-block;width:0;height:0;border-left:5px solid var(--text-muted);border-top:4px solid transparent;border-bottom:4px solid transparent;transition:transform 0.15s}
146+ .release[open] summary::before{transform:rotate(90deg)}
147+ .release-tag{font-family:'IBM Plex Mono',monospace;font-weight:500;color:var(--link)}
148+ .release-date{font-size:13px;color:var(--text-muted)}
149+ .release-latest{font-size:11px;padding:2px 8px;border-radius:16px;background:var(--badge-bg);color:var(--badge-text);font-weight:500}
150+ .release-body{padding:0 16px 16px;font-size:14px;color:var(--text-secondary);line-height:1.7}
151+ .release-body h2,.release-body h3{font-size:14px;font-weight:500;color:var(--text-primary);margin:12px 0 4px;border:none;padding:0}
133152 .release-body ul{padding-left:20px}
134153 .release-body li{margin-bottom:2px;font-size:13px}
135- .release-assets{margin-top:12px}
136- .release-assets summary{font-size:13px;font-weight:500;cursor:pointer;color:#24292f}
137- .release-asset{font-family:'IBM Plex Mono',monospace;font-size:12px;color:#57606a;padding:3px 0}
138- .loading{text-align:center;padding:32px;color:#8b949e;font-size:14px}
154+ .release-body pre{background:var(--bg-code);border:1px solid var(--border);border-radius:6px;padding:12px;overflow-x:auto;font-size:13px;line-height:1.5}
155+ .release-body code{font-family:'IBM Plex Mono',monospace;font-size:13px;background:var(--bg-code);padding:2px 4px;border-radius:3px}
156+ .release-body pre code{background:transparent;padding:0}
157+ .release-assets{margin-top:12px;border-top:1px solid var(--border);padding-top:12px}
158+ .release-assets summary{padding:0;font-size:13px;font-weight:500;cursor:pointer;color:var(--text-primary);list-style:revert}
159+ .release-assets summary::-webkit-details-marker{display:revert}
160+ .release-assets summary::before{display:none}
161+ .release-asset{font-family:'IBM Plex Mono',monospace;font-size:12px;color:var(--text-secondary);padding:3px 0}
162+ .loading{text-align:center;padding:32px;color:var(--text-muted);font-size:14px}
139163 @media(max-width:640px){.site-header{padding:12px 16px}.container{padding:0 12px;margin:16px auto}.content{padding:20px}}
140164 </style>
141165 </head>
@@ -150,6 +174,7 @@ jobs:
150174 {% for p in site.pages %}{% if p.path == 'LICENSE.md' %}<a href="{{ '/license/' | relative_url }}" {% if page.url contains '/license' %}class="active"{% endif %}>License</a>{% break %}{% endif %}{% endfor %}
151175 {% for p in site.pages %}{% if p.path == 'CONTRIBUTING.md' %}<a href="{{ '/contributing/' | relative_url }}" {% if page.url contains '/contributing' %}class="active"{% endif %}>Contributing</a>{% break %}{% endif %}{% endfor %}
152176 {% for p in site.pages %}{% if p.path == 'CODE_OF_CONDUCT.md' %}<a href="{{ '/code-of-conduct/' | relative_url }}" {% if page.url contains '/code-of-conduct' %}class="active"{% endif %}>Code of conduct</a>{% break %}{% endif %}{% endfor %}
177+ <a href="{{ site.github_repo }}" target="_blank" rel="noopener">View source</a>
153178 </nav>
154179 </header>
155180 <main class="container">
@@ -171,6 +196,18 @@ jobs:
171196 echo "Processed README.md → homepage"
172197 fi
173198
199+ # ── Fix relative links for Jekyll ─────────────────────────────
200+ # Jekyll with permalink:pretty changes paths, so relative links
201+ # like [X](CONTRIBUTING.md) or [X](LICENSE) would 404.
202+ # Point them at their Jekyll permalinks instead.
203+ # Only targets known community files in root-level markdown.
204+ for f in *.md; do
205+ [ -f "$f" ] || continue
206+ [ -f "CONTRIBUTING.md" ] && sed -i 's|\](CONTRIBUTING\.md)|\](contributing/)|g' "$f"
207+ [ -f "CODE_OF_CONDUCT.md" ] && sed -i 's|\](CODE_OF_CONDUCT\.md)|\](code-of-conduct/)|g' "$f"
208+ ([ -f "LICENSE" ] || [ -f "LICENSE.txt" ] || [ -f "LICENSE.md" ]) && sed -i 's|\](LICENSE)|\](license/)|g' "$f"
209+ done
210+
174211 # ── Docs: add front matter + create index ─────────────────────
175212 if [ -d "docs" ]; then
176213 for f in docs/*.md; do
@@ -213,65 +250,116 @@ jobs:
213250 fi
214251
215252 # ── Releases page ─────────────────────────────────────────────
253+ # Write the JS to a separate file to avoid heredoc escaping issues
254+ mkdir -p _includes
255+ cat > _includes/releases.js << 'JSEOF'
256+ (async function() {
257+ var c = document.getElementById('releases-container');
258+ var repo = c.getAttribute('data-repo');
259+ try {
260+ var res = await fetch('https://api.github.com/repos/' + repo + '/releases');
261+ if (!res.ok) throw new Error(res.status);
262+ var data = await res.json();
263+ if (!data.length) { c.innerHTML = '<p>No releases found.</p>'; return; }
264+
265+ function md(s) {
266+ if (!s) return '';
267+ var lines = s.split('\n');
268+ var html = '';
269+ var inList = false;
270+ for (var i = 0; i < lines.length; i++) {
271+ var line = lines[i];
272+ var h3 = line.match(/^### (.+)$/);
273+ var h2 = line.match(/^## (.+)$/);
274+ var li = line.match(/^\* (.+)$/);
275+ var cb = line.match(/^```/);
276+ if (cb) {
277+ if (inList) { html += '</ul>'; inList = false; }
278+ var code = '';
279+ i++;
280+ while (i < lines.length && !lines[i].match(/^```/)) { code += lines[i] + '\n'; i++; }
281+ html += '<pre><code>' + code + '</code></pre>';
282+ } else if (h2) {
283+ if (inList) { html += '</ul>'; inList = false; }
284+ html += '<h2>' + h2[1] + '</h2>';
285+ } else if (h3) {
286+ if (inList) { html += '</ul>'; inList = false; }
287+ html += '<h3>' + h3[1] + '</h3>';
288+ } else if (li) {
289+ if (!inList) { html += '<ul>'; inList = true; }
290+ var text = li[1].replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
291+ html += '<li>' + text + '</li>';
292+ } else {
293+ if (inList) { html += '</ul>'; inList = false; }
294+ if (line.trim() === '---') { html += '<hr>'; }
295+ else if (line.trim()) {
296+ var text = line.replace(/`([^`]+)`/g, '<code>$1</code>').replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>').replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
297+ html += '<p>' + text + '</p>';
298+ }
299+ }
300+ }
301+ if (inList) html += '</ul>';
302+ return html;
303+ }
304+
305+ c.innerHTML = data.map(function(r, i) {
306+ var d = new Date(r.published_at).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' });
307+ var latest = i === 0 ? '<span class="release-latest">latest</span>' : '';
308+ var openAttr = i === 0 ? ' open' : '';
309+ var assets = '';
310+ if (r.assets.length) {
311+ assets = '<details class="release-assets"><summary>Assets (' + r.assets.length + ')</summary>';
312+ for (var j = 0; j < r.assets.length; j++) {
313+ var a = r.assets[j];
314+ assets += '<div class="release-asset">' + a.name + ' <span style="color:var(--text-muted)">' + (a.size / 1024).toFixed(1) + ' KB</span></div>';
315+ }
316+ assets += '</details>';
317+ }
318+ return '<details class="release"' + openAttr + '><summary><span class="release-tag">' + r.tag_name + '</span>' + latest + '<span class="release-date">' + d + '</span></summary><div class="release-body">' + md(r.body) + assets + '</div></details>';
319+ }).join('');
320+ } catch(e) {
321+ c.innerHTML = '<p>Unable to load releases. Visit <a href="https://github.com/' + repo + '/releases">GitHub</a> directly.</p>';
322+ }
323+ })();
324+ JSEOF
325+
216326 cat > releases.md << RELEOF
217327 ---
218328 layout: default
219329 title: Releases
220330 permalink: /releases/
221331 ---
222332 # Releases
223- <div id="releases-container"><div class="loading">Loading releases...</div></div>
333+ <div id="releases-container" data-repo="${REPO_FULL}" ><div class="loading">Loading releases...</div></div>
224334 <script>
225- (async function(){
226- const c=document.getElementById('releases-container');
227- try{
228- const r=await fetch('https://api.github.com/repos/${REPO_FULL}/releases');
229- if(!r.ok)throw new Error(r.status);
230- const data=await r.json();
231- if(!data.length){c.innerHTML='<p>No releases found.</p>';return}
232- c.innerHTML=data.map((r,i)=>{
233- const d=new Date(r.published_at).toLocaleDateString('en-GB',{day:'numeric',month:'short',year:'numeric'});
234- const l=i===0?'<span class="release-latest">latest</span>':'';
235- const a=r.assets.length?'<details class="release-assets"><summary>Assets ('+r.assets.length+')</summary>'+r.assets.map(a=>'<div class="release-asset">'+a.name+' <span style="color:#8b949e">'+(a.size/1024).toFixed(1)+' KB</span></div>').join('')+'</details>':'';
236- let b=(r.body||'').replace(/^### (.+)$/gm,'<h3>$1</h3>').replace(/^## (.+)$/gm,'<h2>$1</h2>').replace(/^\* (.+)$/gm,'<li>$1</li>').replace(/(<li>.*<\/li>\n?)+/gs,'<ul>$&</ul>').replace(/\x60\x60\x60(\w*)\n([\s\S]*?)\x60\x60\x60/g,'<pre><code>$2</code></pre>').replace(/\x60([^\x60]+)\x60/g,'<code>$1</code>').replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2">$1</a>').replace(/\n\n/g,'<br><br>').replace(/\n/g,'<br>');
237- return '<div class="release"><div><span class="release-tag">'+r.tag_name+'</span>'+l+'<span class="release-date">'+d+'</span></div><div class="release-body">'+b+'</div>'+a+'</div>';
238- }).join('');
239- }catch(e){
240- c.innerHTML='<p>Unable to load releases. Visit <a href="https://github.com/${REPO_FULL}/releases">GitHub</a> directly.</p>';
241- }
242- })();
335+ {% include releases.js %}
243336 </script>
244337 RELEOF
245338 echo "Created releases.md"
246339
247- # ── License page (from LICENSE/LICENSE.txt) ────────────── ──────
340+ # ── License page (full content from LICENSE/LICENSE.txt) ──────
248341 LICENSE_SRC=""
249342 for lf in LICENSE LICENSE.txt; do
250343 [ -f "$lf" ] && LICENSE_SRC="$lf" && break
251344 done
252345
253- if [ -n "$LICENSE_SRC" ] || [ -f "LICENSE.md" ]; then
254- SRC="${LICENSE_SRC:-LICENSE.md}"
255- LICENSE_TYPE="License"
256- grep -qi "apache" "$SRC" 2>/dev/null && LICENSE_TYPE="Apache License 2.0"
257- grep -qi "mit license" "$SRC" 2>/dev/null && LICENSE_TYPE="MIT License"
258- grep -qi "gnu general public" "$SRC" 2>/dev/null && LICENSE_TYPE="GPL"
259- grep -qi "bsd" "$SRC" 2>/dev/null && LICENSE_TYPE="BSD License"
260-
346+ if [ -n "$LICENSE_SRC" ]; then
347+ # Create LICENSE.md with front matter + full license text
261348 if ! ([ -f "LICENSE.md" ] && head -1 LICENSE.md | grep -q '^\-\-\-'); then
262- cat > LICENSE.md << LICEOF
263- ---
264- layout: default
265- title: License
266- permalink: /license/
267- ---
268- # License
269- This project is licensed under the **${LICENSE_TYPE}**.
270-
271- See the [LICENSE](https://github.com/${REPO_FULL}/blob/main/${SRC}) file for the full license text.
272- LICEOF
273- echo "Created LICENSE.md (${LICENSE_TYPE})"
349+ TEMP=$(mktemp)
350+ printf -- '---\nlayout: default\ntitle: License\npermalink: /license/\n---\n\n# License\n\n```\n' > "$TEMP"
351+ cat "$LICENSE_SRC" >> "$TEMP"
352+ printf '\n```\n' >> "$TEMP"
353+ mv "$TEMP" LICENSE.md
354+ echo "Created LICENSE.md from ${LICENSE_SRC}"
274355 fi
356+ elif [ -f "LICENSE.md" ] && ! head -1 LICENSE.md | grep -q '^\-\-\-'; then
357+ # LICENSE.md exists but has no front matter
358+ TEMP=$(mktemp)
359+ printf -- '---\nlayout: default\ntitle: License\npermalink: /license/\n---\n\n' > "$TEMP"
360+ cat LICENSE.md >> "$TEMP"
361+ mv "$TEMP" LICENSE.md
362+ echo "Processed LICENSE.md"
275363 fi
276364
277365 # ── CONTRIBUTING.md ───────────────────────────────────────────
0 commit comments