onelobby 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -2
  3. package/dist/chat.d.ts +60 -0
  4. package/dist/chat.js +163 -0
  5. package/dist/chat.js.map +1 -0
  6. package/dist/chunking.d.ts +17 -0
  7. package/dist/chunking.js +59 -0
  8. package/dist/chunking.js.map +1 -0
  9. package/dist/clock.d.ts +8 -0
  10. package/dist/clock.js +39 -0
  11. package/dist/clock.js.map +1 -0
  12. package/dist/codec.d.ts +141 -0
  13. package/dist/codec.js +308 -0
  14. package/dist/codec.js.map +1 -0
  15. package/dist/crockford.d.ts +14 -0
  16. package/dist/crockford.js +55 -0
  17. package/dist/crockford.js.map +1 -0
  18. package/dist/deflate.d.ts +4 -0
  19. package/dist/deflate.js +32 -0
  20. package/dist/deflate.js.map +1 -0
  21. package/dist/deprecated.d.ts +18 -0
  22. package/dist/deprecated.js +11 -0
  23. package/dist/deprecated.js.map +1 -0
  24. package/dist/direct/hub-host.d.ts +82 -0
  25. package/dist/direct/hub-host.js +393 -0
  26. package/dist/direct/hub-host.js.map +1 -0
  27. package/dist/direct/hub.d.ts +178 -0
  28. package/dist/direct/hub.js +862 -0
  29. package/dist/direct/hub.js.map +1 -0
  30. package/dist/direct/index.d.ts +57 -0
  31. package/dist/direct/index.js +71 -0
  32. package/dist/direct/index.js.map +1 -0
  33. package/dist/direct/portmap.d.ts +84 -0
  34. package/dist/direct/portmap.js +465 -0
  35. package/dist/direct/portmap.js.map +1 -0
  36. package/dist/direct-address.d.ts +20 -0
  37. package/dist/direct-address.js +61 -0
  38. package/dist/direct-address.js.map +1 -0
  39. package/dist/emitter.d.ts +9 -0
  40. package/dist/emitter.js +36 -0
  41. package/dist/emitter.js.map +1 -0
  42. package/dist/friendly.d.ts +20 -0
  43. package/dist/friendly.js +120 -0
  44. package/dist/friendly.js.map +1 -0
  45. package/dist/global.d.ts +8 -0
  46. package/dist/global.js +9 -0
  47. package/dist/global.js.map +1 -0
  48. package/dist/gridpaint.d.ts +27 -0
  49. package/dist/gridpaint.js +106 -0
  50. package/dist/gridpaint.js.map +1 -0
  51. package/dist/hub.d.ts +25 -0
  52. package/dist/hub.js +2 -0
  53. package/dist/hub.js.map +1 -0
  54. package/dist/index.d.ts +35 -0
  55. package/dist/index.js +25 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/internals.d.ts +28 -0
  58. package/dist/internals.js +24 -0
  59. package/dist/internals.js.map +1 -0
  60. package/dist/joincode.d.ts +21 -0
  61. package/dist/joincode.js +84 -0
  62. package/dist/joincode.js.map +1 -0
  63. package/dist/lobby.d.ts +203 -0
  64. package/dist/lobby.js +840 -0
  65. package/dist/lobby.js.map +1 -0
  66. package/dist/lockstep.d.ts +132 -0
  67. package/dist/lockstep.js +471 -0
  68. package/dist/lockstep.js.map +1 -0
  69. package/dist/migration.d.ts +17 -0
  70. package/dist/migration.js +34 -0
  71. package/dist/migration.js.map +1 -0
  72. package/dist/onelobby.bundle.mjs +140 -0
  73. package/dist/onelobby.bundle.mjs.map +7 -0
  74. package/dist/onelobby.d.ts +151 -0
  75. package/dist/onelobby.global.js +3897 -0
  76. package/dist/onelobby.global.js.map +7 -0
  77. package/dist/onelobby.global.min.js +140 -0
  78. package/dist/onelobby.global.min.js.map +7 -0
  79. package/dist/onelobby.js +337 -0
  80. package/dist/onelobby.js.map +1 -0
  81. package/dist/signaling.d.ts +94 -0
  82. package/dist/signaling.js +134 -0
  83. package/dist/signaling.js.map +1 -0
  84. package/dist/socket.d.ts +63 -0
  85. package/dist/socket.js +142 -0
  86. package/dist/socket.js.map +1 -0
  87. package/dist/testing.d.ts +135 -0
  88. package/dist/testing.js +438 -0
  89. package/dist/testing.js.map +1 -0
  90. package/dist/transport.d.ts +80 -0
  91. package/dist/transport.js +7 -0
  92. package/dist/transport.js.map +1 -0
  93. package/dist/ui/controller.d.ts +137 -0
  94. package/dist/ui/controller.js +337 -0
  95. package/dist/ui/controller.js.map +1 -0
  96. package/dist/ui/element.d.ts +22 -0
  97. package/dist/ui/element.js +284 -0
  98. package/dist/ui/element.js.map +1 -0
  99. package/dist/ui/index.d.ts +5 -0
  100. package/dist/ui/index.js +9 -0
  101. package/dist/ui/index.js.map +1 -0
  102. package/dist/ui/server-url.d.ts +9 -0
  103. package/dist/ui/server-url.js +39 -0
  104. package/dist/ui/server-url.js.map +1 -0
  105. package/dist/ui/styles.d.ts +2 -0
  106. package/dist/ui/styles.js +70 -0
  107. package/dist/ui/styles.js.map +1 -0
  108. package/dist/voice-browser.d.ts +19 -0
  109. package/dist/voice-browser.js +151 -0
  110. package/dist/voice-browser.js.map +1 -0
  111. package/dist/voice-core.d.ts +98 -0
  112. package/dist/voice-core.js +233 -0
  113. package/dist/voice-core.js.map +1 -0
  114. package/dist/voice-types.d.ts +35 -0
  115. package/dist/voice-types.js +3 -0
  116. package/dist/voice-types.js.map +1 -0
  117. package/dist/voice.d.ts +68 -0
  118. package/dist/voice.js +223 -0
  119. package/dist/voice.js.map +1 -0
  120. package/dist/webrtc.d.ts +29 -0
  121. package/dist/webrtc.js +169 -0
  122. package/dist/webrtc.js.map +1 -0
  123. package/package.json +93 -4
