Skip to content

Commit 5a77f2d

Browse files
committed
Add a thin boot progress bar under 'waking the city' that fills as the population loads
1 parent 473e534 commit 5a77f2d

4 files changed

Lines changed: 41 additions & 3 deletions

File tree

frontend/index.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,14 @@
2323
<!-- top-left: title (pixel font, all black) -->
2424
<div id="title" class="title">sim francisco</div>
2525

26-
<!-- simulation status: SF · residents · date, directly below the title -->
26+
<!-- simulation status: SF · residents · date (top-right) -->
2727
<div id="status" class="status">waking the city…</div>
2828

29+
<!-- boot progress: a thin line that fills while the city wakes -->
30+
<div id="boot" class="boot hidden" aria-hidden="true">
31+
<div class="boot-track"><div id="boot-fill" class="boot-fill"></div></div>
32+
</div>
33+
2934
<!-- return-to-overview button (visible only when zoomed in) -->
3035
<button id="return" class="return hidden" aria-label="Return to the whole city">
3136
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">

frontend/src/api.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const createSimulation = (overrides = {}) =>
4444
// Page through every alive agent on a branch. Returns [{id, name, lonlat, values, ...}].
4545
// `cap` is only a runaway guard; the real bound is the branch's total_matched,
4646
// which we learn from the first page — so we never silently drop agents.
47-
export async function getAllAgents(branchId, cap = 50000) {
47+
export async function getAllAgents(branchId, onProgress, cap = 50000) {
4848
const limit = 1000;
4949
let offset = 0;
5050
const out = [];
@@ -54,6 +54,7 @@ export async function getAllAgents(branchId, cap = 50000) {
5454
out.push(...batch);
5555
const total = page.total_matched ?? out.length;
5656
offset += limit;
57+
if (onProgress) onProgress(out.length, total);
5758
if (out.length >= total || batch.length < limit) break;
5859
}
5960
return out;

frontend/src/app.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const $ = (id) => document.getElementById(id);
1818
const els = {
1919
canvas: $("map"),
2020
status: $("status"),
21+
boot: $("boot"),
22+
bootFill: $("boot-fill"),
2123
returnBtn: $("return"),
2224
summary: $("summary"),
2325
summaryLabel: $("summary-label"),
@@ -61,22 +63,33 @@ function cleanupBranch() {
6163
}
6264

6365
// ── boot ───────────────────────────────────────────────────────────────
66+
// 0 → 1 progress on the thin boot bar. createSim is one slow step (~0.2); the
67+
// agent fetch is paged, so it fills the rest (0.2 → 0.97) as residents arrive.
68+
function setBoot(p) { els.bootFill.style.width = `${Math.round(Math.max(0, Math.min(1, p)) * 100)}%`; }
69+
6470
async function boot() {
6571
map.onZoomChange = (zoomedIn) => { zoomedIn ? show(els.returnBtn) : hide(els.returnBtn); };
6672
map.start();
6773
els.status.textContent = "waking the city…";
74+
show(els.boot); setBoot(0.06);
6875
try {
6976
const sim = await api.createSimulation();
7077
state.simId = sim.simulation_id;
7178
state.mainBranch = sim.main_branch;
72-
const agents = await api.getAllAgents(state.mainBranch);
79+
setBoot(0.2);
80+
const agents = await api.getAllAgents(state.mainBranch, (loaded, total) => {
81+
setBoot(0.2 + 0.77 * (total ? loaded / total : 0));
82+
});
7383
if (!agents.length) throw new Error("no agents returned");
7484
map.setAgents(agents);
7585
state.residents = agents.length;
86+
setBoot(1);
87+
setTimeout(() => hide(els.boot), 450); // let the bar finish, then fade out
7688
setIdleStatus();
7789
state.phase = "idle";
7890
} catch (err) {
7991
console.error(err);
92+
hide(els.boot);
8093
map.setAgents(fallbackAgents(SIM.n)); // never leave an empty city
8194
els.status.textContent = "offline preview · backend unreachable";
8295
toast("Couldn't reach the backend — showing an offline preview.");

frontend/styles.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,24 @@ body {
8787
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
8888
}
8989

90+
/* ── boot progress (thin line under the status while the city wakes) ────── */
91+
.boot {
92+
position: absolute; top: 58px; right: 20px;
93+
width: min(210px, 48vw);
94+
pointer-events: none;
95+
}
96+
.boot-track {
97+
height: 5px; border-radius: 999px; overflow: hidden;
98+
background: rgba(255, 255, 255, 0.55);
99+
border: 1px solid var(--glass-edge);
100+
box-shadow: inset 0 1px 0 var(--glass-hi), 0 2px 6px rgba(20, 20, 20, 0.1);
101+
}
102+
.boot-fill {
103+
height: 100%; width: 5%; border-radius: 999px;
104+
background: linear-gradient(90deg, #3a3a3a, var(--ink));
105+
transition: width .35s var(--ease);
106+
}
107+
90108
/* ── return-to-overview button (top-left, below the title with breathing room) ── */
91109
.return {
92110
position: absolute; top: 82px; left: 20px;
@@ -301,6 +319,7 @@ body {
301319
@media (max-width: 560px) {
302320
.title { top: 12px; left: 12px; font-size: 19px; padding: 8px 12px 7px; border-radius: 11px; }
303321
.status { top: 14px; right: 12px; font-size: 11px; max-width: 52vw; padding: 6px 11px; }
322+
.boot { top: 46px; right: 12px; width: min(150px, 52vw); }
304323
.return { top: 52px; left: 12px; font-size: 12px; padding: 6px 11px 6px 9px; }
305324

306325
.dock { bottom: 16px; gap: 9px; max-width: 96vw; }

0 commit comments

Comments
 (0)