@minicode-dev/chat 0.1.11 → 0.1.12

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.
@@ -0,0 +1,1225 @@
1
+ import { d as e, o as t } from "./connect4Sounds-Cv0q31VW.js";
2
+ import { n, o as r, r as i, s as a } from "./createPeerLink-DBVs_yvQ.js";
3
+ import { t as o } from "./ChatContext-DjIEifGY.js";
4
+ import { a as s, i as c, n as l, r as u, t as d } from "./tanksSounds-CUNDIOal.js";
5
+ import { memo as f, useContext as p, useEffect as m, useId as h, useRef as g, useState as _ } from "react";
6
+ import { AlertTriangle as v, CheckCircle2 as y, Link2 as b, Loader2 as x, Settings2 as S, ShieldCheck as C, X as w } from "lucide-react";
7
+ import { Fragment as T, jsx as E, jsxs as D } from "react/jsx-runtime";
8
+ // \0rolldown/runtime.js
9
+ var O = Object.defineProperty, k = (e, t) => {
10
+ let n = {};
11
+ for (var r in e) O(n, r, {
12
+ get: e[r],
13
+ enumerable: !0
14
+ });
15
+ return t || O(n, Symbol.toStringTag, { value: "Module" }), n;
16
+ }, A = "{\n \"iceServers\": [{\n \"urls\": [\"turn:turn.cloudflare.com:3478?transport=udp\"],\n \"username\": \"usuario-temporal\",\n \"credential\": \"credencial-temporal\"\n }]\n}";
17
+ function j({ open: e, onClose: t, onApply: n, manualTurn: r }) {
18
+ let a = g(null), o = g(0), s = h(), c = h(), l = h(), [u, d] = _(""), [f, p] = _(""), [v, S] = _(!1), [T, O] = _(""), [k, j] = _(!1), [M, N] = _(!1);
19
+ m(() => {
20
+ let t = a.current;
21
+ if (!e) {
22
+ o.current += 1, d(""), p(""), S(!1), O(""), j(!1), N(!1), t?.open && t.close();
23
+ return;
24
+ }
25
+ let n = document.activeElement;
26
+ return t.showModal(), () => {
27
+ o.current += 1, t.open && t.close(), n instanceof HTMLElement && n.isConnected && n.focus({ preventScroll: !0 });
28
+ };
29
+ }, [e]);
30
+ let P = async (e) => {
31
+ if (e.preventDefault(), k || !u.trim()) return;
32
+ O("");
33
+ let t = {
34
+ iceTransportPolicy: v ? "relay" : "all",
35
+ ...f.trim() ? { ttlSeconds: Number(f) } : {}
36
+ };
37
+ try {
38
+ i(u, t);
39
+ } catch (e) {
40
+ O(e.message);
41
+ return;
42
+ }
43
+ let r = ++o.current;
44
+ j(!0), N(!1);
45
+ try {
46
+ if (await n(u, t), r !== o.current) return;
47
+ d(""), p(""), N(!0);
48
+ } catch (e) {
49
+ r === o.current && O(e.message || "No se pudo compartir la configuración. Inténtalo de nuevo.");
50
+ } finally {
51
+ r === o.current && j(!1);
52
+ }
53
+ }, F = r?.status === "applying" ? "Aplicando configuración en ambos jugadores…" : r?.status === "waiting_peer" ? "Esperando al otro jugador para compartir la configuración…" : "Sincronizando con el otro jugador…";
54
+ return /* @__PURE__ */ D("dialog", {
55
+ ref: a,
56
+ className: "tl-turn-dialog",
57
+ "aria-labelledby": s,
58
+ "aria-describedby": c,
59
+ onCancel: (e) => {
60
+ e.preventDefault(), t();
61
+ },
62
+ onClick: (e) => {
63
+ if (e.target === e.currentTarget) {
64
+ let n = e.currentTarget.getBoundingClientRect();
65
+ (e.clientX < n.left || e.clientX > n.right || e.clientY < n.top || e.clientY > n.bottom) && t();
66
+ }
67
+ },
68
+ children: [/* @__PURE__ */ D("header", {
69
+ className: "tl-turn-dialog__header",
70
+ children: [
71
+ /* @__PURE__ */ E("span", {
72
+ className: "tl-turn-dialog__icon",
73
+ "aria-hidden": "true",
74
+ children: /* @__PURE__ */ E(b, { size: 22 })
75
+ }),
76
+ /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("h2", {
77
+ id: s,
78
+ children: "Configurar conexión alternativa"
79
+ }), /* @__PURE__ */ E("p", { children: "Se comparte con el otro jugador." })] }),
80
+ /* @__PURE__ */ E("button", {
81
+ type: "button",
82
+ className: "tl-turn-dialog__close",
83
+ "aria-label": "Cerrar configuración de conexión",
84
+ onClick: t,
85
+ children: /* @__PURE__ */ E(w, { size: 18 })
86
+ })
87
+ ]
88
+ }), /* @__PURE__ */ D("form", {
89
+ onSubmit: P,
90
+ children: [/* @__PURE__ */ D("div", {
91
+ className: "tl-turn-dialog__body",
92
+ children: [
93
+ /* @__PURE__ */ E("p", {
94
+ id: c,
95
+ children: "Pega el JSON de credenciales temporales de Cloudflare TURN. Se compartirá con el otro jugador para buscar una conexión alternativa."
96
+ }),
97
+ /* @__PURE__ */ D("label", {
98
+ className: "tl-turn-dialog__field",
99
+ children: ["Configuración JSON", /* @__PURE__ */ E("textarea", {
100
+ autoFocus: !0,
101
+ value: u,
102
+ onChange: (e) => {
103
+ d(e.target.value), O(""), N(!1);
104
+ },
105
+ placeholder: A,
106
+ maxLength: 6e3,
107
+ rows: 7,
108
+ required: !M,
109
+ readOnly: k,
110
+ autoComplete: "off",
111
+ autoCorrect: "off",
112
+ autoCapitalize: "off",
113
+ spellCheck: !1,
114
+ "aria-invalid": !!T,
115
+ "aria-describedby": T ? l : void 0
116
+ })]
117
+ }),
118
+ /* @__PURE__ */ D("div", {
119
+ className: "tl-turn-dialog__privacy",
120
+ children: [/* @__PURE__ */ E(C, {
121
+ size: 16,
122
+ "aria-hidden": "true"
123
+ }), /* @__PURE__ */ E("span", { children: "Solo credenciales temporales. No pegues tu API Token ni secretos permanentes." })]
124
+ }),
125
+ /* @__PURE__ */ D("label", {
126
+ className: "tl-turn-dialog__field",
127
+ children: [
128
+ "Validez restante en segundos ",
129
+ /* @__PURE__ */ E("span", {
130
+ className: "tl-turn-dialog__optional",
131
+ children: "Opcional"
132
+ }),
133
+ /* @__PURE__ */ E("input", {
134
+ type: "number",
135
+ min: "1",
136
+ max: "172800",
137
+ step: "1",
138
+ inputMode: "numeric",
139
+ value: f,
140
+ onChange: (e) => {
141
+ p(e.target.value), O("");
142
+ },
143
+ disabled: k,
144
+ placeholder: "Ej. 3600"
145
+ }),
146
+ /* @__PURE__ */ E("small", { children: "Déjalo vacío si no conoces la caducidad. Si dejan de funcionar, pega credenciales nuevas." })
147
+ ]
148
+ }),
149
+ /* @__PURE__ */ D("label", {
150
+ className: "tl-turn-dialog__debug",
151
+ children: [/* @__PURE__ */ E("input", {
152
+ type: "checkbox",
153
+ checked: v,
154
+ onChange: (e) => S(e.target.checked),
155
+ disabled: k
156
+ }), /* @__PURE__ */ D("span", { children: [/* @__PURE__ */ E("strong", { children: "Forzar relay para diagnóstico" }), /* @__PURE__ */ E("small", { children: "Normalmente se permite P2P. Actívalo solo para comprobar TURN." })] })]
157
+ }),
158
+ k && /* @__PURE__ */ D("p", {
159
+ className: "tl-turn-dialog__feedback",
160
+ role: "status",
161
+ children: [/* @__PURE__ */ E(x, {
162
+ size: 16,
163
+ className: "tl-connection-spinner",
164
+ "aria-hidden": "true"
165
+ }), F]
166
+ }),
167
+ M && /* @__PURE__ */ D("p", {
168
+ className: "tl-turn-dialog__feedback tl-turn-dialog__feedback--success",
169
+ role: "status",
170
+ children: [/* @__PURE__ */ E(y, {
171
+ size: 17,
172
+ "aria-hidden": "true"
173
+ }), "Configuración compartida y aplicada en ambos jugadores. La ruta utilizada se mostrará al conectar."]
174
+ }),
175
+ T && /* @__PURE__ */ E("p", {
176
+ id: l,
177
+ className: "tl-turn-dialog__error",
178
+ role: "alert",
179
+ children: T
180
+ })
181
+ ]
182
+ }), /* @__PURE__ */ D("footer", {
183
+ className: "tl-turn-dialog__footer",
184
+ children: [/* @__PURE__ */ E("button", {
185
+ type: "button",
186
+ onClick: t,
187
+ children: M ? "Listo" : "Cancelar"
188
+ }), /* @__PURE__ */ D("button", {
189
+ type: "submit",
190
+ className: "tl-turn-dialog__apply",
191
+ disabled: k || !u.trim(),
192
+ children: [k ? /* @__PURE__ */ E(x, {
193
+ size: 16,
194
+ className: "tl-connection-spinner",
195
+ "aria-hidden": "true"
196
+ }) : /* @__PURE__ */ E(b, {
197
+ size: 16,
198
+ "aria-hidden": "true"
199
+ }), "Aplicar y reconectar"]
200
+ })]
201
+ })]
202
+ })]
203
+ });
204
+ }
205
+ // src/games/tanks-live/net/connectionDiagnostics.js
206
+ var M = (e) => typeof e == "string" && /^[a-z_]+$/.test(e) ? e : e ? "connection_error" : null;
207
+ function N(e = {}) {
208
+ let t = e.route, n = e.candidates ?? {}, r = e.manualTurn ?? {};
209
+ return {
210
+ phase: e.phase ?? "idle",
211
+ ice: e.ice ?? "new",
212
+ connection: e.connection ?? "new",
213
+ signalingState: e.signalingState ?? "stable",
214
+ gathering: e.gathering ?? "new",
215
+ signaling: {
216
+ sent: e.signals?.sent ?? 0,
217
+ received: e.signals?.received ?? 0,
218
+ sendFailures: e.signals?.sendFailures ?? 0,
219
+ ignored: e.signals?.ignored ?? 0,
220
+ invalid: e.signals?.invalid ?? 0,
221
+ retransmits: e.signals?.retransmits ?? 0,
222
+ duplicates: e.signals?.duplicates ?? 0,
223
+ tooLarge: e.signals?.tooLarge ?? 0
224
+ },
225
+ turnConfigured: !!e.turnConfigured,
226
+ configuration: {
227
+ status: r.status ?? "idle",
228
+ shared: !!r.shared,
229
+ received: !!r.received,
230
+ expiresAt: r.expiresAt ?? null,
231
+ expired: r.expiresAt != null && r.expiresAt <= Date.now()
232
+ },
233
+ route: t?.label ?? null,
234
+ selectedPair: t ? {
235
+ relay: !!t.relay,
236
+ localType: t.local ?? null,
237
+ remoteType: t.remote ?? null,
238
+ protocol: t.protocol ?? null,
239
+ relayProtocol: t.relayProtocol ?? null
240
+ } : null,
241
+ candidates: {
242
+ local: n.local ?? 0,
243
+ remote: n.remote ?? 0,
244
+ accepted: n.accepted ?? 0,
245
+ rejected: n.rejected ?? 0,
246
+ localRelay: n.localRelay ?? 0,
247
+ remoteRelay: n.remoteRelay ?? 0
248
+ },
249
+ iceErrors: (e.iceErrors ?? []).slice(-10).map((e) => ({
250
+ code: Number(e.code) || 0,
251
+ stage: e.stage === "gathering" ? "gathering" : "remote_candidate"
252
+ })),
253
+ reconnects: e.reconnects ?? 0,
254
+ restarts: e.restarts ?? 0,
255
+ rttMs: e.rtt ?? e.statsRtt ?? null,
256
+ connectionTimeMs: e.elapsedMs ?? 0,
257
+ channels: {
258
+ move: e.channels?.move ?? "closed",
259
+ events: e.channels?.events ?? "closed"
260
+ },
261
+ movesSent: e.move?.sent ?? 0,
262
+ movesReceived: e.move?.received ?? 0,
263
+ error: M(e.error)
264
+ };
265
+ }
266
+ function P(e = {}) {
267
+ return e.phase === "connected" ? "" : e.manualTurn?.status === "applying" ? "Aplicando la configuración de conexión alternativa." : e.manualTurn?.status === "sharing" ? "Sincronizando la configuración con el otro jugador." : e.manualTurn?.status === "waiting_peer" ? "Esperando al otro jugador para compartir la configuración." : e.error === "turn_credentials_expired" || e.manualTurn?.expiresAt != null && e.manualTurn.expiresAt <= Date.now() ? "La validez indicada de TURN terminó. Pega credenciales temporales nuevas para reconectar." : e.signals?.sendFailures ? "No se pudieron enviar señales por el chat. Revisa la conexión WebSocket y reintenta." : e.ice === "checking" || e.ice === "failed" ? e.turnConfigured ? "WebRTC aún no encuentra una ruta entre los jugadores. Puedes revisar o renovar la configuración TURN." : "WebRTC aún no encuentra una ruta entre los jugadores. Algunas redes necesitan un servidor TURN; puedes configurar una conexión alternativa." : e.phase === "listening" ? "Esperando la conexión del rival. Ambos deben mantener abierta la partida y usar la misma versión del chat." : e.phase === "failed" ? "No se pudo conectar. Revisa el diagnóstico de ambos jugadores y reintenta." : "Intercambiando señales de conexión por el chat. El combate empieza cuando conecta WebRTC.";
268
+ }
269
+ // src/games/tanks-live/logic/simulation.js
270
+ var F = Object.freeze({
271
+ speed: 16,
272
+ reverse: 9,
273
+ turn: 1.8,
274
+ acceleration: 28,
275
+ radius: 2,
276
+ reloadMs: 1500,
277
+ shellSpeed: 60,
278
+ damage: 25
279
+ }), I = (e, t, n) => Math.max(t, Math.min(n, e)), L = (e, t) => Math.atan2(Math.sin(t - e), Math.cos(t - e));
280
+ function ee() {
281
+ return {
282
+ size: 120,
283
+ bounds: {
284
+ minX: 0,
285
+ maxX: 120,
286
+ minZ: 0,
287
+ maxZ: 120
288
+ },
289
+ obstacles: [
290
+ [
291
+ 42,
292
+ 35,
293
+ 4
294
+ ],
295
+ [
296
+ 78,
297
+ 85,
298
+ 4
299
+ ],
300
+ [
301
+ 60,
302
+ 30,
303
+ 3
304
+ ],
305
+ [
306
+ 60,
307
+ 90,
308
+ 3
309
+ ],
310
+ [
311
+ 35,
312
+ 65,
313
+ 3
314
+ ],
315
+ [
316
+ 85,
317
+ 55,
318
+ 3
319
+ ],
320
+ [
321
+ 60,
322
+ 60,
323
+ 5
324
+ ]
325
+ ].map(([e, t, n], r) => ({
326
+ x: e,
327
+ z: t,
328
+ r: n,
329
+ kind: r % 2 ? "tree" : "rock"
330
+ }))
331
+ };
332
+ }
333
+ function te(e, t) {
334
+ return {
335
+ id: String(e),
336
+ x: t ? 102 : 18,
337
+ z: 60,
338
+ yaw: t ? Math.PI : 0,
339
+ turretYaw: t ? Math.PI : 0,
340
+ speed: 0,
341
+ hp: 100,
342
+ alive: !0,
343
+ color: t ? "#d7463f" : "#54a34a"
344
+ };
345
+ }
346
+ function R(e, t, n, r = F.radius) {
347
+ let i = n.bounds;
348
+ return e >= i.minX + r && e <= i.maxX - r && t >= i.minZ + r && t <= i.maxZ - r && !n.obstacles.some((n) => Math.hypot(e - n.x, t - n.z) < n.r + r);
349
+ }
350
+ function ne(e, t, n, r) {
351
+ if (!e.alive) return;
352
+ let i = I(Number(t.throttle) || 0, -1, 1), a = I(Number(t.steer) || 0, -1, 1), o = i * (i < 0 ? F.reverse : F.speed);
353
+ e.speed += I(o - e.speed, -F.acceleration * n, F.acceleration * n), e.yaw += a * F.turn * n, Number.isFinite(t.turretYaw) && (e.turretYaw = t.turretYaw);
354
+ let s = e.x + Math.cos(e.yaw) * e.speed * n, c = e.z + Math.sin(e.yaw) * e.speed * n;
355
+ R(s, e.z, r) ? e.x = s : e.speed = 0, R(e.x, c, r) ? e.z = c : e.speed = 0;
356
+ }
357
+ function z(e, t, n, r, i, a, o) {
358
+ let s = n - e, c = r - t, l = s * s + c * c, u = l ? I(((i - e) * s + (a - t) * c) / l, 0, 1) : 0;
359
+ return Math.hypot(e + s * u - i, t + c * u - a) <= o ? u : null;
360
+ }
361
+ function re(e, t, n, r) {
362
+ let i = e.x + Math.cos(e.yaw) * F.shellSpeed * t, a = e.z + Math.sin(e.yaw) * F.shellSpeed * t, o = null;
363
+ for (let t of n.obstacles) {
364
+ let n = z(e.x, e.z, i, a, t.x, t.z, t.r);
365
+ n !== null && (!o || n < o.t) && (o = {
366
+ t: n,
367
+ kind: "obstacle"
368
+ });
369
+ }
370
+ for (let t of r) {
371
+ if (!t.alive || t.id === e.owner) continue;
372
+ let n = z(e.x, e.z, i, a, t.x, t.z, F.radius);
373
+ n !== null && (!o || n < o.t) && (o = {
374
+ t: n,
375
+ kind: "tank",
376
+ target: t.id
377
+ });
378
+ }
379
+ return o ? (e.x += (i - e.x) * o.t, e.z += (a - e.z) * o.t, o) : (e.x = i, e.z = a, e.age += t, !R(i, a, {
380
+ ...n,
381
+ obstacles: []
382
+ }, 0) || e.age > 3 ? { kind: "terrain" } : null);
383
+ }
384
+ function ie(e, t, n) {
385
+ let r = I(n, 0, 1);
386
+ return {
387
+ ...t,
388
+ x: e.x + (t.x - e.x) * r,
389
+ z: e.z + (t.z - e.z) * r,
390
+ yaw: e.yaw + L(e.yaw, t.yaw) * r,
391
+ turretYaw: e.turretYaw + L(e.turretYaw, t.turretYaw) * r
392
+ };
393
+ }
394
+ // src/games/tanks-live/net/createLiveMatch.js
395
+ var B = (e) => typeof e == "number" && Number.isFinite(e), ae = (e) => typeof e == "string" && e.length > 0 && e.length < 160;
396
+ function oe({ game: e, selfId: t, isHost: i = String(t) === String(e.hostId), signaling: o, onChange: s = () => {}, peerFactory: c = n, iceServers: l, iceTransportPolicy: u, RTCPeerConnectionImpl: d } = {}) {
397
+ t = String(t);
398
+ let f = [String(e.hostId), String(e.guestId)];
399
+ if (!f.includes(t) || f[0] === f[1]) throw Error("Invalid live match players");
400
+ let p = (l || []).some((e) => [e.urls].flat().some((e) => /^turns?:/i.test(String(e)))), m = f.find((e) => e !== t), h = ee(), g = f.map(te), _ = [], v = [], y = {}, b = 0, x = 0, S = 0, C = 0, w = "connecting", T = null, E = !1, D = {
401
+ phase: "idle",
402
+ rtt: null,
403
+ route: null
404
+ }, O = !1, k = !1, A = null, j = [], M = null, N = null, P = /* @__PURE__ */ new Map(), I = /* @__PURE__ */ new Set(), L = /* @__PURE__ */ new Set(), z = () => g.find((e) => e.id === t), oe = () => g.find((e) => e.id === m), V = () => {
405
+ E || s({
406
+ status: w,
407
+ winner: T,
408
+ net: D
409
+ });
410
+ }, H = (e) => {
411
+ v.push(e), v.length > 200 && v.shift();
412
+ }, U = (t) => X.sendEvent({
413
+ ...t,
414
+ g: e.gameId,
415
+ r: S
416
+ }), W = (e) => {
417
+ I.add(e), I.size > 2048 && I.delete(I.values().next().value);
418
+ }, G = () => ({
419
+ k: "snapshot",
420
+ hp: g.map((e) => e.hp),
421
+ status: w,
422
+ winner: T,
423
+ reason: A
424
+ });
425
+ function K() {
426
+ g = f.map(te), _ = [], j = [], M = null, N = null, P.clear(), I.clear(), L.clear(), k = !1, A = null, T = null, w = D.phase === "connected" ? "playing" : "connecting", H({ k: "rematch" }), V();
427
+ }
428
+ function q(e, t = !1) {
429
+ (w !== "over" || t && T !== e) && (w = "over", T = e, _ = [], X.clearManualTurn?.(), H({
430
+ k: "over",
431
+ winner: T
432
+ }), V());
433
+ }
434
+ function J(e, t, n) {
435
+ if (w !== "playing" || !ae(t.id) || I.has(t.id)) return !1;
436
+ let r = g.find((t) => t.id === e);
437
+ if (!r?.alive || b - (P.get(e) ?? -Infinity) < F.reloadMs - 20 || ![
438
+ t.x,
439
+ t.z,
440
+ t.yaw
441
+ ].every(B) || Math.hypot(t.x - (e === m && N ? N.x : r.x), t.z - (e === m && N ? N.z : r.z)) > 6) return !1;
442
+ W(t.id), P.set(e, b);
443
+ let i = {
444
+ ...t,
445
+ k: "fire",
446
+ owner: e,
447
+ x: t.x + Math.cos(t.yaw) * 2.8,
448
+ z: t.z + Math.sin(t.yaw) * 2.8,
449
+ y: 1.4,
450
+ age: 0
451
+ };
452
+ return _.push(i), H(i), n && U({
453
+ ...t,
454
+ k: "fire"
455
+ }), !0;
456
+ }
457
+ function Y(t) {
458
+ if (E || w !== "playing" || t?.g !== e.gameId || t.r !== S || !Array.isArray(t.p) || t.p.length !== 5 || !t.p.every(B)) return;
459
+ let [n, r, i, a, o] = t.p;
460
+ if (!R(n, r, h) || Math.abs(o) > F.speed + .1 || Math.abs(i) > 1e6 || Math.abs(a) > 1e6) return;
461
+ let s = M === null ? .1 : Math.max(0, (b - M) / 1e3), c = N || oe();
462
+ if (Math.hypot(n - c.x, r - c.z) > F.speed * s + 3) return;
463
+ let l = {
464
+ ...oe(),
465
+ x: n,
466
+ z: r,
467
+ yaw: i,
468
+ turretYaw: a,
469
+ speed: o
470
+ };
471
+ N = l, M = b, j.push({
472
+ time: b,
473
+ pose: l
474
+ }), j.length > 30 && j.shift();
475
+ }
476
+ function se(n) {
477
+ if (!E && n && n.g === e.gameId && Number.isInteger(n.r)) {
478
+ if (n.k === "round" && !i && w === "over" && L.has(t) && n.r === S + 1) S = n.r, K();
479
+ else if (n.r === S) {
480
+ if (n.k === "sync" && i) U(G());
481
+ else if (n.k === "snapshot" && !i && Array.isArray(n.hp) && n.hp.length === 2 && n.hp.every((e) => Number.isInteger(e) && e >= 0 && e <= 100)) {
482
+ let e = n.status === "playing" && n.winner === null && n.hp.every((e) => e > 0), t = n.status === "over" && f.includes(n.winner) && (n.hp[1 - f.indexOf(n.winner)] === 0 || n.reason === "resigned");
483
+ if (!e && !t) return;
484
+ g.forEach((e, t) => {
485
+ e.hp = n.hp[t], e.alive = e.hp > 0;
486
+ }), t && (k = !1, A = n.reason, q(n.winner, !0));
487
+ } else n.k === "resign" ? (A = "resigned", q(t, i), i && U(G())) : n.k === "rematch" && w === "over" ? (L.add(m), ce()) : w === "playing" && (n.k === "fire" && J(m, n, !1), !i && n.k === "hit" && ae(n.id) && !I.has(`hit:${n.id}`) && f.includes(n.target) && Array.isArray(n.hp) && n.hp.length === 2 && n.hp.every((e, t) => Number.isInteger(e) && e >= 0 && e <= g[t].hp) && [n.x, n.z].every(B) && (W(`hit:${n.id}`), g.forEach((e, t) => {
488
+ e.hp = n.hp[t], e.alive = e.hp > 0;
489
+ }), _ = _.filter((e) => e.id !== n.id), H(n)), !i && n.k === "destroyed" && f.includes(n.target) && !I.has(`destroyed:${n.target}`) && g.find((e) => e.id === n.target).hp === 0 && (W(`destroyed:${n.target}`), H(n)), !i && n.k === "over" && f.includes(n.winner) && g.find((e) => e.id !== n.winner).hp === 0 && q(n.winner));
490
+ }
491
+ }
492
+ }
493
+ let X = c({
494
+ configurationAuthority: i,
495
+ signaling: o && {
496
+ send(t) {
497
+ let n = r(t);
498
+ if (!n) return Promise.reject(/* @__PURE__ */ Error("Invalid live signal"));
499
+ let i = a({
500
+ ...n,
501
+ liveGame: e.gameId
502
+ });
503
+ return i ? o.send(i) : Promise.reject(/* @__PURE__ */ Error("Live signal too large"));
504
+ },
505
+ subscribe(t) {
506
+ return o.subscribe((n) => {
507
+ r(n?.text)?.liveGame === e.gameId && t(n);
508
+ });
509
+ }
510
+ },
511
+ selfId: t,
512
+ peerId: m,
513
+ roomId: e.roomId,
514
+ iceServers: l,
515
+ iceTransportPolicy: u,
516
+ RTCPeerConnectionImpl: d,
517
+ onMove: Y,
518
+ onEvent: se,
519
+ onChange(e) {
520
+ let t = D.phase;
521
+ if (D = e, e.phase === "connected") {
522
+ let e = O;
523
+ O = !0, w !== "over" && (w = "playing"), t !== "connected" && queueMicrotask(() => {
524
+ E || (k && U({ k: "resign" }), i && e ? U(G()) : i || U({ k: "sync" }));
525
+ });
526
+ } else w !== "over" && (w = O ? "disconnected" : "connecting");
527
+ V();
528
+ }
529
+ });
530
+ function ce() {
531
+ i && L.size === 2 && (S++, U({ k: "round" }), K());
532
+ }
533
+ return i ? X.connect() : X.setListening(!0), {
534
+ setInput(e) {
535
+ y = { ...e };
536
+ },
537
+ step(n) {
538
+ if (E) return;
539
+ let r = Math.max(0, Math.min(Number(n) || 0, 100));
540
+ if (b += r, w !== "playing") return;
541
+ let a = r / 1e3, o = b - 100;
542
+ for (; j.length > 1 && j[1].time <= o;) j.shift();
543
+ if (j.length) {
544
+ let e = j[0], t = j[1] || e, n = ie(e.pose, t.pose, t.time === e.time ? 1 : (o - e.time) / (t.time - e.time));
545
+ for (let e of [
546
+ "x",
547
+ "z",
548
+ "yaw",
549
+ "turretYaw",
550
+ "speed"
551
+ ]) oe()[e] = n[e];
552
+ }
553
+ for (let e = a; e > 0; e -= .016) ne(z(), y, Math.min(e, .016), h);
554
+ y.fire && J(t, {
555
+ id: `${t}:${S}:${++C}`,
556
+ x: z().x,
557
+ z: z().z,
558
+ yaw: z().turretYaw,
559
+ t: b
560
+ }, !0);
561
+ let s = D.rtt > 250 || D.move?.lossPct > 15 ? 10 : D.rtt > 100 ? 15 : 20;
562
+ if (b >= x) {
563
+ x = b + 1e3 / s;
564
+ let t = z();
565
+ X.sendMove({
566
+ g: e.gameId,
567
+ r: S,
568
+ p: [
569
+ t.x,
570
+ t.z,
571
+ t.yaw,
572
+ t.turretYaw,
573
+ t.speed
574
+ ],
575
+ t: b
576
+ });
577
+ }
578
+ _ = _.filter((e) => {
579
+ let t = re(e, a, h, g);
580
+ if (!t) return !0;
581
+ if (i && t.target && w === "playing") {
582
+ let n = g.find((e) => e.id === t.target);
583
+ n.hp = Math.max(0, n.hp - F.damage), n.alive = n.hp > 0;
584
+ let r = {
585
+ k: "hit",
586
+ id: e.id,
587
+ target: n.id,
588
+ dmg: F.damage,
589
+ hp: g.map((e) => e.hp),
590
+ x: e.x,
591
+ z: e.z
592
+ };
593
+ if (U(r), H(r), !n.alive) {
594
+ let t = {
595
+ k: "destroyed",
596
+ target: n.id
597
+ };
598
+ U(t), H(t), U({
599
+ k: "over",
600
+ winner: e.owner
601
+ }), q(e.owner);
602
+ }
603
+ } else H({
604
+ k: "impact",
605
+ kind: t.kind,
606
+ x: e.x,
607
+ z: e.z
608
+ });
609
+ return !1;
610
+ });
611
+ },
612
+ getRender() {
613
+ let e = {
614
+ arena: h,
615
+ tanks: g.map((e) => ({
616
+ ...e,
617
+ self: e.id === t,
618
+ reload: Math.min(1, (b - (P.get(e.id) ?? -Infinity)) / F.reloadMs)
619
+ })),
620
+ shells: _.map((e) => ({ ...e })),
621
+ events: v,
622
+ status: w,
623
+ winner: T,
624
+ net: {
625
+ ...D,
626
+ turnConfigured: D.turnConfigured ?? p
627
+ }
628
+ };
629
+ return v = [], e;
630
+ },
631
+ configureTurn(e, t) {
632
+ return E || w === "over" ? Promise.reject(/* @__PURE__ */ Error("La partida ya terminó.")) : X.configureTurn(e, t);
633
+ },
634
+ reconnect() {
635
+ !E && w !== "over" && (X.requestReconnect ? X.requestReconnect() : X.connect());
636
+ },
637
+ rematch() {
638
+ !E && w === "over" && (L.add(t), U({ k: "rematch" }), ce());
639
+ },
640
+ resign() {
641
+ !E && w !== "over" && (k = !0, A = "resigned", U({ k: "resign" }), q(m));
642
+ },
643
+ destroy() {
644
+ E || (E = !0, X.destroy(), _ = [], j = [], v = []);
645
+ }
646
+ };
647
+ }
648
+ var V = 180 / Math.PI, H = (e, t) => Number.isFinite(e) ? e : t;
649
+ function U(e) {
650
+ let t = e || {}, n = t.bounds || t;
651
+ if (Number.isFinite(n.minX) && Number.isFinite(n.maxX) && Number.isFinite(n.minZ) && Number.isFinite(n.maxZ)) return W({
652
+ minX: n.minX,
653
+ minZ: n.minZ,
654
+ maxX: n.maxX,
655
+ maxZ: n.maxZ
656
+ });
657
+ let r = H(t.width, H(t.size, 120)), i = H(t.depth, H(t.height, H(t.size, r)));
658
+ return W(t.centered === !0 || t.origin === "center" ? {
659
+ minX: -r / 2,
660
+ minZ: -i / 2,
661
+ maxX: r / 2,
662
+ maxZ: i / 2
663
+ } : {
664
+ minX: 0,
665
+ minZ: 0,
666
+ maxX: r,
667
+ maxZ: i
668
+ });
669
+ }
670
+ function W(e) {
671
+ return {
672
+ ...e,
673
+ width: e.maxX - e.minX,
674
+ depth: e.maxZ - e.minZ,
675
+ cx: (e.minX + e.maxX) / 2,
676
+ cz: (e.minZ + e.maxZ) / 2
677
+ };
678
+ }
679
+ function G(e) {
680
+ return (e?.obstacles || []).filter((e) => Number.isFinite(e?.x) && Number.isFinite(e?.z)).map((e) => ({
681
+ x: e.x,
682
+ z: e.z,
683
+ r: H(e.r, H(e.radius, 1.5)),
684
+ kind: e.kind === "tree" ? "tree" : "rock"
685
+ }));
686
+ }
687
+ var K = (e) => e ? e.k ?? e.type ?? null : null;
688
+ function q(e, t) {
689
+ let n = t || [], r = e?.tank ?? e?.by ?? e?.owner ?? e?.from;
690
+ if (r != null) {
691
+ let e = n.find((e) => String(e.id) === String(r));
692
+ if (e) return e;
693
+ }
694
+ if (!Number.isFinite(e?.x) || !Number.isFinite(e?.z)) return null;
695
+ let i = null, a = Infinity;
696
+ for (let t of n) {
697
+ let n = (t.x - e.x) ** 2 + (t.z - e.z) ** 2;
698
+ n < a && (a = n, i = t);
699
+ }
700
+ return i;
701
+ }
702
+ var J = (e, t) => t == null ? null : (e || []).find((e) => String(e.id) === String(t)) || null, Y = (e) => (e || []).find((e) => e.self) || null, se = (e, t = !0) => !t || e <= 25 ? 2 : +(e <= 50), X = (e) => {
703
+ let t = e % (Math.PI * 2);
704
+ return t > Math.PI && (t -= Math.PI * 2), t < -Math.PI && (t += Math.PI * 2), t;
705
+ };
706
+ function ce({ moveSpeed: e = .6, turnSpeed: t = .35, holdMs: n = 160 } = {}) {
707
+ let r = null, i = !1, a = !1, o = 0, s = 0;
708
+ return {
709
+ update(c, l) {
710
+ let u = [];
711
+ if (!c || c.alive === !1) return i && u.push({ type: "move_end" }), a && u.push({ type: "turret_end" }), i = a = !1, r = null, u;
712
+ let d = Math.max(1, l) / 1e3;
713
+ if (r) {
714
+ let f = Math.hypot(c.x - r.x, c.z - r.z) / d, p = Math.abs(X((c.yaw ?? 0) - r.yaw)) / d, m = Math.abs(X((c.turretYaw ?? 0) - (c.yaw ?? 0) - r.rel)) / d, h = f > e || p > t;
715
+ o = h ? 0 : o + l, h && !i ? (i = !0, u.push({ type: "move_start" })) : !h && i && o >= n && (i = !1, u.push({ type: "move_end" }));
716
+ let g = m > t;
717
+ s = g ? 0 : s + l, g && !a ? (a = !0, u.push({ type: "turret_start" })) : !g && a && s >= n && (a = !1, u.push({ type: "turret_end" }));
718
+ }
719
+ return r = {
720
+ x: c.x,
721
+ z: c.z,
722
+ yaw: c.yaw ?? 0,
723
+ rel: X((c.turretYaw ?? 0) - (c.yaw ?? 0))
724
+ }, u;
725
+ },
726
+ reset() {
727
+ r = null, i = a = !1, o = s = 0;
728
+ },
729
+ get moving() {
730
+ return i;
731
+ },
732
+ get turning() {
733
+ return a;
734
+ }
735
+ };
736
+ }
737
+ function le(e) {
738
+ switch (K(e)) {
739
+ case "fire": return [{ type: "fire" }, { type: "projectile_start" }];
740
+ case "impact": return [{
741
+ type: "impact",
742
+ kind: e.kind === "obstacle" ? "obstacle" : "terrain"
743
+ }];
744
+ case "hit": return [{ type: "tank_hit" }];
745
+ case "destroyed": return [{ type: "destroyed" }];
746
+ default: return [];
747
+ }
748
+ }
749
+ function ue(e) {
750
+ if (!e || e.status !== "over") return null;
751
+ let t = Y(e.tanks);
752
+ return !t || e.winner == null ? null : String(e.winner) === String(t.id) ? "victory" : "defeat";
753
+ }
754
+ // src/games/tanks-live/scene/createLiveScene2d.js
755
+ function de(e, { arena: t } = {}) {
756
+ let n = document.createElement("canvas");
757
+ n.style.cssText = "width:100%;height:100%;display:block", e.appendChild(n);
758
+ let r = n.getContext("2d");
759
+ if (!r) throw n.remove(), Error("Canvas 2D unavailable");
760
+ let i = U(t), a = 1, o = 1, s = 1, c = 0, l = 0;
761
+ function u() {
762
+ a = Math.max(1, e.clientWidth), o = Math.max(1, e.clientHeight);
763
+ let t = Math.min(window.devicePixelRatio || 1, 2);
764
+ n.width = a * t, n.height = o * t, r.setTransform(t, 0, 0, t, 0, 0), s = Math.min(a / i.width, o / i.depth), c = (a - i.width * s) / 2, l = (o - i.depth * s) / 2;
765
+ }
766
+ u();
767
+ let d = typeof ResizeObserver == "function" ? new ResizeObserver(u) : null;
768
+ return d?.observe(e), {
769
+ kind: "2d",
770
+ resize: u,
771
+ screenToGround(e, t) {
772
+ return {
773
+ x: ((e + 1) * a / 2 - c) / s + i.minX,
774
+ z: ((1 - t) * o / 2 - l) / s + i.minZ
775
+ };
776
+ },
777
+ render(e) {
778
+ r.fillStyle = "#152519", r.fillRect(0, 0, a, o), r.save(), r.translate(c, l), r.scale(s, s), r.translate(-i.minX, -i.minZ), r.fillStyle = "#526348", r.fillRect(i.minX, i.minZ, i.width, i.depth);
779
+ for (let e of G(t)) r.fillStyle = e.kind === "tree" ? "#24472d" : "#929285", r.beginPath(), r.arc(e.x, e.z, e.r, 0, Math.PI * 2), r.fill();
780
+ for (let t of e.tanks || []) r.save(), r.translate(t.x, t.z), r.rotate(t.yaw), r.fillStyle = t.alive === !1 ? "#333" : t.color || "#73ce68", r.fillRect(-3, -1.8, 6, 3.6), r.restore(), r.save(), r.translate(t.x, t.z), r.rotate(t.turretYaw), r.strokeStyle = "#dce8cf", r.lineWidth = .8, r.beginPath(), r.moveTo(0, 0), r.lineTo(5, 0), r.stroke(), r.restore(), t.self && (r.strokeStyle = "#fff", r.lineWidth = .3, r.beginPath(), r.arc(t.x, t.z, 4, 0, Math.PI * 2), r.stroke());
781
+ r.fillStyle = "#ffe097";
782
+ for (let t of e.shells || []) r.beginPath(), r.arc(t.x, t.z, .7, 0, Math.PI * 2), r.fill();
783
+ r.restore();
784
+ },
785
+ dispose() {
786
+ d?.disconnect(), n.remove();
787
+ }
788
+ };
789
+ }
790
+ // src/games/tanks-live/TanksLiveGameCard.jsx
791
+ var fe = /* @__PURE__ */ k({ default: () => Q }), pe = (t, n) => {
792
+ e.getState().act(t, n).catch(() => {});
793
+ }, Z = (e) => {
794
+ e.throttle = 0, e.steer = 0, e.fire = !1;
795
+ };
796
+ function me(e, t) {
797
+ let n = e?.manualTurn;
798
+ return n?.status === "applying" ? "Aplicando configuración…" : n?.status === "sharing" ? "Sincronizando con el otro jugador…" : n?.status === "waiting_peer" ? "Esperando al otro jugador para compartir la configuración…" : e?.phase === "connected" ? "¡Conexión establecida!" : n?.status === "failed" || e?.phase === "failed" || e?.ice === "failed" ? "No pudimos conectar. Puedes reintentar." : t ? "La conexión está tardando más de lo esperado" : e?.phase === "restarting" || e?.phase === "reconnecting" ? "Reconectando…" : e?.phase === "listening" ? "Esperando la conexión del otro jugador…" : e?.phase === "calling" ? "Buscando al otro jugador…" : e?.phase === "negotiating" ? "Buscando una conexión entre los jugadores…" : "Preparando partida…";
799
+ }
800
+ function he({ gameKey: n, currentUserId: r, onMinimize: i, onDismiss: a, suspended: f = !1 }) {
801
+ let h = p(o)?.config?.webrtc, C = e((e) => e.games[n]), w = g(f);
802
+ w.current = f;
803
+ let O = g(null), k = g(null), A = g(null), M = g({
804
+ throttle: 0,
805
+ steer: 0,
806
+ fire: !1
807
+ }), [F, I] = _(null), [L, ee] = _(""), [te, R] = _(!1), [ne, z] = _("loading"), [re, ie] = _(!1), [B, ae] = _(0), V = F?.net?.phase === "connected";
808
+ m(() => {
809
+ if (!C || C.status !== "active" || V) {
810
+ ae(0);
811
+ return;
812
+ }
813
+ let e = Date.now(), t = setInterval(() => ae(Date.now() - e), 1e3);
814
+ return () => clearInterval(t);
815
+ }, [
816
+ n,
817
+ C?.status,
818
+ V
819
+ ]), m(() => {
820
+ ie(!1);
821
+ }, [n, C?.status]), m(() => {
822
+ f ? (Z(M.current), s()) : A.current?.resize();
823
+ }, [f]);
824
+ let H = ["active", "won"].includes(C?.status), [U, W] = _(null);
825
+ if (m(() => {
826
+ if (!H || !U || !O.current) return;
827
+ let e = !1, t = null, n = {
828
+ arena: U,
829
+ lowPower: matchMedia("(pointer: coarse)").matches,
830
+ reducedMotion: matchMedia("(prefers-reduced-motion: reduce)").matches,
831
+ onEvent: (e) => {
832
+ e.type === "context_lost" && R(!0);
833
+ }
834
+ }, r = () => {
835
+ e || (t = de(O.current, n), A.current = t, z("2d"));
836
+ };
837
+ return te ? r() : import("./createLiveScene-BqI7qV4n.js").then(({ createLiveScene: i }) => {
838
+ if (!e) try {
839
+ t = i(O.current, n), A.current = t, z("3d");
840
+ } catch {
841
+ r();
842
+ }
843
+ }).catch(r), () => {
844
+ e = !0, t?.dispose(), A.current === t && (A.current = null);
845
+ };
846
+ }, [
847
+ H,
848
+ U,
849
+ te
850
+ ]), m(() => {
851
+ if (!H || C.status !== "active" || !O.current) return;
852
+ let t = !1, i = 0, a = performance.now(), o = 0, d = null, f = ce(), p = /* @__PURE__ */ new Set();
853
+ c();
854
+ let m;
855
+ try {
856
+ let t = e.getState().createLiveSignaling(n);
857
+ m = oe({
858
+ iceServers: h?.iceServers,
859
+ iceTransportPolicy: h?.iceTransportPolicy,
860
+ game: C,
861
+ selfId: String(r),
862
+ isHost: String(C.hostId) === String(r),
863
+ signaling: t
864
+ }), k.current = m;
865
+ } catch (e) {
866
+ ee(e.message || "No se pudo iniciar WebRTC");
867
+ return;
868
+ }
869
+ W(m.getRender().arena);
870
+ function g() {
871
+ p.clear(), Z(M.current), s();
872
+ }
873
+ function _(e, t) {
874
+ /^(INPUT|TEXTAREA|SELECT)$/.test(e.target?.tagName) || [
875
+ "KeyW",
876
+ "KeyA",
877
+ "KeyS",
878
+ "KeyD",
879
+ "ArrowUp",
880
+ "ArrowDown",
881
+ "ArrowLeft",
882
+ "ArrowRight",
883
+ "Space"
884
+ ].includes(e.code) && (e.preventDefault(), t ? p.add(e.code) : p.delete(e.code), M.current.throttle = Number(p.has("KeyW") || p.has("ArrowUp")) - Number(p.has("KeyS") || p.has("ArrowDown")), M.current.steer = Number(p.has("KeyD") || p.has("ArrowRight")) - Number(p.has("KeyA") || p.has("ArrowLeft")), M.current.fire = p.has("Space"));
885
+ }
886
+ let v = (e) => _(e, !0), y = (e) => _(e, !1), b = O.current;
887
+ b.addEventListener("keydown", v), b.addEventListener("keyup", y), b.addEventListener("blur", g);
888
+ let x = () => {
889
+ document.hidden && g();
890
+ };
891
+ document.addEventListener("visibilitychange", x), l({ type: "engine_start" }, n);
892
+ function S(r) {
893
+ if (t) return;
894
+ let c = Math.min(80, r - a);
895
+ a = r;
896
+ let h = w.current || document.hidden;
897
+ m.setInput(h ? {
898
+ throttle: 0,
899
+ steer: 0,
900
+ fire: !1
901
+ } : M.current), m.step(c);
902
+ let g = m.getRender();
903
+ if (h || A.current?.render(g, c), h && (p.clear(), f.reset(), s()), !h) for (let e of g.events || []) for (let t of le(e)) l(t, e.id);
904
+ if (!h) for (let e of f.update(Y(g.tanks), c)) l(e);
905
+ let _ = ue(g);
906
+ _ && !d && (I(g), d = _, h || u(d, n), e.getState().liveFinish?.(n, g.winner)), r - o > 100 && (o = r, I(g)), i = requestAnimationFrame(S);
907
+ }
908
+ return i = requestAnimationFrame(S), () => {
909
+ t = !0, cancelAnimationFrame(i), g(), m.destroy(), k.current = null, b.removeEventListener("keydown", v), b.removeEventListener("keyup", y), b.removeEventListener("blur", g), document.removeEventListener("visibilitychange", x);
910
+ };
911
+ }, [
912
+ H,
913
+ n,
914
+ r
915
+ ]), !C) return null;
916
+ let G = String(r) === String(C.guestId), K = F?.status === "over" || C.status === "won", q = F?.net, J = q?.manualTurn, se = [
917
+ "waiting_peer",
918
+ "sharing",
919
+ "applying"
920
+ ].includes(J?.status), X = !V && (B >= 12e3 || q?.phase === "failed" || q?.ice === "failed" || J?.status === "failed" || !!L), fe = V ? q?.route?.relay === !0 ? "TURN Relay" : q?.route?.relay === !1 ? "P2P" : null : null, he = async (e, t) => {
921
+ if (!k.current?.configureTurn) throw Error("La partida aún no está preparada. Espera un momento y vuelve a intentarlo.");
922
+ await k.current.configureTurn(e, t);
923
+ };
924
+ function Q(e) {
925
+ let t = O.current?.getBoundingClientRect(), n = Y(F?.tanks);
926
+ if (!t || !n) return;
927
+ let r = A.current?.screenToGround((e.clientX - t.left) / t.width * 2 - 1, 1 - (e.clientY - t.top) / t.height * 2);
928
+ r && (M.current.turretYaw = Math.atan2(r.z - n.z, r.x - n.x));
929
+ }
930
+ let $ = (e, t) => ({
931
+ onPointerDown: (n) => {
932
+ n.preventDefault(), n.currentTarget.setPointerCapture(n.pointerId), M.current[e] = t;
933
+ },
934
+ onPointerUp: () => {
935
+ M.current[e] = e !== "fire" && 0;
936
+ },
937
+ onPointerCancel: () => {
938
+ M.current[e] = e !== "fire" && 0;
939
+ },
940
+ onLostPointerCapture: () => {
941
+ M.current[e] = e !== "fire" && 0;
942
+ }
943
+ });
944
+ return /* @__PURE__ */ D("section", {
945
+ className: `chat-game-card tl-card${H ? " tl-card--active" : ""}`,
946
+ "aria-label": "Tanques LIVE 3D",
947
+ children: [
948
+ /* @__PURE__ */ D("header", {
949
+ className: "tl-header",
950
+ children: [/* @__PURE__ */ E("strong", { children: "Tanques LIVE 3D" }), /* @__PURE__ */ D("div", {
951
+ className: "tl-header-actions",
952
+ children: [
953
+ /* @__PURE__ */ E(t, { manager: d() }),
954
+ i && /* @__PURE__ */ E("button", {
955
+ onClick: i,
956
+ "aria-label": "Minimizar",
957
+ children: "−"
958
+ }),
959
+ a && /* @__PURE__ */ E("button", {
960
+ onClick: a,
961
+ "aria-label": "Ocultar",
962
+ children: "×"
963
+ })
964
+ ]
965
+ })]
966
+ }),
967
+ C.status === "invited" && /* @__PURE__ */ D("div", {
968
+ className: "tl-invite",
969
+ children: [
970
+ /* @__PURE__ */ E("span", {
971
+ className: "tl-invite-icon",
972
+ "aria-hidden": "true",
973
+ children: "🪖"
974
+ }),
975
+ /* @__PURE__ */ E("strong", { children: G ? "¿Listo para combatir?" : "Invitación enviada" }),
976
+ /* @__PURE__ */ E("p", { children: G ? "Te invitaron a un combate en tiempo real." : "Esperando que el rival acepte…" }),
977
+ G && /* @__PURE__ */ D("div", {
978
+ className: "tl-invite-actions",
979
+ children: [/* @__PURE__ */ E("button", {
980
+ className: "tl-button-primary",
981
+ onClick: () => pe(n, "game_accept"),
982
+ children: "Aceptar combate"
983
+ }), /* @__PURE__ */ E("button", {
984
+ onClick: () => pe(n, "game_decline"),
985
+ children: "Rechazar"
986
+ })]
987
+ })
988
+ ]
989
+ }),
990
+ H && !K && /* @__PURE__ */ D("div", {
991
+ className: `tl-connection tl-connection--${V ? "connected" : X ? "problem" : "pending"}`,
992
+ children: [
993
+ /* @__PURE__ */ D("div", {
994
+ className: "tl-connection-heading",
995
+ children: [
996
+ V ? /* @__PURE__ */ E(y, {
997
+ size: 18,
998
+ "aria-hidden": "true"
999
+ }) : X ? /* @__PURE__ */ E(v, {
1000
+ size: 18,
1001
+ "aria-hidden": "true"
1002
+ }) : /* @__PURE__ */ E(x, {
1003
+ size: 18,
1004
+ className: "tl-connection-spinner",
1005
+ "aria-hidden": "true"
1006
+ }),
1007
+ /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("strong", { children: X ? "Problemas de conexión" : me(q, !1) }), X && /* @__PURE__ */ E("p", { children: "No pudimos establecer una conexión con el otro jugador. Puedes configurar una conexión alternativa para continuar." })] }),
1008
+ fe && /* @__PURE__ */ E("span", {
1009
+ className: "tl-route-badge",
1010
+ children: fe
1011
+ })
1012
+ ]
1013
+ }),
1014
+ !V && /* @__PURE__ */ E("p", {
1015
+ className: "tl-connection-message",
1016
+ role: "status",
1017
+ children: X ? me(q, B >= 12e3) : P(q)
1018
+ }),
1019
+ J?.status === "synchronized" && /* @__PURE__ */ E("p", {
1020
+ className: "tl-connection-shared",
1021
+ role: "status",
1022
+ children: J.received ? "Configuración recibida del otro jugador y aplicada en ambos." : "Configuración compartida y aplicada en ambos jugadores."
1023
+ }),
1024
+ J?.expiresAt != null && J.expiresAt <= Date.now() && /* @__PURE__ */ E("p", {
1025
+ className: "tl-connection-expired",
1026
+ role: "status",
1027
+ children: "La validez indicada para las credenciales terminó. Introduce una configuración nueva si necesitas reconectar."
1028
+ }),
1029
+ /* @__PURE__ */ D("div", {
1030
+ className: "tl-connection-actions",
1031
+ children: [/* @__PURE__ */ D("button", {
1032
+ type: "button",
1033
+ className: X ? "tl-button-primary" : "",
1034
+ onClick: () => ie(!0),
1035
+ children: [/* @__PURE__ */ E(S, {
1036
+ size: 15,
1037
+ "aria-hidden": "true"
1038
+ }), "Configurar conexión alternativa"]
1039
+ }), !V && /* @__PURE__ */ D("button", {
1040
+ type: "button",
1041
+ disabled: se,
1042
+ onClick: () => k.current?.reconnect(),
1043
+ children: [/* @__PURE__ */ E(b, {
1044
+ size: 15,
1045
+ "aria-hidden": "true"
1046
+ }), "Reintentar conexión"]
1047
+ })]
1048
+ })
1049
+ ]
1050
+ }),
1051
+ /* @__PURE__ */ E(j, {
1052
+ open: re && H && !K,
1053
+ onClose: () => ie(!1),
1054
+ onApply: he,
1055
+ manualTurn: J
1056
+ }),
1057
+ (H || F) && /* @__PURE__ */ D(T, { children: [
1058
+ /* @__PURE__ */ D("div", {
1059
+ className: "tl-hud",
1060
+ children: [(F?.tanks || []).map((e) => /* @__PURE__ */ D("span", { children: [
1061
+ e.self ? "Tú" : "Rival",
1062
+ " ",
1063
+ /* @__PURE__ */ E("meter", {
1064
+ min: "0",
1065
+ max: "100",
1066
+ value: e.hp,
1067
+ "aria-label": e.self ? "Tu vida" : "Vida del rival"
1068
+ }),
1069
+ " ",
1070
+ e.hp,
1071
+ " HP"
1072
+ ] }, e.id)), /* @__PURE__ */ D("span", {
1073
+ role: "status",
1074
+ children: [
1075
+ V ? fe || "Conectado" : me(q, B >= 12e3),
1076
+ " ",
1077
+ Number.isFinite(q?.rtt) ? `· ${Math.round(q.rtt)} ms` : ""
1078
+ ]
1079
+ })]
1080
+ }),
1081
+ /* @__PURE__ */ E("div", {
1082
+ className: "tl-stage",
1083
+ ref: O,
1084
+ tabIndex: 0,
1085
+ "aria-label": "Arena. WASD o flechas para conducir, ratón para apuntar, clic o espacio para disparar",
1086
+ onPointerMove: Q,
1087
+ onPointerDown: (e) => {
1088
+ Q(e), e.pointerType === "mouse" && (e.currentTarget.focus(), e.currentTarget.setPointerCapture(e.pointerId), Q(e), M.current.fire = !0);
1089
+ },
1090
+ onPointerUp: () => {
1091
+ M.current.fire = !1;
1092
+ },
1093
+ onPointerCancel: () => {
1094
+ Z(M.current);
1095
+ },
1096
+ onLostPointerCapture: () => {
1097
+ M.current.fire = !1;
1098
+ }
1099
+ }),
1100
+ /* @__PURE__ */ D("div", {
1101
+ className: "tl-controls",
1102
+ children: [
1103
+ /* @__PURE__ */ E("div", {
1104
+ className: "tl-joystick",
1105
+ role: "group",
1106
+ "aria-label": "Joystick de conducción",
1107
+ onPointerDown: (e) => {
1108
+ e.preventDefault(), e.currentTarget.setPointerCapture(e.pointerId);
1109
+ let t = e.currentTarget.getBoundingClientRect();
1110
+ M.current.steer = Math.max(-1, Math.min(1, (e.clientX - t.left - t.width / 2) / (t.width / 2))), M.current.throttle = Math.max(-1, Math.min(1, -(e.clientY - t.top - t.height / 2) / (t.height / 2)));
1111
+ },
1112
+ onPointerMove: (e) => {
1113
+ if (!e.currentTarget.hasPointerCapture(e.pointerId)) return;
1114
+ let t = e.currentTarget.getBoundingClientRect();
1115
+ M.current.steer = Math.max(-1, Math.min(1, (e.clientX - t.left - t.width / 2) / (t.width / 2))), M.current.throttle = Math.max(-1, Math.min(1, -(e.clientY - t.top - t.height / 2) / (t.height / 2)));
1116
+ },
1117
+ onPointerUp: () => Z(M.current),
1118
+ onPointerCancel: () => Z(M.current),
1119
+ onLostPointerCapture: () => Z(M.current),
1120
+ children: "✥"
1121
+ }),
1122
+ /* @__PURE__ */ D("div", {
1123
+ className: "tl-drive",
1124
+ "aria-label": "Controles de conducción",
1125
+ children: [
1126
+ /* @__PURE__ */ E("button", {
1127
+ "aria-label": "Avanzar",
1128
+ ...$("throttle", 1),
1129
+ children: "▲"
1130
+ }),
1131
+ /* @__PURE__ */ E("button", {
1132
+ "aria-label": "Girar izquierda",
1133
+ ...$("steer", -1),
1134
+ children: "◀"
1135
+ }),
1136
+ /* @__PURE__ */ E("button", {
1137
+ "aria-label": "Retroceder",
1138
+ ...$("throttle", -1),
1139
+ children: "▼"
1140
+ }),
1141
+ /* @__PURE__ */ E("button", {
1142
+ "aria-label": "Girar derecha",
1143
+ ...$("steer", 1),
1144
+ children: "▶"
1145
+ })
1146
+ ]
1147
+ }),
1148
+ /* @__PURE__ */ E("button", {
1149
+ className: "tl-fire",
1150
+ ...$("fire", !0),
1151
+ children: "Disparar"
1152
+ })
1153
+ ]
1154
+ }),
1155
+ /* @__PURE__ */ E("div", {
1156
+ className: "tl-reload",
1157
+ children: /* @__PURE__ */ D("label", { children: [
1158
+ "Recarga ",
1159
+ /* @__PURE__ */ E("progress", {
1160
+ max: "1",
1161
+ value: Y(F?.tanks)?.reload ?? 1
1162
+ }),
1163
+ " ",
1164
+ (Y(F?.tanks)?.reload ?? 1) >= 1 ? "Listo" : "Recargando…"
1165
+ ] })
1166
+ }),
1167
+ /* @__PURE__ */ E("small", { children: "WASD / flechas · ratón o toca la arena para apuntar · espacio / clic para disparar" }),
1168
+ /* @__PURE__ */ E("button", {
1169
+ onClick: () => R((e) => !e),
1170
+ children: ne === "2d" ? "Probar 3D" : "Vista 2D"
1171
+ })
1172
+ ] }),
1173
+ K && /* @__PURE__ */ D("p", {
1174
+ role: "status",
1175
+ children: [
1176
+ String(F?.winner ?? C.winnerId) === String(r) ? "¡Victoria!" : "Fin del combate",
1177
+ " ",
1178
+ /* @__PURE__ */ E("button", {
1179
+ onClick: () => {
1180
+ Promise.resolve(e.getState().liveRematch?.(n)).catch(() => {});
1181
+ },
1182
+ children: "Revancha"
1183
+ })
1184
+ ]
1185
+ }),
1186
+ H && /* @__PURE__ */ D("details", {
1187
+ className: "tl-diagnostics",
1188
+ children: [/* @__PURE__ */ E("summary", { children: "Diagnóstico de conexión" }), /* @__PURE__ */ E("pre", { children: JSON.stringify(N(F?.net), null, 2) })]
1189
+ }),
1190
+ H && !K && /* @__PURE__ */ E("button", {
1191
+ onClick: () => k.current?.resign(),
1192
+ children: "Rendirse"
1193
+ }),
1194
+ C.pending && /* @__PURE__ */ E("p", {
1195
+ role: "status",
1196
+ children: "Enviando…"
1197
+ }),
1198
+ C.pendingEvent && C.error && /* @__PURE__ */ E("button", {
1199
+ onClick: () => {
1200
+ e.getState().retry(n).catch(() => {});
1201
+ },
1202
+ children: "Reintentar envío"
1203
+ }),
1204
+ L && /* @__PURE__ */ E("p", {
1205
+ role: "alert",
1206
+ children: L
1207
+ }),
1208
+ C.error && /* @__PURE__ */ E("p", {
1209
+ role: "alert",
1210
+ children: C.error
1211
+ }),
1212
+ !["closed", "declined"].includes(C.status) && /* @__PURE__ */ E("footer", {
1213
+ className: "tl-footer",
1214
+ children: /* @__PURE__ */ E("button", {
1215
+ onClick: () => pe(n, "game_end"),
1216
+ children: "Cerrar partida"
1217
+ })
1218
+ })
1219
+ ]
1220
+ });
1221
+ }
1222
+ var Q = f(he);
1223
+ export { se as a, J as c, Q as default, G as i, X as l, V as n, K as o, U as r, q as s, fe as t };
1224
+
1225
+ //# sourceMappingURL=TanksLiveGameCard-DGsnMD3j.js.map