@@ -0,0 +1,284 @@
1
+ /**
2
+ * `<one-lobby>`: a drop-in lobby (host, join by code or invite link, player list, start) for people who paste
3
+ * HTML rather than write code. Zero dependencies; shadow DOM; themed with CSS custom properties.
4
+ *
5
+ * <one-lobby server="https://your-name.onrender.com" game="my-game" max-players="4">fallback text</one-lobby>
6
+ *
7
+ * Game API: el.send(data), el.shared.set/add/get, el.isHost, el.players, events message / player-joined /
8
+ * player-left / start / error / shared-change (all bubble, `event.detail` carries the data).
9
+ */
10
+ import { formatCode } from "../crockford.js";
11
+ import { OneLobby } from "../onelobby.js";
12
+ import { OneLobbyError } from "../signaling.js";
13
+ import { LobbyController } from "./controller.js";
14
+ import { codeFromUrl, shareUrl } from "../friendly.js";
15
+ import { LOBBY_CSS } from "./styles.js";
16
+ import { lobbyServerUrl } from "./server-url.js";
17
+ export const LOBBY_TAG = "one-lobby";
18
+ const NAME_KEY = "onelobby-name";
19
+ const TEMPLATE = `
20
+ <div class="card" part="card">
21
+ <p class="status" id="status" role="status" aria-live="polite"></p>
22
+ <p class="hint" id="hint" hidden></p>
23
+
24
+ <div id="v-idle" class="view">
25
+ <div class="row">
26
+ <label id="nameLabel">Your name
27
+ <input id="name" maxlength="24" autocomplete="nickname" placeholder="e.g. Sam">
28
+ </label>
29
+ </div>
30
+ <div class="row" style="margin-top:.75rem">
31
+ <button id="host" class="primary" type="button">Host a game</button>
32
+ </div>
33
+ <p class="or">or join a friend</p>
34
+ <form id="joinForm" class="row">
35
+ <label>Game code
36
+ <input id="code" maxlength="9" autocomplete="off" autocapitalize="characters" spellcheck="false" placeholder="ABC 123">
37
+ </label>
38
+ <button id="join" type="submit">Join</button>
39
+ </form>
40
+ </div>
41
+
42
+ <div id="v-busy" class="view" hidden>
43
+ <button id="cancel" type="button">Cancel</button>
44
+ </div>
45
+
46
+ <div id="v-lobby" class="view" hidden>
47
+ <div class="codebox">
48
+ <h3>Game code</h3>
49
+ <span class="code" id="codeText"></span>
50
+ <div class="row">
51
+ <button id="copyCode" type="button">Copy code</button>
52
+ <button id="copyLink" type="button">Copy invite link</button>
53
+ </div>
54
+ <span class="sr" id="copied" aria-live="polite"></span>
55
+ </div>
56
+ <h3 id="playersTitle" style="margin-top:.75rem">Players</h3>
57
+ <ul id="players" aria-labelledby="playersTitle"></ul>
58
+ <div class="bar" style="margin-top:.75rem">
59
+ <button id="start" class="primary" type="button">Start</button>
60
+ <button id="leave" type="button">Leave</button>
61
+ </div>
62
+ </div>
63
+
64
+ <div id="v-error" class="view" hidden>
65
+ <div class="row">
66
+ <button id="retry" class="primary" type="button">Try again</button>
67
+ <button id="back" type="button">Back</button>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ `;
72
+ function readStored() {
73
+ try {
74
+ return localStorage.getItem(NAME_KEY) ?? "";
75
+ }
76
+ catch {
77
+ return "";
78
+ }
79
+ }
80
+ function store(name) {
81
+ try {
82
+ localStorage.setItem(NAME_KEY, name);
83
+ }
84
+ catch {
85
+ /* private window */
86
+ }
87
+ }
88
+ async function copyText(text) {
89
+ try {
90
+ await navigator.clipboard.writeText(text);
91
+ return true;
92
+ }
93
+ catch {
94
+ const ta = document.createElement("textarea");
95
+ ta.value = text;
96
+ ta.style.position = "fixed";
97
+ ta.style.opacity = "0";
98
+ document.body.appendChild(ta);
99
+ ta.select();
100
+ let ok = false;
101
+ try {
102
+ ok = document.execCommand("copy");
103
+ }
104
+ catch {
105
+ ok = false;
106
+ }
107
+ ta.remove();
108
+ return ok;
109
+ }
110
+ }
111
+ function makeClass() {
112
+ class OneLobbyBox extends HTMLElement {
113
+ static observedAttributes = ["name", "theme"];
114
+ /** @internal exposed for debugging and tests */
115
+ _controller;
116
+ root;
117
+ $ = (id) => this.root.getElementById(id);
118
+ autoJoined = false;
119
+ copiedTimer;
120
+ constructor() {
121
+ super();
122
+ this.root = this.attachShadow({ mode: "open" });
123
+ this.root.innerHTML = `<style>${LOBBY_CSS}</style>${TEMPLATE}`;
124
+ this._controller = new LobbyController({ makeKit: (name) => this.makeKit(name), maxPlayers: () => this.maxPlayers });
125
+ this._controller.onChange = () => this.render();
126
+ this._controller.onEvent = (type, detail) => this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
127
+ this.wire();
128
+ }
129
+ // ---- public API ----------------------------------------------------------------------------------------------
130
+ get isHost() { return this._controller.isHost; }
131
+ get players() { return this._controller.players; }
132
+ get code() { return this._controller.code; }
133
+ get state() { return this._controller.state; }
134
+ get shared() { return this._controller.shared; }
135
+ send(data) { this._controller.send(data); }
136
+ host() { return this._controller.host(this.playerName()); }
137
+ join(code) { return this._controller.join(code, this.playerName()); }
138
+ start() { return this._controller.start(); }
139
+ leave() { return this._controller.leave(); }
140
+ // ---- lifecycle -----------------------------------------------------------------------------------------------
141
+ connectedCallback() {
142
+ const name = this.getAttribute("name");
143
+ const input = this.$("name");
144
+ if (!input.value)
145
+ input.value = name ?? readStored();
146
+ this.$("nameLabel").hidden = !!name;
147
+ this.render();
148
+ if (!this.autoJoined) {
149
+ this.autoJoined = true;
150
+ const code = codeFromUrl();
151
+ if (code && this._controller.state === "idle") {
152
+ this.$("code").value = formatCode(code);
153
+ if (input.value.trim())
154
+ void this.join(code);
155
+ else {
156
+ this.$("status").textContent = "You're invited! Type your name, then press Join.";
157
+ input.focus();
158
+ }
159
+ }
160
+ else if (!code && this._controller.state === "idle" && lobbyServerUrl(this.getAttribute("server"))) {
161
+ // A reload in the middle of a game: quietly take this tab's seat back (OneLobby rememberSeat).
162
+ void this._controller.resume(input.value.trim());
163
+ }
164
+ }
165
+ }
166
+ disconnectedCallback() {
167
+ // Leaving the page section (or the page) ends this player's session.
168
+ if (this._controller.lobby)
169
+ void this._controller.leave();
170
+ }
171
+ attributeChangedCallback(attr) {
172
+ if (attr === "name" && this.isConnected) {
173
+ const v = this.getAttribute("name");
174
+ if (v)
175
+ this.$("name").value = v;
176
+ this.$("nameLabel").hidden = !!v;
177
+ }
178
+ }
179
+ // ---- internals -----------------------------------------------------------------------------------------------
180
+ get maxPlayers() {
181
+ const n = parseInt(this.getAttribute("max-players") ?? "", 10);
182
+ return Number.isFinite(n) && n >= 2 ? Math.min(n, 250) : 8;
183
+ }
184
+ playerName() {
185
+ // empty is fine: other players then see "Player 2" and so on
186
+ const v = this.$("name").value.trim() || this.getAttribute("name") || "";
187
+ if (!this.getAttribute("name") && v)
188
+ store(v);
189
+ return v;
190
+ }
191
+ makeKit(name) {
192
+ // missing, still the guide's placeholder, or not an address: say so instead of "couldn't reach the server"
193
+ const server = lobbyServerUrl(this.getAttribute("server"));
194
+ if (!server)
195
+ throw new OneLobbyError("no_server", "the <one-lobby> tag has no usable server address");
196
+ return new OneLobby({
197
+ gameId: this.getAttribute("game") || "onelobby-drop-in",
198
+ netVersion: this.getAttribute("version") || "1",
199
+ lobbyServer: server,
200
+ mode: "raw",
201
+ displayName: name,
202
+ // joiners give up (with a friendly "couldn't connect" and a retry button) after this long
203
+ readyTimeoutMs: 15_000,
204
+ advanced: { createMic: null, createSink: null, voiceCodec: null },
205
+ });
206
+ }
207
+ wire() {
208
+ const on = (id, ev, fn) => this.$(id).addEventListener(ev, fn);
209
+ on("host", "click", () => void this.host());
210
+ on("joinForm", "submit", (e) => {
211
+ e.preventDefault();
212
+ void this.join(this.$("code").value);
213
+ });
214
+ on("cancel", "click", () => this._controller.reset());
215
+ on("start", "click", () => void this.start());
216
+ on("leave", "click", () => void this.leave());
217
+ on("retry", "click", () => void this._controller.retry());
218
+ on("back", "click", () => this._controller.reset());
219
+ on("copyCode", "click", () => void this.copy(this.code ?? "", "copyCode", "Copied!"));
220
+ on("copyLink", "click", () => void this.copy(this.code ? shareUrl(this.code) : "", "copyLink", "Link copied!"));
221
+ }
222
+ async copy(text, id, done) {
223
+ if (!text)
224
+ return;
225
+ const ok = await copyText(text);
226
+ const btn = this.$(id);
227
+ const label = id === "copyCode" ? "Copy code" : "Copy invite link";
228
+ btn.textContent = ok ? done : "Couldn't copy, select it by hand";
229
+ this.$("copied").textContent = ok ? done : "";
230
+ clearTimeout(this.copiedTimer);
231
+ this.copiedTimer = setTimeout(() => {
232
+ btn.textContent = label;
233
+ this.$("copied").textContent = "";
234
+ }, 2000);
235
+ }
236
+ render() {
237
+ const c = this._controller;
238
+ const s = c.state;
239
+ const status = this.$("status");
240
+ status.textContent = s === "error" && c.error?.code === "no_server"
241
+ ? "This page is missing its lobby server address."
242
+ : c.statusText;
243
+ status.classList.toggle("error", s === "error");
244
+ const hint = this.$("hint");
245
+ const hintText = s === "error"
246
+ ? (c.error?.code === "no_server" ? 'Put your lobby server address in server="…" on the <one-lobby> tag (it looks like https://your-name.onrender.com). The guide shows how to get one.' : c.error?.hint ?? "")
247
+ : "";
248
+ hint.textContent = hintText;
249
+ hint.hidden = !hintText;
250
+ this.$("v-idle").hidden = s !== "idle";
251
+ this.$("v-busy").hidden = s !== "connecting";
252
+ this.$("v-lobby").hidden = s !== "lobby" && s !== "playing";
253
+ this.$("v-error").hidden = s !== "error";
254
+ this.setAttribute("state", s);
255
+ if (s === "lobby" || s === "playing") {
256
+ this.$("codeText").textContent = formatCode(c.code ?? "");
257
+ const ul = this.$("players");
258
+ ul.replaceChildren(...c.players.map((p) => {
259
+ const li = document.createElement("li");
260
+ const nm = document.createElement("span");
261
+ nm.textContent = p.name;
262
+ li.append(nm);
263
+ if (p.isHost)
264
+ li.append(Object.assign(document.createElement("span"), { className: "tag", textContent: "host" }));
265
+ if (p.isMe)
266
+ li.append(Object.assign(document.createElement("span"), { className: "tag", textContent: "you" }));
267
+ return li;
268
+ }));
269
+ const start = this.$("start");
270
+ start.hidden = !c.isHost || s === "playing";
271
+ start.disabled = c.players.length < 2;
272
+ start.title = start.disabled ? "Wait for at least one more player" : "";
273
+ }
274
+ }
275
+ }
276
+ return OneLobbyBox;
277
+ }
278
+ /** Register `<one-lobby>` (safe to call more than once; a no-op outside a browser). */
279
+ export function defineLobbyElement(tag = LOBBY_TAG) {
280
+ if (typeof customElements === "undefined" || customElements.get(tag))
281
+ return;
282
+ customElements.define(tag, makeClass());
283
+ }
284
+ //# sourceMappingURL=element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.js","sourceRoot":"","sources":["../../src/ui/element.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAA+C,MAAM,iBAAiB,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,CAAC,MAAM,SAAS,GAAG,WAAW,CAAC;AACrC,MAAM,QAAQ,GAAG,eAAe,CAAC;AAEjC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoDhB,CAAC;AAkBF,SAAS,UAAU;IACjB,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AACD,SAAS,KAAK,CAAC,IAAY;IACzB,IAAI,CAAC;QACH,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,oBAAoB;IACtB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,IAAY;IAClC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAC9C,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC;QAChB,EAAE,CAAC,KAAK,CAAC,QAAQ,GAAG,OAAO,CAAC;QAC5B,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC;QACvB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAC9B,EAAE,CAAC,MAAM,EAAE,CAAC;QACZ,IAAI,EAAE,GAAG,KAAK,CAAC;QACf,IAAI,CAAC;YACH,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,EAAE,GAAG,KAAK,CAAC;QACb,CAAC;QACD,EAAE,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,WAAY,SAAQ,WAAW;QACnC,MAAM,CAAC,kBAAkB,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC9C,gDAAgD;QACvC,WAAW,CAAkB;QAC9B,IAAI,CAAa;QACjB,CAAC,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAgB,CAAC;QAChE,UAAU,GAAG,KAAK,CAAC;QACnB,WAAW,CAA4C;QAE/D;YACE,KAAK,EAAE,CAAC;YACR,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAChD,IAAI,CAAC,IAAI,CAAC,SAAS,GAAG,UAAU,SAAS,WAAW,QAAQ,EAAE,CAAC;YAC/D,IAAI,CAAC,WAAW,GAAG,IAAI,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;YACrH,IAAI,CAAC,WAAW,CAAC,QAAQ,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAChD,IAAI,CAAC,WAAW,CAAC,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YAClI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;QAED,iHAAiH;QAEjH,IAAI,MAAM,KAAc,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QACzD,IAAI,OAAO,KAAe,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5D,IAAI,IAAI,KAAoB,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3D,IAAI,KAAK,KAAc,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;QACvD,IAAI,MAAM,KAAkB,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAa,IAAU,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1D,IAAI,KAAoB,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1E,IAAI,CAAC,IAAY,IAAmB,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5F,KAAK,KAAoB,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC3D,KAAK,KAAoB,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAE3D,iHAAiH;QAEjH,iBAAiB;YACf,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YACvC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAqB,CAAC;YACjD,IAAI,CAAC,KAAK,CAAC,KAAK;gBAAE,KAAK,CAAC,KAAK,GAAG,IAAI,IAAI,UAAU,EAAE,CAAC;YACrD,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC;YACpC,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;gBACrB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;gBACvB,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;gBAC3B,IAAI,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;oBAC7C,IAAI,CAAC,CAAC,CAAC,MAAM,CAAsB,CAAC,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;oBAC9D,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE;wBAAE,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;yBACxC,CAAC;wBACJ,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW,GAAG,kDAAkD,CAAC;wBAClF,KAAK,CAAC,KAAK,EAAE,CAAC;oBAChB,CAAC;gBACH,CAAC;qBAAM,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,KAAK,MAAM,IAAI,cAAc,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;oBACrG,+FAA+F;oBAC/F,KAAK,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;gBACnD,CAAC;YACH,CAAC;QACH,CAAC;QAED,oBAAoB;YAClB,qEAAqE;YACrE,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK;gBAAE,KAAK,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QAC5D,CAAC;QAED,wBAAwB,CAAC,IAAY;YACnC,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBACxC,MAAM,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;gBACpC,IAAI,CAAC;oBAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAsB,CAAC,KAAK,GAAG,CAAC,CAAC;gBACtD,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QAED,iHAAiH;QAEjH,IAAY,UAAU;YACpB,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YAC/D,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7D,CAAC;QAEO,UAAU;YAChB,6DAA6D;YAC7D,MAAM,CAAC,GAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAsB,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAC/F,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC;gBAAE,KAAK,CAAC,CAAC,CAAC,CAAC;YAC9C,OAAO,CAAC,CAAC;QACX,CAAC;QAEO,OAAO,CAAC,IAAY;YAC1B,2GAA2G;YAC3G,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC3D,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,aAAa,CAAC,WAAW,EAAE,kDAAkD,CAAC,CAAC;YACtG,OAAO,IAAI,QAAQ,CAAC;gBAClB,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,kBAAkB;gBACvD,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,GAAG;gBAC/C,WAAW,EAAE,MAAM;gBACnB,IAAI,EAAE,KAAK;gBACX,WAAW,EAAE,IAAI;gBACjB,0FAA0F;gBAC1F,cAAc,EAAE,MAAM;gBACtB,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;aAClE,CAAC,CAAC;QACL,CAAC;QAEO,IAAI;YACV,MAAM,EAAE,GAAG,CAAC,EAAU,EAAE,EAAU,EAAE,EAAsB,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,gBAAgB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YACnG,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAC5C,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;gBAC7B,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,KAAK,IAAI,CAAC,IAAI,CAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAsB,CAAC,KAAK,CAAC,CAAC;YAC7D,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;YACtD,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;YAC9C,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;YAC9C,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;YAC1D,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;YACpD,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;YACtF,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC;QAClH,CAAC;QAEO,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,EAAU,EAAE,IAAY;YACvD,IAAI,CAAC,IAAI;gBAAE,OAAO;YAClB,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;YAChC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACvB,MAAM,KAAK,GAAG,EAAE,KAAK,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,kBAAkB,CAAC;YACnE,GAAG,CAAC,WAAW,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,kCAAkC,CAAC;YACjE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC/B,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC,GAAG,EAAE;gBACjC,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC;gBACxB,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW,GAAG,EAAE,CAAC;YACpC,CAAC,EAAE,IAAI,CAAC,CAAC;QACX,CAAC;QAEO,MAAM;YACZ,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC;YAC3B,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;YAClB,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YAChC,MAAM,CAAC,WAAW,GAAG,CAAC,KAAK,OAAO,IAAI,CAAC,CAAC,KAAK,EAAE,IAAI,KAAK,WAAW;gBACjE,CAAC,CAAC,gDAAgD;gBAClD,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;YACjB,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC;YAChD,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAC5B,MAAM,QAAQ,GAAG,CAAC,KAAK,OAAO;gBAC5B,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,oJAAoJ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC;gBAC9M,CAAC,CAAC,EAAE,CAAC;YACP,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAC;YAC5B,IAAI,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC;YAExB,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,KAAK,MAAM,CAAC;YACvC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,KAAK,YAAY,CAAC;YAC7C,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,SAAS,CAAC;YAC5D,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,KAAK,OAAO,CAAC;YACzC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAE9B,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;gBACrC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;gBAC1D,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;gBAC7B,EAAE,CAAC,eAAe,CAChB,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;oBACrB,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;oBACxC,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;oBAC1C,EAAE,CAAC,WAAW,GAAG,CAAC,CAAC,IAAI,CAAC;oBACxB,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;oBACd,IAAI,CAAC,CAAC,MAAM;wBAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;oBAClH,IAAI,CAAC,CAAC,IAAI;wBAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;oBAC/G,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CACH,CAAC;gBACF,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAsB,CAAC;gBACnD,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,KAAK,SAAS,CAAC;gBAC5C,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;gBACtC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,mCAAmC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1E,CAAC;QACH,CAAC;;IAEH,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,kBAAkB,CAAC,GAAG,GAAG,SAAS;IAChD,IAAI,OAAO,cAAc,KAAK,WAAW,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO;IAC7E,cAAc,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;AAC1C,CAAC","sourcesContent":["/**\n * `<one-lobby>`: a drop-in lobby (host, join by code or invite link, player list, start) for people who paste\n * HTML rather than write code. Zero dependencies; shadow DOM; themed with CSS custom properties.\n *\n * <one-lobby server=\"https://your-name.onrender.com\" game=\"my-game\" max-players=\"4\">fallback text</one-lobby>\n *\n * Game API: el.send(data), el.shared.set/add/get, el.isHost, el.players, events message / player-joined /\n * player-left / start / error / shared-change (all bubble, `event.detail` carries the data).\n */\nimport { formatCode } from \"../crockford.js\";\nimport { OneLobby } from \"../onelobby.js\";\nimport { OneLobbyError } from \"../signaling.js\";\nimport { LobbyController, type Player, type SharedState, type UiState } from \"./controller.js\";\nimport { codeFromUrl, shareUrl } from \"../friendly.js\";\nimport { LOBBY_CSS } from \"./styles.js\";\nimport { lobbyServerUrl } from \"./server-url.js\";\n\nexport const LOBBY_TAG = \"one-lobby\";\nconst NAME_KEY = \"onelobby-name\";\n\nconst TEMPLATE = `\n<div class=\"card\" part=\"card\">\n <p class=\"status\" id=\"status\" role=\"status\" aria-live=\"polite\"></p>\n <p class=\"hint\" id=\"hint\" hidden></p>\n\n <div id=\"v-idle\" class=\"view\">\n <div class=\"row\">\n <label id=\"nameLabel\">Your name\n <input id=\"name\" maxlength=\"24\" autocomplete=\"nickname\" placeholder=\"e.g. Sam\">\n </label>\n </div>\n <div class=\"row\" style=\"margin-top:.75rem\">\n <button id=\"host\" class=\"primary\" type=\"button\">Host a game</button>\n </div>\n <p class=\"or\">or join a friend</p>\n <form id=\"joinForm\" class=\"row\">\n <label>Game code\n <input id=\"code\" maxlength=\"9\" autocomplete=\"off\" autocapitalize=\"characters\" spellcheck=\"false\" placeholder=\"ABC 123\">\n </label>\n <button id=\"join\" type=\"submit\">Join</button>\n </form>\n </div>\n\n <div id=\"v-busy\" class=\"view\" hidden>\n <button id=\"cancel\" type=\"button\">Cancel</button>\n </div>\n\n <div id=\"v-lobby\" class=\"view\" hidden>\n <div class=\"codebox\">\n <h3>Game code</h3>\n <span class=\"code\" id=\"codeText\"></span>\n <div class=\"row\">\n <button id=\"copyCode\" type=\"button\">Copy code</button>\n <button id=\"copyLink\" type=\"button\">Copy invite link</button>\n </div>\n <span class=\"sr\" id=\"copied\" aria-live=\"polite\"></span>\n </div>\n <h3 id=\"playersTitle\" style=\"margin-top:.75rem\">Players</h3>\n <ul id=\"players\" aria-labelledby=\"playersTitle\"></ul>\n <div class=\"bar\" style=\"margin-top:.75rem\">\n <button id=\"start\" class=\"primary\" type=\"button\">Start</button>\n <button id=\"leave\" type=\"button\">Leave</button>\n </div>\n </div>\n\n <div id=\"v-error\" class=\"view\" hidden>\n <div class=\"row\">\n <button id=\"retry\" class=\"primary\" type=\"button\">Try again</button>\n <button id=\"back\" type=\"button\">Back</button>\n </div>\n </div>\n</div>\n`;\n// No <slot>: text placed inside <one-lobby> is a fallback that shows only while the script has not loaded.\n\n/** The element's public surface (for TypeScript users). */\nexport interface OneLobbyElement extends HTMLElement {\n readonly isHost: boolean;\n readonly players: Player[];\n readonly code: string | null;\n readonly state: UiState;\n readonly shared: SharedState;\n send(data: unknown): void;\n host(): Promise<void>;\n join(code: string): Promise<void>;\n start(): Promise<void>;\n leave(): Promise<void>;\n}\n\n\nfunction readStored(): string {\n try {\n return localStorage.getItem(NAME_KEY) ?? \"\";\n } catch {\n return \"\";\n }\n}\nfunction store(name: string): void {\n try {\n localStorage.setItem(NAME_KEY, name);\n } catch {\n /* private window */\n }\n}\n\nasync function copyText(text: string): Promise<boolean> {\n try {\n await navigator.clipboard.writeText(text);\n return true;\n } catch {\n const ta = document.createElement(\"textarea\");\n ta.value = text;\n ta.style.position = \"fixed\";\n ta.style.opacity = \"0\";\n document.body.appendChild(ta);\n ta.select();\n let ok = false;\n try {\n ok = document.execCommand(\"copy\");\n } catch {\n ok = false;\n }\n ta.remove();\n return ok;\n }\n}\n\nfunction makeClass(): CustomElementConstructor {\n class OneLobbyBox extends HTMLElement implements OneLobbyElement {\n static observedAttributes = [\"name\", \"theme\"];\n /** @internal exposed for debugging and tests */\n readonly _controller: LobbyController;\n private root: ShadowRoot;\n private $ = (id: string) => this.root.getElementById(id) as HTMLElement;\n private autoJoined = false;\n private copiedTimer: ReturnType<typeof setTimeout> | undefined;\n\n constructor() {\n super();\n this.root = this.attachShadow({ mode: \"open\" });\n this.root.innerHTML = `<style>${LOBBY_CSS}</style>${TEMPLATE}`;\n this._controller = new LobbyController({ makeKit: (name) => this.makeKit(name), maxPlayers: () => this.maxPlayers });\n this._controller.onChange = () => this.render();\n this._controller.onEvent = (type, detail) => this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));\n this.wire();\n }\n\n // ---- public API ----------------------------------------------------------------------------------------------\n\n get isHost(): boolean { return this._controller.isHost; }\n get players(): Player[] { return this._controller.players; }\n get code(): string | null { return this._controller.code; }\n get state(): UiState { return this._controller.state; }\n get shared(): SharedState { return this._controller.shared; }\n send(data: unknown): void { this._controller.send(data); }\n host(): Promise<void> { return this._controller.host(this.playerName()); }\n join(code: string): Promise<void> { return this._controller.join(code, this.playerName()); }\n start(): Promise<void> { return this._controller.start(); }\n leave(): Promise<void> { return this._controller.leave(); }\n\n // ---- lifecycle -----------------------------------------------------------------------------------------------\n\n connectedCallback(): void {\n const name = this.getAttribute(\"name\");\n const input = this.$(\"name\") as HTMLInputElement;\n if (!input.value) input.value = name ?? readStored();\n this.$(\"nameLabel\").hidden = !!name;\n this.render();\n if (!this.autoJoined) {\n this.autoJoined = true;\n const code = codeFromUrl();\n if (code && this._controller.state === \"idle\") {\n (this.$(\"code\") as HTMLInputElement).value = formatCode(code);\n if (input.value.trim()) void this.join(code);\n else {\n this.$(\"status\").textContent = \"You're invited! Type your name, then press Join.\";\n input.focus();\n }\n } else if (!code && this._controller.state === \"idle\" && lobbyServerUrl(this.getAttribute(\"server\"))) {\n // A reload in the middle of a game: quietly take this tab's seat back (OneLobby rememberSeat).\n void this._controller.resume(input.value.trim());\n }\n }\n }\n\n disconnectedCallback(): void {\n // Leaving the page section (or the page) ends this player's session.\n if (this._controller.lobby) void this._controller.leave();\n }\n\n attributeChangedCallback(attr: string): void {\n if (attr === \"name\" && this.isConnected) {\n const v = this.getAttribute(\"name\");\n if (v) (this.$(\"name\") as HTMLInputElement).value = v;\n this.$(\"nameLabel\").hidden = !!v;\n }\n }\n\n // ---- internals -----------------------------------------------------------------------------------------------\n\n private get maxPlayers(): number {\n const n = parseInt(this.getAttribute(\"max-players\") ?? \"\", 10);\n return Number.isFinite(n) && n >= 2 ? Math.min(n, 250) : 8;\n }\n\n private playerName(): string {\n // empty is fine: other players then see \"Player 2\" and so on\n const v = (this.$(\"name\") as HTMLInputElement).value.trim() || this.getAttribute(\"name\") || \"\";\n if (!this.getAttribute(\"name\") && v) store(v);\n return v;\n }\n\n private makeKit(name: string): OneLobby {\n // missing, still the guide's placeholder, or not an address: say so instead of \"couldn't reach the server\"\n const server = lobbyServerUrl(this.getAttribute(\"server\"));\n if (!server) throw new OneLobbyError(\"no_server\", \"the <one-lobby> tag has no usable server address\");\n return new OneLobby({\n gameId: this.getAttribute(\"game\") || \"onelobby-drop-in\",\n netVersion: this.getAttribute(\"version\") || \"1\",\n lobbyServer: server,\n mode: \"raw\",\n displayName: name,\n // joiners give up (with a friendly \"couldn't connect\" and a retry button) after this long\n readyTimeoutMs: 15_000,\n advanced: { createMic: null, createSink: null, voiceCodec: null },\n });\n }\n\n private wire(): void {\n const on = (id: string, ev: string, fn: (e: Event) => void) => this.$(id).addEventListener(ev, fn);\n on(\"host\", \"click\", () => void this.host());\n on(\"joinForm\", \"submit\", (e) => {\n e.preventDefault();\n void this.join((this.$(\"code\") as HTMLInputElement).value);\n });\n on(\"cancel\", \"click\", () => this._controller.reset());\n on(\"start\", \"click\", () => void this.start());\n on(\"leave\", \"click\", () => void this.leave());\n on(\"retry\", \"click\", () => void this._controller.retry());\n on(\"back\", \"click\", () => this._controller.reset());\n on(\"copyCode\", \"click\", () => void this.copy(this.code ?? \"\", \"copyCode\", \"Copied!\"));\n on(\"copyLink\", \"click\", () => void this.copy(this.code ? shareUrl(this.code) : \"\", \"copyLink\", \"Link copied!\"));\n }\n\n private async copy(text: string, id: string, done: string): Promise<void> {\n if (!text) return;\n const ok = await copyText(text);\n const btn = this.$(id);\n const label = id === \"copyCode\" ? \"Copy code\" : \"Copy invite link\";\n btn.textContent = ok ? done : \"Couldn't copy, select it by hand\";\n this.$(\"copied\").textContent = ok ? done : \"\";\n clearTimeout(this.copiedTimer);\n this.copiedTimer = setTimeout(() => {\n btn.textContent = label;\n this.$(\"copied\").textContent = \"\";\n }, 2000);\n }\n\n private render(): void {\n const c = this._controller;\n const s = c.state;\n const status = this.$(\"status\");\n status.textContent = s === \"error\" && c.error?.code === \"no_server\"\n ? \"This page is missing its lobby server address.\"\n : c.statusText;\n status.classList.toggle(\"error\", s === \"error\");\n const hint = this.$(\"hint\");\n const hintText = s === \"error\"\n ? (c.error?.code === \"no_server\" ? 'Put your lobby server address in server=\"…\" on the <one-lobby> tag (it looks like https://your-name.onrender.com). The guide shows how to get one.' : c.error?.hint ?? \"\")\n : \"\";\n hint.textContent = hintText;\n hint.hidden = !hintText;\n\n this.$(\"v-idle\").hidden = s !== \"idle\";\n this.$(\"v-busy\").hidden = s !== \"connecting\";\n this.$(\"v-lobby\").hidden = s !== \"lobby\" && s !== \"playing\";\n this.$(\"v-error\").hidden = s !== \"error\";\n this.setAttribute(\"state\", s);\n\n if (s === \"lobby\" || s === \"playing\") {\n this.$(\"codeText\").textContent = formatCode(c.code ?? \"\");\n const ul = this.$(\"players\");\n ul.replaceChildren(\n ...c.players.map((p) => {\n const li = document.createElement(\"li\");\n const nm = document.createElement(\"span\");\n nm.textContent = p.name;\n li.append(nm);\n if (p.isHost) li.append(Object.assign(document.createElement(\"span\"), { className: \"tag\", textContent: \"host\" }));\n if (p.isMe) li.append(Object.assign(document.createElement(\"span\"), { className: \"tag\", textContent: \"you\" }));\n return li;\n }),\n );\n const start = this.$(\"start\") as HTMLButtonElement;\n start.hidden = !c.isHost || s === \"playing\";\n start.disabled = c.players.length < 2;\n start.title = start.disabled ? \"Wait for at least one more player\" : \"\";\n }\n }\n }\n return OneLobbyBox;\n}\n\n/** Register `<one-lobby>` (safe to call more than once; a no-op outside a browser). */\nexport function defineLobbyElement(tag = LOBBY_TAG): void {\n if (typeof customElements === \"undefined\" || customElements.get(tag)) return;\n customElements.define(tag, makeClass());\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"one-lobby\": OneLobbyElement;\n }\n}\n"]}
@@ -0,0 +1,5 @@
1
+ export { defineLobbyElement, LOBBY_TAG } from "./element.js";
2
+ export type { OneLobbyElement } from "./element.js";
3
+ export { LobbyController, SharedState } from "./controller.js";
4
+ export { lobbyServerUrl } from "./server-url.js";
5
+ export type { Player, UiState, ControllerEvents, ControllerOptions, KitLike } from "./controller.js";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `onelobby/ui`: the `<one-lobby>` drop-in element. Loading this module registers the tag.
3
+ */
4
+ import { defineLobbyElement } from "./element.js";
5
+ export { defineLobbyElement, LOBBY_TAG } from "./element.js";
6
+ export { LobbyController, SharedState } from "./controller.js";
7
+ export { lobbyServerUrl } from "./server-url.js";
8
+ defineLobbyElement();
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,kBAAkB,EAAE,CAAC","sourcesContent":["/**\n * `onelobby/ui`: the `<one-lobby>` drop-in element. Loading this module registers the tag.\n */\nimport { defineLobbyElement } from \"./element.js\";\n\nexport { defineLobbyElement, LOBBY_TAG } from \"./element.js\";\nexport type { OneLobbyElement } from \"./element.js\";\nexport { LobbyController, SharedState } from \"./controller.js\";\nexport { lobbyServerUrl } from \"./server-url.js\";\nexport type { Player, UiState, ControllerEvents, ControllerOptions, KitLike } from \"./controller.js\";\n\ndefineLobbyElement();\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The `server="..."` attribute of `<one-lobby>` as a WebSocket URL, or null when it is missing, still the placeholder
3
+ * from the guide, or not an address at all. Null makes the element say "This page is missing its lobby server
4
+ * address" instead of the less helpful "Couldn't reach the game server".
5
+ *
6
+ * Accepts what people paste: `https://name.onrender.com` (the address Render shows), `wss://host/ws`, or a bare host.
7
+ * http(s) becomes ws(s); the server answers on `/` and `/ws`, so no path is needed.
8
+ */
9
+ export declare function lobbyServerUrl(raw: string | null | undefined): string | null;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The `server="..."` attribute of `<one-lobby>` as a WebSocket URL, or null when it is missing, still the placeholder
3
+ * from the guide, or not an address at all. Null makes the element say "This page is missing its lobby server
4
+ * address" instead of the less helpful "Couldn't reach the game server".
5
+ *
6
+ * Accepts what people paste: `https://name.onrender.com` (the address Render shows), `wss://host/ws`, or a bare host.
7
+ * http(s) becomes ws(s); the server answers on `/` and `/ws`, so no path is needed.
8
+ */
9
+ export function lobbyServerUrl(raw) {
10
+ const s = (raw ?? "").trim();
11
+ if (!s || /\s/.test(s))
12
+ return null;
13
+ // the guide's and example's placeholders: YOUR-LOBBY-SERVER, YOUR-SERVER, REPLACE-ME ...
14
+ if (/your[-_ ]?(lobby[-_ ]?)?server|replace[-_ ]?me|<|>/i.test(s))
15
+ return null;
16
+ let url = s;
17
+ if (/^https?:\/\//i.test(url))
18
+ url = url.replace(/^http/i, "ws");
19
+ else if (!/^wss?:\/\//i.test(url))
20
+ url = "wss://" + url;
21
+ let u;
22
+ try {
23
+ u = new URL(url);
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ const host = u.hostname.toLowerCase();
29
+ if (!host)
30
+ return null;
31
+ // documentation-only names (RFC 2606): example.com/.net/.org and *.example never reach a real server
32
+ if (/(^|\.)example\.(com|net|org)$/.test(host) || /\.example$/.test(host) || host === "example")
33
+ return null;
34
+ // a single word that is not localhost is a typo, not a server (e.g. server="lobby")
35
+ if (!host.includes(".") && !host.includes(":") && host !== "localhost")
36
+ return null;
37
+ return url;
38
+ }
39
+ //# sourceMappingURL=server-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-url.js","sourceRoot":"","sources":["../../src/ui/server-url.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,GAA8B;IAC3D,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,yFAAyF;IACzF,IAAI,qDAAqD,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/E,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;SAC5D,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,GAAG,GAAG,QAAQ,GAAG,GAAG,CAAC;IACxD,IAAI,CAAM,CAAC;IACX,IAAI,CAAC;QACH,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IACtC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,qGAAqG;IACrG,IAAI,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC7G,oFAAoF;IACpF,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IACpF,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * The `server=\"...\"` attribute of `<one-lobby>` as a WebSocket URL, or null when it is missing, still the placeholder\n * from the guide, or not an address at all. Null makes the element say \"This page is missing its lobby server\n * address\" instead of the less helpful \"Couldn't reach the game server\".\n *\n * Accepts what people paste: `https://name.onrender.com` (the address Render shows), `wss://host/ws`, or a bare host.\n * http(s) becomes ws(s); the server answers on `/` and `/ws`, so no path is needed.\n */\nexport function lobbyServerUrl(raw: string | null | undefined): string | null {\n const s = (raw ?? \"\").trim();\n if (!s || /\\s/.test(s)) return null;\n // the guide's and example's placeholders: YOUR-LOBBY-SERVER, YOUR-SERVER, REPLACE-ME ...\n if (/your[-_ ]?(lobby[-_ ]?)?server|replace[-_ ]?me|<|>/i.test(s)) return null;\n let url = s;\n if (/^https?:\\/\\//i.test(url)) url = url.replace(/^http/i, \"ws\");\n else if (!/^wss?:\\/\\//i.test(url)) url = \"wss://\" + url;\n let u: URL;\n try {\n u = new URL(url);\n } catch {\n return null;\n }\n const host = u.hostname.toLowerCase();\n if (!host) return null;\n // documentation-only names (RFC 2606): example.com/.net/.org and *.example never reach a real server\n if (/(^|\\.)example\\.(com|net|org)$/.test(host) || /\\.example$/.test(host) || host === \"example\") return null;\n // a single word that is not localhost is a typo, not a server (e.g. server=\"lobby\")\n if (!host.includes(\".\") && !host.includes(\":\") && host !== \"localhost\") return null;\n return url;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ /** Shadow-DOM styles for `<one-lobby>`. Theme with CSS custom properties on the element (see docs/no-code.md). */
2
+ export declare const LOBBY_CSS = "\n:host {\n --onelobby-bg: #ffffff;\n --onelobby-fg: #1b1f24;\n --onelobby-muted: #57606a;\n --onelobby-border: #d0d7de;\n --onelobby-accent: #0b5cad;\n --onelobby-accent-fg: #ffffff;\n --onelobby-danger: #b42318;\n --onelobby-field: #f6f8fa;\n --onelobby-radius: 12px;\n --onelobby-font: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n display: block;\n max-width: 28rem;\n font-family: var(--onelobby-font);\n color: var(--onelobby-fg);\n line-height: 1.4;\n}\n:host([theme=\"dark\"]) {\n --onelobby-bg: #161b22; --onelobby-fg: #e6edf3; --onelobby-muted: #9da7b3; --onelobby-border: #30363d;\n --onelobby-accent: #4c9be8; --onelobby-accent-fg: #0d1117; --onelobby-danger: #ff7b72; --onelobby-field: #0d1117;\n}\n@media (prefers-color-scheme: dark) {\n :host([theme=\"auto\"]), :host(:not([theme])) {\n --onelobby-bg: #161b22; --onelobby-fg: #e6edf3; --onelobby-muted: #9da7b3; --onelobby-border: #30363d;\n --onelobby-accent: #4c9be8; --onelobby-accent-fg: #0d1117; --onelobby-danger: #ff7b72; --onelobby-field: #0d1117;\n }\n}\n[hidden] { display: none !important; }\n.card {\n background: var(--onelobby-bg);\n border: 1px solid var(--onelobby-border);\n border-radius: var(--onelobby-radius);\n padding: 1rem;\n display: grid;\n gap: 0.75rem;\n}\n.status { font-size: 1rem; margin: 0; }\n.status.error { color: var(--onelobby-danger); font-weight: 600; }\n.hint { margin: 0; color: var(--onelobby-muted); }\nlabel { display: grid; gap: 0.25rem; font-size: 0.9rem; color: var(--onelobby-muted); }\ninput {\n font: inherit; font-size: 1rem; color: var(--onelobby-fg); background: var(--onelobby-field);\n border: 1px solid var(--onelobby-border); border-radius: 8px; padding: 0.6rem 0.7rem; min-height: 44px; box-sizing: border-box; width: 100%;\n}\n#code { text-transform: uppercase; letter-spacing: 0.15em; font-family: ui-monospace, Consolas, monospace; }\nbutton {\n font: inherit; font-size: 1rem; min-height: 44px; padding: 0.5rem 1rem; border-radius: 8px; cursor: pointer;\n border: 1px solid var(--onelobby-border); background: var(--onelobby-field); color: var(--onelobby-fg);\n}\nbutton.primary { background: var(--onelobby-accent); color: var(--onelobby-accent-fg); border-color: var(--onelobby-accent); font-weight: 600; }\nbutton:disabled { opacity: 0.55; cursor: not-allowed; }\nbutton:focus-visible, input:focus-visible { outline: 3px solid var(--onelobby-accent); outline-offset: 2px; }\n.row { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: end; }\n.row > label { flex: 1 1 10rem; }\n.or { text-align: center; color: var(--onelobby-muted); font-size: 0.9rem; }\n.codebox { display: grid; gap: 0.5rem; justify-items: start; }\n.code {\n font-family: ui-monospace, Consolas, monospace; font-size: clamp(2rem, 9vw, 2.75rem); font-weight: 700;\n letter-spacing: 0.12em; user-select: all;\n}\nh3 { margin: 0; font-size: 0.9rem; color: var(--onelobby-muted); font-weight: 600; }\nul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.25rem; }\nli { display: flex; gap: 0.5rem; align-items: center; }\n.tag { font-size: 0.75rem; border: 1px solid var(--onelobby-border); border-radius: 999px; padding: 0 0.5rem; color: var(--onelobby-muted); }\n.bar { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; justify-content: space-between; }\n.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n";
@@ -0,0 +1,70 @@
1
+ /** Shadow-DOM styles for `<one-lobby>`. Theme with CSS custom properties on the element (see docs/no-code.md). */
2
+ export const LOBBY_CSS = `
3
+ :host {
4
+ --onelobby-bg: #ffffff;
5
+ --onelobby-fg: #1b1f24;
6
+ --onelobby-muted: #57606a;
7
+ --onelobby-border: #d0d7de;
8
+ --onelobby-accent: #0b5cad;
9
+ --onelobby-accent-fg: #ffffff;
10
+ --onelobby-danger: #b42318;
11
+ --onelobby-field: #f6f8fa;
12
+ --onelobby-radius: 12px;
13
+ --onelobby-font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
14
+ display: block;
15
+ max-width: 28rem;
16
+ font-family: var(--onelobby-font);
17
+ color: var(--onelobby-fg);
18
+ line-height: 1.4;
19
+ }
20
+ :host([theme="dark"]) {
21
+ --onelobby-bg: #161b22; --onelobby-fg: #e6edf3; --onelobby-muted: #9da7b3; --onelobby-border: #30363d;
22
+ --onelobby-accent: #4c9be8; --onelobby-accent-fg: #0d1117; --onelobby-danger: #ff7b72; --onelobby-field: #0d1117;
23
+ }
24
+ @media (prefers-color-scheme: dark) {
25
+ :host([theme="auto"]), :host(:not([theme])) {
26
+ --onelobby-bg: #161b22; --onelobby-fg: #e6edf3; --onelobby-muted: #9da7b3; --onelobby-border: #30363d;
27
+ --onelobby-accent: #4c9be8; --onelobby-accent-fg: #0d1117; --onelobby-danger: #ff7b72; --onelobby-field: #0d1117;
28
+ }
29
+ }
30
+ [hidden] { display: none !important; }
31
+ .card {
32
+ background: var(--onelobby-bg);
33
+ border: 1px solid var(--onelobby-border);
34
+ border-radius: var(--onelobby-radius);
35
+ padding: 1rem;
36
+ display: grid;
37
+ gap: 0.75rem;
38
+ }
39
+ .status { font-size: 1rem; margin: 0; }
40
+ .status.error { color: var(--onelobby-danger); font-weight: 600; }
41
+ .hint { margin: 0; color: var(--onelobby-muted); }
42
+ label { display: grid; gap: 0.25rem; font-size: 0.9rem; color: var(--onelobby-muted); }
43
+ input {
44
+ font: inherit; font-size: 1rem; color: var(--onelobby-fg); background: var(--onelobby-field);
45
+ border: 1px solid var(--onelobby-border); border-radius: 8px; padding: 0.6rem 0.7rem; min-height: 44px; box-sizing: border-box; width: 100%;
46
+ }
47
+ #code { text-transform: uppercase; letter-spacing: 0.15em; font-family: ui-monospace, Consolas, monospace; }
48
+ button {
49
+ font: inherit; font-size: 1rem; min-height: 44px; padding: 0.5rem 1rem; border-radius: 8px; cursor: pointer;
50
+ border: 1px solid var(--onelobby-border); background: var(--onelobby-field); color: var(--onelobby-fg);
51
+ }
52
+ button.primary { background: var(--onelobby-accent); color: var(--onelobby-accent-fg); border-color: var(--onelobby-accent); font-weight: 600; }
53
+ button:disabled { opacity: 0.55; cursor: not-allowed; }
54
+ button:focus-visible, input:focus-visible { outline: 3px solid var(--onelobby-accent); outline-offset: 2px; }
55
+ .row { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: end; }
56
+ .row > label { flex: 1 1 10rem; }
57
+ .or { text-align: center; color: var(--onelobby-muted); font-size: 0.9rem; }
58
+ .codebox { display: grid; gap: 0.5rem; justify-items: start; }
59
+ .code {
60
+ font-family: ui-monospace, Consolas, monospace; font-size: clamp(2rem, 9vw, 2.75rem); font-weight: 700;
61
+ letter-spacing: 0.12em; user-select: all;
62
+ }
63
+ h3 { margin: 0; font-size: 0.9rem; color: var(--onelobby-muted); font-weight: 600; }
64
+ ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.25rem; }
65
+ li { display: flex; gap: 0.5rem; align-items: center; }
66
+ .tag { font-size: 0.75rem; border: 1px solid var(--onelobby-border); border-radius: 999px; padding: 0 0.5rem; color: var(--onelobby-muted); }
67
+ .bar { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; justify-content: space-between; }
68
+ .sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
69
+ `;
70
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../src/ui/styles.ts"],"names":[],"mappings":"AAAA,kHAAkH;AAClH,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmExB,CAAC","sourcesContent":["/** Shadow-DOM styles for `<one-lobby>`. Theme with CSS custom properties on the element (see docs/no-code.md). */\nexport const LOBBY_CSS = `\n:host {\n --onelobby-bg: #ffffff;\n --onelobby-fg: #1b1f24;\n --onelobby-muted: #57606a;\n --onelobby-border: #d0d7de;\n --onelobby-accent: #0b5cad;\n --onelobby-accent-fg: #ffffff;\n --onelobby-danger: #b42318;\n --onelobby-field: #f6f8fa;\n --onelobby-radius: 12px;\n --onelobby-font: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n display: block;\n max-width: 28rem;\n font-family: var(--onelobby-font);\n color: var(--onelobby-fg);\n line-height: 1.4;\n}\n:host([theme=\"dark\"]) {\n --onelobby-bg: #161b22; --onelobby-fg: #e6edf3; --onelobby-muted: #9da7b3; --onelobby-border: #30363d;\n --onelobby-accent: #4c9be8; --onelobby-accent-fg: #0d1117; --onelobby-danger: #ff7b72; --onelobby-field: #0d1117;\n}\n@media (prefers-color-scheme: dark) {\n :host([theme=\"auto\"]), :host(:not([theme])) {\n --onelobby-bg: #161b22; --onelobby-fg: #e6edf3; --onelobby-muted: #9da7b3; --onelobby-border: #30363d;\n --onelobby-accent: #4c9be8; --onelobby-accent-fg: #0d1117; --onelobby-danger: #ff7b72; --onelobby-field: #0d1117;\n }\n}\n[hidden] { display: none !important; }\n.card {\n background: var(--onelobby-bg);\n border: 1px solid var(--onelobby-border);\n border-radius: var(--onelobby-radius);\n padding: 1rem;\n display: grid;\n gap: 0.75rem;\n}\n.status { font-size: 1rem; margin: 0; }\n.status.error { color: var(--onelobby-danger); font-weight: 600; }\n.hint { margin: 0; color: var(--onelobby-muted); }\nlabel { display: grid; gap: 0.25rem; font-size: 0.9rem; color: var(--onelobby-muted); }\ninput {\n font: inherit; font-size: 1rem; color: var(--onelobby-fg); background: var(--onelobby-field);\n border: 1px solid var(--onelobby-border); border-radius: 8px; padding: 0.6rem 0.7rem; min-height: 44px; box-sizing: border-box; width: 100%;\n}\n#code { text-transform: uppercase; letter-spacing: 0.15em; font-family: ui-monospace, Consolas, monospace; }\nbutton {\n font: inherit; font-size: 1rem; min-height: 44px; padding: 0.5rem 1rem; border-radius: 8px; cursor: pointer;\n border: 1px solid var(--onelobby-border); background: var(--onelobby-field); color: var(--onelobby-fg);\n}\nbutton.primary { background: var(--onelobby-accent); color: var(--onelobby-accent-fg); border-color: var(--onelobby-accent); font-weight: 600; }\nbutton:disabled { opacity: 0.55; cursor: not-allowed; }\nbutton:focus-visible, input:focus-visible { outline: 3px solid var(--onelobby-accent); outline-offset: 2px; }\n.row { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: end; }\n.row > label { flex: 1 1 10rem; }\n.or { text-align: center; color: var(--onelobby-muted); font-size: 0.9rem; }\n.codebox { display: grid; gap: 0.5rem; justify-items: start; }\n.code {\n font-family: ui-monospace, Consolas, monospace; font-size: clamp(2rem, 9vw, 2.75rem); font-weight: 700;\n letter-spacing: 0.12em; user-select: all;\n}\nh3 { margin: 0; font-size: 0.9rem; color: var(--onelobby-muted); font-weight: 600; }\nul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.25rem; }\nli { display: flex; gap: 0.5rem; align-items: center; }\n.tag { font-size: 0.75rem; border: 1px solid var(--onelobby-border); border-radius: 999px; padding: 0 0.5rem; color: var(--onelobby-muted); }\n.bar { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; justify-content: space-between; }\n.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n`;\n"]}
@@ -0,0 +1,19 @@
1
+ import type { AudioSink, FrameSource, VoiceCodec } from "./voice-types.js";
2
+ export declare function webCodecsOpusAvailable(): boolean;
3
+ /** Opus through WebCodecs (not Baseline: Chromium/Edge/Safari; check `webCodecsOpusAvailable()`). */
4
+ export declare const webCodecsOpus: VoiceCodec;
5
+ /** getUserMedia + AudioWorklet producing 960-sample frames at 48 kHz. Must be started from a user gesture. */
6
+ export declare class WorkletMic implements FrameSource {
7
+ private ctx;
8
+ private stream;
9
+ start(onFrame: (pcm: Float32Array) => void): Promise<void>;
10
+ stop(): void;
11
+ }
12
+ /** Plays decoded PCM per speaker, scheduled back to back on an AudioContext. */
13
+ export declare class WebAudioSink implements AudioSink {
14
+ private ctx;
15
+ private next;
16
+ constructor();
17
+ play(peerId: number, pcm: Float32Array, gain: number): void;
18
+ close(): void;
19
+ }