Skip to content

Commit ccbfedd

Browse files
committed
pages: add INI browser landing page
1 parent 2f75b3f commit ccbfedd

1 file changed

Lines changed: 289 additions & 0 deletions

File tree

index.html

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Mazduino Firmware — INI Files</title>
7+
<style>
8+
:root {
9+
--bg: #0d1117;
10+
--surface: #161b22;
11+
--border: #30363d;
12+
--text: #c9d1d9;
13+
--muted: #8b949e;
14+
--accent: #58a6ff;
15+
--green: #3fb950;
16+
--yellow: #d29922;
17+
}
18+
* { box-sizing: border-box; margin: 0; padding: 0; }
19+
body {
20+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
21+
background: var(--bg);
22+
color: var(--text);
23+
line-height: 1.6;
24+
min-height: 100vh;
25+
}
26+
.container { max-width: 860px; margin: 0 auto; padding: 32px 20px; }
27+
28+
header { margin-bottom: 32px; }
29+
header h1 { font-size: 1.5rem; font-weight: 600; margin-bottom: 4px; }
30+
header p { color: var(--muted); font-size: 0.9rem; }
31+
header a { color: var(--accent); text-decoration: none; }
32+
header a:hover { text-decoration: underline; }
33+
34+
.filters {
35+
display: flex; gap: 8px; flex-wrap: wrap;
36+
margin-bottom: 24px;
37+
}
38+
.filter-btn {
39+
padding: 4px 14px; border-radius: 20px;
40+
border: 1px solid var(--border);
41+
background: transparent; color: var(--text);
42+
cursor: pointer; font-size: 0.82rem;
43+
transition: background 0.1s, border-color 0.1s;
44+
}
45+
.filter-btn:hover { border-color: var(--accent); }
46+
.filter-btn.active {
47+
background: var(--accent); border-color: var(--accent);
48+
color: #0d1117; font-weight: 600;
49+
}
50+
51+
#status { color: var(--muted); font-size: 0.85rem; margin-bottom: 20px; min-height: 20px; }
52+
53+
.board-section { margin-bottom: 32px; }
54+
.board-header {
55+
display: flex; align-items: baseline; gap: 10px;
56+
padding-bottom: 8px; margin-bottom: 0;
57+
border-bottom: 1px solid var(--border);
58+
}
59+
.board-name { font-size: 1rem; font-weight: 600; }
60+
.board-count { font-size: 0.8rem; color: var(--muted); }
61+
62+
table { width: 100%; border-collapse: collapse; font-size: 0.855rem; }
63+
th {
64+
text-align: left; color: var(--muted); font-weight: 500;
65+
padding: 8px 10px 6px 0; white-space: nowrap;
66+
}
67+
td { padding: 9px 10px 9px 0; border-top: 1px solid var(--border); vertical-align: middle; }
68+
tr:last-child td { border-bottom: 1px solid var(--border); }
69+
70+
.badge {
71+
display: inline-block; padding: 1px 8px; border-radius: 10px;
72+
font-size: 0.75rem; border: 1px solid var(--border);
73+
background: var(--surface); color: var(--muted);
74+
white-space: nowrap;
75+
}
76+
.badge-main { border-color: var(--green); color: var(--green); }
77+
.badge-feature { border-color: var(--yellow); color: var(--yellow); }
78+
79+
.hash { font-family: 'SFMono-Regular', Consolas, monospace; color: var(--muted); font-size: 0.8rem; }
80+
.latest-label {
81+
font-size: 0.72rem; padding: 1px 6px; border-radius: 8px;
82+
background: rgba(63,185,80,0.1); border: 1px solid var(--green);
83+
color: var(--green); margin-left: 6px; vertical-align: middle;
84+
}
85+
86+
a { color: var(--accent); text-decoration: none; }
87+
a:hover { text-decoration: underline; }
88+
89+
.error { color: #f85149; font-size: 0.9rem; }
90+
.empty { color: var(--muted); font-size: 0.9rem; padding: 16px 0; }
91+
92+
footer {
93+
margin-top: 48px; padding-top: 20px;
94+
border-top: 1px solid var(--border);
95+
color: var(--muted); font-size: 0.8rem;
96+
display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px;
97+
}
98+
</style>
99+
</head>
100+
<body>
101+
<div class="container">
102+
<header>
103+
<h1>Mazduino Firmware</h1>
104+
<p>
105+
TunerStudio INI configuration files &mdash;
106+
<a href="https://github.com/mazduino/mazduino-fw/releases" target="_blank">Releases &amp; firmware binaries &rarr;</a>
107+
</p>
108+
</header>
109+
110+
<div id="filters" class="filters"></div>
111+
<div id="status">Loading&hellip;</div>
112+
<div id="content"></div>
113+
114+
<footer>
115+
<span>
116+
<a href="https://github.com/mazduino/mazduino-fw" target="_blank">mazduino/mazduino-fw</a>
117+
&nbsp;&middot;&nbsp;
118+
<a href="https://www.mazduino.com/" target="_blank">mazduino.com</a>
119+
</span>
120+
<span id="last-updated"></span>
121+
</footer>
122+
</div>
123+
124+
<script>
125+
const REPO = 'mazduino/mazduino-fw';
126+
const BASE_URL = 'https://mazduino.github.io/mazduino-fw';
127+
128+
let allFiles = [];
129+
let activeBoard = 'all';
130+
131+
async function fetchFiles() {
132+
const url = `https://api.github.com/repos/${REPO}/git/trees/gh-pages?recursive=1`;
133+
const res = await fetch(url, { headers: { Accept: 'application/vnd.github+json' } });
134+
if (!res.ok) throw new Error(`GitHub API responded with ${res.status}`);
135+
const data = await res.json();
136+
137+
if (data.truncated) {
138+
console.warn('GitHub tree response truncated — some files may be missing');
139+
}
140+
141+
return data.tree
142+
.filter(item => item.type === 'blob' && item.path.startsWith('ini/') && item.path.endsWith('.ini'))
143+
.map(item => {
144+
// ini/<branch>/<year>/<month>/<day>/<target>/<hash>.ini
145+
const parts = item.path.replace(/^ini\//, '').split('/');
146+
if (parts.length < 6) return null;
147+
const [branch, year, month, day, target, filename] = parts;
148+
const hash = filename.replace('.ini', '');
149+
return { path: item.path, branch, year, month, day, target, hash };
150+
})
151+
.filter(Boolean);
152+
}
153+
154+
function groupByBoard(files) {
155+
const groups = {};
156+
for (const f of files) {
157+
if (!groups[f.target]) groups[f.target] = [];
158+
groups[f.target].push(f);
159+
}
160+
for (const target of Object.keys(groups)) {
161+
groups[target].sort((a, b) => {
162+
const da = `${a.year}${a.month.padStart(2,'0')}${a.day.padStart(2,'0')}`;
163+
const db = `${b.year}${b.month.padStart(2,'0')}${b.day.padStart(2,'0')}`;
164+
if (db !== da) return db.localeCompare(da);
165+
// same date: main branch first
166+
if (a.branch === 'main' && b.branch !== 'main') return -1;
167+
if (b.branch === 'main' && a.branch !== 'main') return 1;
168+
return a.branch.localeCompare(b.branch);
169+
});
170+
}
171+
return groups;
172+
}
173+
174+
function branchBadge(branch) {
175+
const cls = branch === 'main' ? 'badge badge-main'
176+
: branch.startsWith('feature') ? 'badge badge-feature'
177+
: 'badge';
178+
return `<span class="${cls}">${escHtml(branch)}</span>`;
179+
}
180+
181+
function escHtml(s) {
182+
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
183+
}
184+
185+
function renderFilters(boards) {
186+
const div = document.getElementById('filters');
187+
div.innerHTML = '';
188+
189+
const all = document.createElement('button');
190+
all.className = 'filter-btn' + (activeBoard === 'all' ? ' active' : '');
191+
all.textContent = 'All boards';
192+
all.onclick = () => setBoard('all');
193+
div.appendChild(all);
194+
195+
for (const board of boards) {
196+
const btn = document.createElement('button');
197+
btn.className = 'filter-btn' + (activeBoard === board ? ' active' : '');
198+
btn.textContent = board;
199+
btn.onclick = () => setBoard(board);
200+
div.appendChild(btn);
201+
}
202+
}
203+
204+
function setBoard(board) {
205+
activeBoard = board;
206+
render();
207+
}
208+
209+
function render() {
210+
const groups = groupByBoard(allFiles);
211+
const boards = Object.keys(groups).sort();
212+
renderFilters(boards);
213+
214+
const content = document.getElementById('content');
215+
content.innerHTML = '';
216+
217+
const visible = activeBoard === 'all' ? boards : boards.filter(b => b === activeBoard);
218+
219+
if (visible.length === 0) {
220+
content.innerHTML = '<p class="empty">No INI files found.</p>';
221+
return;
222+
}
223+
224+
for (const board of visible) {
225+
const files = groups[board];
226+
const section = document.createElement('div');
227+
section.className = 'board-section';
228+
229+
let rows = '';
230+
files.forEach((f, idx) => {
231+
const date = `${f.year}-${f.month.padStart(2,'0')}-${f.day.padStart(2,'0')}`;
232+
const url = `${BASE_URL}/${f.path}`;
233+
const filename = `${f.hash}.ini`;
234+
const latestTag = idx === 0 ? '<span class="latest-label">latest</span>' : '';
235+
rows += `<tr>
236+
<td>${date}${latestTag}</td>
237+
<td>${branchBadge(f.branch)}</td>
238+
<td><span class="hash">${f.hash.slice(0, 8)}</span></td>
239+
<td><a href="${url}">${escHtml(filename)}</a></td>
240+
</tr>`;
241+
});
242+
243+
section.innerHTML = `
244+
<div class="board-header">
245+
<span class="board-name">${escHtml(board)}</span>
246+
<span class="board-count">${files.length} build${files.length !== 1 ? 's' : ''}</span>
247+
</div>
248+
<table>
249+
<thead><tr><th>Date</th><th>Branch</th><th>Build hash</th><th>Download</th></tr></thead>
250+
<tbody>${rows}</tbody>
251+
</table>`;
252+
content.appendChild(section);
253+
}
254+
}
255+
256+
(async () => {
257+
try {
258+
allFiles = await fetchFiles();
259+
const count = allFiles.length;
260+
const latestDate = allFiles.length
261+
? [...allFiles].sort((a, b) => {
262+
const da = `${a.year}${a.month}${a.day}`;
263+
const db = `${b.year}${b.month}${b.day}`;
264+
return db.localeCompare(da);
265+
})[0]
266+
: null;
267+
268+
document.getElementById('status').textContent =
269+
`${count} INI file${count !== 1 ? 's' : ''} available`;
270+
271+
if (latestDate) {
272+
document.getElementById('last-updated').textContent =
273+
`Last build: ${latestDate.year}-${latestDate.month.padStart(2,'0')}-${latestDate.day.padStart(2,'0')}`;
274+
}
275+
276+
render();
277+
} catch (e) {
278+
document.getElementById('status').textContent = '';
279+
document.getElementById('content').innerHTML =
280+
`<p class="error">Could not load file list: ${e.message}</p>
281+
<p style="color:var(--muted);font-size:0.85rem;margin-top:8px;">
282+
You can browse INI files directly at
283+
<a href="https://github.com/mazduino/mazduino-fw/releases">GitHub Releases</a>.
284+
</p>`;
285+
}
286+
})();
287+
</script>
288+
</body>
289+
</html>

0 commit comments

Comments
 (0)