Self-hostable 2020 Rec Room backend
+You can switch modes later under Settings.
+(not picked)
+
+
+
+ (not picked)
+
+ { "type": "command-or-event-name", "payload": { ... }, "requestId": "optional-string" }
+/// On the JS side, ${escapeHtml(p.host)}${escapeHtml(p.v)}`;
+ el.hidden = false;
+}
+
+function renderDownloadProgress(p) {
+ // Could render a progress bar; for v0.1 just flash a status line.
+ if (!p || !p.total || p.total <= 0) return;
+ const pct = Math.round(p.fraction * 100);
+ const mb = (p.bytes / (1024 * 1024)).toFixed(1);
+ const totalMb = (p.total / (1024 * 1024)).toFixed(1);
+ const hostEl = document.getElementById('host-status');
+ const joinEl = document.getElementById('join-status');
+ const text = `Downloading… ${mb} / ${totalMb} MB (${pct}%)`;
+ if (hostEl && !hostEl.classList.contains('ok')) hostEl.textContent = text;
+ if (joinEl && !joinEl.classList.contains('ok')) joinEl.textContent = text;
+}
+
+function humanStage(stage) {
+ return ({
+ 'downloading-server': 'Downloading server binary…',
+ 'opening-tunnel': 'Opening Cloudflare tunnel…',
+ 'starting-server': 'Starting the server…',
+ 'patching-client': 'Patching your Rec Room client…',
+ 'downloading-patcher': 'Downloading client patcher…',
+ 'ready': 'Ready.',
+ 'stopped': 'Stopped.',
+ }[stage] || stage);
+}
+
+function escapeHtml(s) {
+ return String(s)
+ .replace(/&/g, '&').replace(//g, '>')
+ .replace(/"/g, '"').replace(/'/g, ''');
+}
+
+// ── Boot ─────────────────────────────────────────────────────────────
+// Render an empty 'first-run' until the C# init reply arrives.
+showView('first-run');
+bridge.send({ type: 'init' });
diff --git a/launcher/ui/index.html b/launcher/ui/index.html
new file mode 100644
index 0000000..37d0479
--- /dev/null
+++ b/launcher/ui/index.html
@@ -0,0 +1,149 @@
+
+
+
+
+ Self-hostable 2020 Rec Room backend
+You can switch modes later under Settings.
+(not picked)
+
+
+
+ (not picked)
+
+