Something went wrong. Try again.
Live STIB-MIVB departures on the Pebble Time 2. It covers the bus, tram, and metro network of Brussels
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Stibble</title><style>:root { color-scheme: dark; }body { margin: 0; font-family: system-ui, sans-serif; background: #111; color: #eee; }main { max-width: 480px; margin: 0 auto; padding: 16px; }h1 { font-size: 1.3rem; }section { background: #1c1c1c; border-radius: 10px; padding: 12px 16px; margin-bottom: 14px; }label { display: block; margin: 8px 0 4px; font-size: 0.9rem; color: #aaa; }select, input[type=search] { width: 100%; padding: 8px; border-radius: 6px; border: 1px solid #333; background: #222; color: #eee; box-sizing: border-box; }#favs li { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; list-style: none; }#favs, #results { padding: 0; margin: 8px 0; }#results li { list-style: none; padding: 8px; cursor: pointer; border-radius: 6px; }#results li:hover { background: #2a2a2a; }button { border: 0; border-radius: 6px; padding: 6px 12px; background: #444; color: #eee; cursor: pointer; }#save { width: 100%; padding: 12px; font-size: 1rem; background: #2b6; color: #000; margin-top: 6px; }footer { color: #666; font-size: 0.75rem; text-align: center; margin: 16px 0; }.toggle { display: flex; justify-content: space-between; align-items: center; }</style></head><body><main><h1>Stibble settings</h1><section><label for="labelLang">Interface language</label><select id="labelLang"><option value="en">English</option><option value="fr">Français</option><option value="nl">Nederlands</option></select><label for="destLang">Stop and destination names</label><select id="destLang"><option value="fr">Français</option><option value="nl">Nederlands</option></select><div class="toggle"><label for="vibrate">Vibrate on arrival</label><input type="checkbox" id="vibrate"></div></section><section><label for="q">Search stops</label><input type="search" id="q" placeholder="Search a stop"><ul id="results"></ul><ul id="favs"></ul></section><button id="save">Save</button><footer>Source: STIB-MIVB – Open Data (CC BY 4.0)</footer></main><script>var settings = { labelLang: "en", destLang: "fr", vibrate: true, favs: [] };try { var fromHash = JSON.parse(decodeURIComponent(location.hash.slice(1))); if (fromHash && typeof fromHash === "object") settings = Object.assign(settings, fromHash);} catch (e) {}if (!Array.isArray(settings.favs)) settings.favs = [];settings.favs = settings.favs.map(function (f) { if (f && typeof f === "object" && typeof f.name === "string") { return Object.assign({}, f, { name: { fr: f.name, nl: f.name } }); } return f;}).filter(function (f) { if (!f || typeof f !== "object" || !f.name || typeof f.name !== "object") return false; return (Array.isArray(f.ids) && f.ids.length > 0) || typeof f.id === "string";}).map(function (f) { return Array.isArray(f.ids) && f.ids.length > 0 ? f : { name: f.name, ids: [f.id] };});
var el = function (id) { return document.getElementById(id); };el("labelLang").value = settings.labelLang;el("destLang").value = settings.destLang;el("vibrate").checked = !!settings.vibrate;
function localName(bilingual) { return settings.destLang === "nl" ? bilingual.nl : bilingual.fr;}
function groupHits(hits) { var order = []; var groups = {}; hits.forEach(function (h) { var key = h.name.fr; if (!groups[key]) { groups[key] = { name: h.name, ids: [], lines: 0, dirCount: 0, dirs: {} }; order.push(key); } var g = groups[key]; g.ids.push(h.id); if (typeof h.lines === "number") g.lines += h.lines; if (h.direction) { g.dirCount += 1; g.dirs[h.direction.fr + "|" + h.direction.nl] = h.direction; } }); return order.map(function (key) { var g = groups[key]; var dirKeys = Object.keys(g.dirs); g.direction = dirKeys.length === 1 ? g.dirs[dirKeys[0]] : null; delete g.dirs; return g; });}
function renderFavs() { el("favs").innerHTML = ""; settings.favs.forEach(function (f, i) { var li = document.createElement("li"); var span = document.createElement("span"); span.textContent = localName(f.name) + " · " + f.ids.length + " ids"; var btn = document.createElement("button"); btn.textContent = "remove"; btn.onclick = function () { settings.favs.splice(i, 1); renderFavs(); }; li.appendChild(span); li.appendChild(btn); el("favs").appendChild(li); });}renderFavs();
var timer = null;el("q").oninput = function () { clearTimeout(timer); var q = this.value.trim(); timer = setTimeout(function () { if (!q) { el("results").innerHTML = ""; return; } fetch("/v1/stops/search?q=" + encodeURIComponent(q)) .then(function (r) { return r.json(); }) .then(function (hits) { el("results").innerHTML = ""; groupHits(hits).forEach(function (g) { var li = document.createElement("li"); var name = localName(g.name); var label = name + " · " + g.lines + " lines · " + g.dirCount + " directions"; if (g.direction) label += " vers " + localName(g.direction); li.textContent = label; li.onclick = function () { if (settings.favs.length >= 8) return; if (!settings.favs.some(function (f) { return f.name.fr === g.name.fr; })) { settings.favs.push({ name: g.name, ids: g.ids.slice() }); } el("q").value = ""; el("results").innerHTML = ""; renderFavs(); }; el("results").appendChild(li); }); }); }, 250);};
el("save").onclick = function () { settings.labelLang = el("labelLang").value; settings.destLang = el("destLang").value; settings.vibrate = el("vibrate").checked; location.href = "pebblejs://close#" + encodeURIComponent(JSON.stringify(settings));};</script></body></html>