most-box 0.2.9 → 0.3.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 (52) hide show
  1. package/out/admin/index.html +0 -0
  2. package/out/app/index.html +0 -0
  3. package/out/assets/{FilePreviewOverlay-BYZIAMJ-.js → FilePreviewOverlay-CM5IqnZD.js} +1 -1
  4. package/out/assets/{MarketingHeader-ceWdRCpe.js → MarketingHeader-B45AaM29.js} +1 -1
  5. package/out/assets/{MarketingLayout-Bun1d2wL.js → MarketingLayout-A00pEOa2.js} +1 -1
  6. package/out/assets/{MilkdownEditor-CT6SHYKG.js → MilkdownEditor-BTq9bDAf.js} +1 -1
  7. package/out/assets/{OpenSidebarButton-CLSCB7CG.js → OpenSidebarButton-D8qfUGzU.js} +1 -1
  8. package/out/assets/{SidebarAccount-CNh0kvtB.js → SidebarAccount-_RBE5K_o.js} +1 -1
  9. package/out/assets/{SidebarHomeLink-DXv0c9Ob.js → SidebarHomeLink-qMFEGjZt.js} +1 -1
  10. package/out/assets/{downloadValidation-Bzl7cci9.js → downloadValidation-CSXwoQKW.js} +1 -1
  11. package/out/assets/index-BRhsWNj1.css +1 -0
  12. package/out/assets/{index-BI8oNSVC.js → index-C-_ZwxuN.js} +2 -2
  13. package/out/assets/index-CYVRmppz.css +1 -0
  14. package/out/assets/index.lazy-0YYV0CR3.js +1 -0
  15. package/out/assets/index.lazy-BTYGr9Vb.js +1 -0
  16. package/out/assets/{index.lazy-BOMHingE.js → index.lazy-BVAtQtTa.js} +1 -1
  17. package/out/assets/{index.lazy-ur0joIFl.js → index.lazy-BbdLA2L-.js} +1 -1
  18. package/out/assets/{index.lazy-bAp_Hgck.js → index.lazy-CbiLtziY.js} +1 -1
  19. package/out/assets/{index.lazy-D4zNn3D6.js → index.lazy-CcEdBg3i.js} +2 -2
  20. package/out/assets/index.lazy-CePHsLXG.js +1 -0
  21. package/out/assets/{index.lazy-Ch4Y17pJ.js → index.lazy-DG_3_VdF.js} +1 -1
  22. package/out/assets/{index.lazy-7omTRykl.js → index.lazy-DR0rWEWZ.js} +1 -1
  23. package/out/assets/{index.lazy-BSY5_gby.js → index.lazy-DSyTSCJD.js} +1 -1
  24. package/out/assets/{index.lazy-C4ipXKZ7.js → index.lazy-DUhsuPLv.js} +1 -1
  25. package/out/assets/{index.lazy-_mEd0Q_u.js → index.lazy-DVnhWbzH.js} +1 -1
  26. package/out/assets/{index.lazy-CPZbEnJZ.js → index.lazy-DZpkg1DJ.js} +1 -1
  27. package/out/assets/useChannelMessages-DOaWHdAP.js +4 -0
  28. package/out/assets/{useGameRoom-cs9YpYt7.js → useGameRoom-hHkV65ii.js} +1 -1
  29. package/out/assets/{userProfile-qcc60tHK.js → userProfile-DwZip0Dt.js} +1 -1
  30. package/out/chat/index.html +0 -0
  31. package/out/chat/join/index.html +0 -0
  32. package/out/download/index.html +2 -2
  33. package/out/game/gandengyan/index.html +0 -0
  34. package/out/game/index.html +0 -0
  35. package/out/game/zhajinhua/index.html +0 -0
  36. package/out/index.html +2 -2
  37. package/out/note/index.html +0 -0
  38. package/out/ping/index.html +2 -2
  39. package/out/profile/index.html +0 -0
  40. package/out/web3/index.html +0 -0
  41. package/package.json +1 -1
  42. package/server/index.js +59 -2
  43. package/server/src/http/app.js +7 -1
  44. package/server/src/http/nodeStatus.js +6 -0
  45. package/server/src/http/routes/channelRoutes.js +12 -0
  46. package/server/src/index.js +476 -3
  47. package/out/assets/index-3OD3Chi9.css +0 -1
  48. package/out/assets/index-BSOvFG3o.css +0 -1
  49. package/out/assets/index.lazy-CvBIIYbm.js +0 -1
  50. package/out/assets/index.lazy-Cz-OZwYL.js +0 -1
  51. package/out/assets/index.lazy-DRY3xoQp.js +0 -1
  52. package/out/assets/useChannelMessages-BeKF0gwo.js +0 -3
@@ -93,6 +93,8 @@ import {
93
93
  } from './config.js'
94
94
 
95
95
  const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
96
+ const CHANNEL_PRESENCE_HEARTBEAT_MS = 15 * 1000
97
+ const CHANNEL_PRESENCE_TIMEOUT_MS = 45 * 1000
96
98
  const CHANNEL_WELCOME_MESSAGE = '我加入了群聊'
97
99
 
98
100
  function createMemoryDuplexPair() {
@@ -158,6 +160,9 @@ export class MostBoxEngine extends EventEmitter {
158
160
  #channelPeers = new Map()
159
161
  #channelCandidateCache = new Map()
160
162
  #channelStreams = new Set()
163
+ #channelPresenceSessions = new Map()
164
+ #channelPresenceProfiles = new Map()
165
+ #channelPresenceSweepTimer = null
161
166
 
162
167
  #accountMetadata = { profiles: {} }
163
168
 
@@ -190,6 +195,90 @@ export class MostBoxEngine extends EventEmitter {
190
195
  }
191
196
  }
192
197
 
198
+ getChannelPresence(channelKeyInput, options = {}) {
199
+ this.#ensureInitialized()
200
+ this.#assertChannelMember(channelKeyInput, options.ownerAddress)
201
+ const channel = this.#resolveChannel(channelKeyInput, options.ownerAddress)
202
+ this.#pruneStaleChannelPresence()
203
+ return this.#getChannelPresenceList(channel.channelKey)
204
+ }
205
+
206
+ joinChannelPresence(channelKeyInput, options = {}) {
207
+ this.#ensureInitialized()
208
+ this.#assertChannelMember(channelKeyInput, options.ownerAddress)
209
+ const channel = this.#resolveChannel(channelKeyInput, options.ownerAddress)
210
+ const event = this.#upsertChannelPresenceSession(channel, {
211
+ ...options,
212
+ address: options.ownerAddress,
213
+ local: true,
214
+ })
215
+ if (event) {
216
+ this.#broadcastChannelPresence(event)
217
+ }
218
+ return this.#getChannelPresenceList(channel.channelKey)
219
+ }
220
+
221
+ heartbeatChannelPresence(channelKeyInput, options = {}) {
222
+ this.#ensureInitialized()
223
+ this.#assertChannelMember(channelKeyInput, options.ownerAddress)
224
+ const channel = this.#resolveChannel(channelKeyInput, options.ownerAddress)
225
+ const event = this.#touchChannelPresenceSession(channel, {
226
+ ...options,
227
+ address: options.ownerAddress,
228
+ local: true,
229
+ })
230
+ this.#broadcastChannelPresence(
231
+ event ||
232
+ this.#formatChannelPresence(
233
+ channel.channelKey,
234
+ options.ownerAddress,
235
+ 'heartbeat'
236
+ )
237
+ )
238
+ return this.#getChannelPresenceList(channel.channelKey)
239
+ }
240
+
241
+ updateChannelPresenceProfile(channelKeyInput, options = {}) {
242
+ this.#ensureInitialized()
243
+ this.#assertChannelMember(channelKeyInput, options.ownerAddress)
244
+ const channel = this.#resolveChannel(channelKeyInput, options.ownerAddress)
245
+ const event = this.#updateChannelPresenceProfile(channel, {
246
+ ...options,
247
+ address: options.ownerAddress,
248
+ local: true,
249
+ })
250
+ if (event) {
251
+ this.#broadcastChannelPresence(event)
252
+ }
253
+ return this.#getChannelPresenceList(channel.channelKey)
254
+ }
255
+
256
+ leaveChannelPresence(channelKeyInput, options = {}) {
257
+ this.#ensureInitialized()
258
+ this.#assertChannelMember(channelKeyInput, options.ownerAddress)
259
+ const channel = this.#resolveChannel(channelKeyInput, options.ownerAddress)
260
+ const events = this.#removeChannelPresenceSessions(channel.channelKey, {
261
+ ...options,
262
+ address: options.ownerAddress,
263
+ })
264
+ events.forEach(event => this.#broadcastChannelPresence(event))
265
+ return this.#getChannelPresenceList(channel.channelKey)
266
+ }
267
+
268
+ clearChannelPresenceSource(sourceId, options = {}) {
269
+ this.#ensureInitialized()
270
+ const events = this.#removeChannelPresenceSessionsBySource(sourceId)
271
+ if (options.broadcast) {
272
+ events.forEach(event => this.#broadcastChannelPresence(event))
273
+ }
274
+ return events
275
+ }
276
+
277
+ pruneChannelPresence() {
278
+ this.#ensureInitialized()
279
+ return this.#pruneStaleChannelPresence()
280
+ }
281
+
193
282
  /**
194
283
  * 初始化引擎 — 必须在调用其他方法之前调用
195
284
  */
@@ -363,6 +452,7 @@ export class MostBoxEngine extends EventEmitter {
363
452
  console.log(`[MostBox] Engine initialized successfully`)
364
453
  this.emit('ready')
365
454
  this.#resumeHoldingsInBackground()
455
+ this.#startChannelPresenceSweeper()
366
456
 
367
457
  return this
368
458
  }
@@ -419,6 +509,7 @@ export class MostBoxEngine extends EventEmitter {
419
509
  this.#channelPeers.clear()
420
510
  this.#channelCandidateCache.clear()
421
511
  this.#channelStreams.clear()
512
+ this.#clearChannelPresenceRuntime()
422
513
  this.#channels = []
423
514
 
424
515
  if (this.#store) {
@@ -3111,6 +3202,312 @@ export class MostBoxEngine extends EventEmitter {
3111
3202
  )
3112
3203
  }
3113
3204
 
3205
+ #normalizePresenceSessionId(sessionId) {
3206
+ return String(sessionId || 'default').trim().slice(0, 120) || 'default'
3207
+ }
3208
+
3209
+ #normalizePresenceSourceId(options = {}) {
3210
+ const sourceId = String(options.sourceId || '').trim()
3211
+ if (sourceId) return sourceId.slice(0, 160)
3212
+ const sourcePeerId = String(options.sourcePeerId || '').trim()
3213
+ if (sourcePeerId) return `peer:${sourcePeerId}`.slice(0, 180)
3214
+ return 'local'
3215
+ }
3216
+
3217
+ #getPresenceSessionKey(options = {}) {
3218
+ return [
3219
+ this.#normalizePresenceSourceId(options),
3220
+ normalizeOwnerAddress(options.address),
3221
+ this.#normalizePresenceSessionId(options.sessionId),
3222
+ ].join(':')
3223
+ }
3224
+
3225
+ #getChannelPresenceSessionMap(channelKey) {
3226
+ if (!this.#channelPresenceSessions.has(channelKey)) {
3227
+ this.#channelPresenceSessions.set(channelKey, new Map())
3228
+ }
3229
+ return this.#channelPresenceSessions.get(channelKey)
3230
+ }
3231
+
3232
+ #getChannelPresenceProfileMap(channelKey) {
3233
+ if (!this.#channelPresenceProfiles.has(channelKey)) {
3234
+ this.#channelPresenceProfiles.set(channelKey, new Map())
3235
+ }
3236
+ return this.#channelPresenceProfiles.get(channelKey)
3237
+ }
3238
+
3239
+ #isChannelPresenceAddressOnline(channelKey, address) {
3240
+ const normalizedAddress = normalizeOwnerAddress(address)
3241
+ if (!normalizedAddress) return false
3242
+ const sessions = this.#channelPresenceSessions.get(channelKey)
3243
+ if (!sessions) return false
3244
+ return [...sessions.values()].some(
3245
+ session => session.address === normalizedAddress
3246
+ )
3247
+ }
3248
+
3249
+ #getChannelPresenceList(channelKey) {
3250
+ const sessions = this.#channelPresenceSessions.get(channelKey)
3251
+ if (!sessions) return []
3252
+ const addresses = uniqueStrings(
3253
+ [...sessions.values()].map(session => session.address)
3254
+ )
3255
+ return addresses
3256
+ .map(address => this.#formatChannelPresence(channelKey, address))
3257
+ .filter(Boolean)
3258
+ }
3259
+
3260
+ #formatChannelPresence(channelKey, address, status = 'online') {
3261
+ const normalizedAddress = normalizeOwnerAddress(address)
3262
+ if (!normalizedAddress) return null
3263
+ const sessions = [
3264
+ ...(this.#channelPresenceSessions.get(channelKey)?.values() || []),
3265
+ ].filter(session => session.address === normalizedAddress)
3266
+ const profile = this.#channelPresenceProfiles
3267
+ .get(channelKey)
3268
+ ?.get(normalizedAddress)
3269
+ const lastSeen = Math.max(
3270
+ 0,
3271
+ Number(profile?.lastSeen) || 0,
3272
+ ...sessions.map(session => Number(session.lastSeen) || 0)
3273
+ )
3274
+ return {
3275
+ channelKey,
3276
+ channelId:
3277
+ this.#channels.find(channel => channel.channelKey === channelKey)
3278
+ ?.channelId || channelKey,
3279
+ address: normalizedAddress,
3280
+ displayName: profile?.displayName || undefined,
3281
+ avatar: profile?.avatar || undefined,
3282
+ profileUpdatedAt: profile?.profileUpdatedAt || undefined,
3283
+ lastSeen,
3284
+ online: sessions.length > 0,
3285
+ local: sessions.some(session => session.local),
3286
+ status,
3287
+ }
3288
+ }
3289
+
3290
+ #upsertChannelPresenceProfile(channelKey, address, options = {}, now = Date.now()) {
3291
+ const normalizedAddress = normalizeOwnerAddress(address)
3292
+ if (!normalizedAddress) return false
3293
+ const hasDisplayName = Object.prototype.hasOwnProperty.call(
3294
+ options,
3295
+ 'displayName'
3296
+ )
3297
+ const hasAvatar = Object.prototype.hasOwnProperty.call(options, 'avatar')
3298
+ const profileUpdatedAt = Number(options.profileUpdatedAt)
3299
+ const hasProfileUpdatedAt =
3300
+ Number.isFinite(profileUpdatedAt) && profileUpdatedAt > 0
3301
+ if (!hasDisplayName && !hasAvatar && !hasProfileUpdatedAt) return false
3302
+
3303
+ const profiles = this.#getChannelPresenceProfileMap(channelKey)
3304
+ const previous = profiles.get(normalizedAddress)
3305
+ const nextUpdatedAt = hasProfileUpdatedAt
3306
+ ? Math.floor(profileUpdatedAt)
3307
+ : now
3308
+ if (
3309
+ previous?.profileUpdatedAt &&
3310
+ hasProfileUpdatedAt &&
3311
+ nextUpdatedAt < previous.profileUpdatedAt
3312
+ ) {
3313
+ return false
3314
+ }
3315
+
3316
+ const next = {
3317
+ address: normalizedAddress,
3318
+ displayName: previous?.displayName || '',
3319
+ avatar: previous?.avatar || '',
3320
+ profileUpdatedAt: nextUpdatedAt,
3321
+ lastSeen: now,
3322
+ }
3323
+ if (hasDisplayName) {
3324
+ next.displayName = normalizeChannelDisplayName(
3325
+ options.displayName,
3326
+ normalizedAddress
3327
+ )
3328
+ }
3329
+ if (hasAvatar) {
3330
+ next.avatar = normalizeChannelAvatar(options.avatar)
3331
+ }
3332
+
3333
+ const changed =
3334
+ !previous ||
3335
+ previous.displayName !== next.displayName ||
3336
+ previous.avatar !== next.avatar ||
3337
+ previous.profileUpdatedAt !== next.profileUpdatedAt
3338
+ profiles.set(normalizedAddress, next)
3339
+ return changed
3340
+ }
3341
+
3342
+ #emitChannelPresence(channelKey, address, status) {
3343
+ const event = this.#formatChannelPresence(channelKey, address, status)
3344
+ if (event) {
3345
+ this.emit('channel:presence', event)
3346
+ }
3347
+ return event
3348
+ }
3349
+
3350
+ #upsertChannelPresenceSession(channel, options = {}) {
3351
+ const address = normalizeOwnerAddress(options.address)
3352
+ if (!address) return null
3353
+ const now = Number(options.lastSeen) || Date.now()
3354
+ const channelKey = channel.channelKey
3355
+ const wasOnline = this.#isChannelPresenceAddressOnline(channelKey, address)
3356
+ const session = {
3357
+ sessionId: this.#normalizePresenceSessionId(options.sessionId),
3358
+ sourceId: this.#normalizePresenceSourceId(options),
3359
+ address,
3360
+ channelKey,
3361
+ lastSeen: now,
3362
+ local: options.local === true,
3363
+ sourcePeerId: String(options.sourcePeerId || '').trim(),
3364
+ }
3365
+ this.#getChannelPresenceSessionMap(channelKey).set(
3366
+ this.#getPresenceSessionKey(session),
3367
+ session
3368
+ )
3369
+ const profileChanged = this.#upsertChannelPresenceProfile(
3370
+ channelKey,
3371
+ address,
3372
+ options,
3373
+ now
3374
+ )
3375
+ if (!wasOnline) {
3376
+ return this.#emitChannelPresence(channelKey, address, 'online')
3377
+ }
3378
+ if (profileChanged) {
3379
+ return this.#emitChannelPresence(channelKey, address, 'profile')
3380
+ }
3381
+ return null
3382
+ }
3383
+
3384
+ #touchChannelPresenceSession(channel, options = {}) {
3385
+ const address = normalizeOwnerAddress(options.address)
3386
+ if (!address) return null
3387
+ const channelKey = channel.channelKey
3388
+ const sessionKey = this.#getPresenceSessionKey({
3389
+ ...options,
3390
+ address,
3391
+ })
3392
+ const sessions = this.#getChannelPresenceSessionMap(channelKey)
3393
+ const existing = sessions.get(sessionKey)
3394
+ if (!existing) {
3395
+ return this.#upsertChannelPresenceSession(channel, {
3396
+ ...options,
3397
+ address,
3398
+ })
3399
+ }
3400
+ existing.lastSeen = Number(options.lastSeen) || Date.now()
3401
+ sessions.set(sessionKey, existing)
3402
+ return null
3403
+ }
3404
+
3405
+ #updateChannelPresenceProfile(channel, options = {}) {
3406
+ const address = normalizeOwnerAddress(options.address)
3407
+ if (!address) return null
3408
+ const now = Number(options.lastSeen) || Date.now()
3409
+ const changed = this.#upsertChannelPresenceProfile(
3410
+ channel.channelKey,
3411
+ address,
3412
+ options,
3413
+ now
3414
+ )
3415
+ if (changed && this.#isChannelPresenceAddressOnline(channel.channelKey, address)) {
3416
+ return this.#emitChannelPresence(channel.channelKey, address, 'profile')
3417
+ }
3418
+ return null
3419
+ }
3420
+
3421
+ #removeChannelPresenceSessions(channelKey, options = {}) {
3422
+ const address = normalizeOwnerAddress(options.address)
3423
+ const sourceId = this.#normalizePresenceSourceId(options)
3424
+ const sessionId = options.sessionId
3425
+ ? this.#normalizePresenceSessionId(options.sessionId)
3426
+ : ''
3427
+ const sessions = this.#channelPresenceSessions.get(channelKey)
3428
+ if (!sessions || (!address && !sourceId)) return []
3429
+
3430
+ const touchedAddresses = new Set()
3431
+ for (const [key, session] of [...sessions]) {
3432
+ if (address && session.address !== address) continue
3433
+ if (sourceId && session.sourceId !== sourceId) continue
3434
+ if (sessionId && session.sessionId !== sessionId) continue
3435
+ touchedAddresses.add(session.address)
3436
+ sessions.delete(key)
3437
+ }
3438
+ if (sessions.size === 0) {
3439
+ this.#channelPresenceSessions.delete(channelKey)
3440
+ }
3441
+
3442
+ return [...touchedAddresses]
3443
+ .filter(item => !this.#isChannelPresenceAddressOnline(channelKey, item))
3444
+ .map(item => this.#emitChannelPresence(channelKey, item, 'offline'))
3445
+ .filter(Boolean)
3446
+ }
3447
+
3448
+ #removeChannelPresenceSessionsBySource(sourceId) {
3449
+ const normalizedSourceId = String(sourceId || '').trim()
3450
+ if (!normalizedSourceId) return []
3451
+ const events = []
3452
+ for (const [channelKey, sessions] of [...this.#channelPresenceSessions]) {
3453
+ const touchedAddresses = new Set()
3454
+ for (const [key, session] of [...sessions]) {
3455
+ if (session.sourceId !== normalizedSourceId) continue
3456
+ touchedAddresses.add(session.address)
3457
+ sessions.delete(key)
3458
+ }
3459
+ if (sessions.size === 0) {
3460
+ this.#channelPresenceSessions.delete(channelKey)
3461
+ }
3462
+ for (const address of touchedAddresses) {
3463
+ if (!this.#isChannelPresenceAddressOnline(channelKey, address)) {
3464
+ const event = this.#emitChannelPresence(channelKey, address, 'offline')
3465
+ if (event) events.push(event)
3466
+ }
3467
+ }
3468
+ }
3469
+ return events
3470
+ }
3471
+
3472
+ #pruneStaleChannelPresence(now = Date.now()) {
3473
+ const events = []
3474
+ for (const [channelKey, sessions] of [...this.#channelPresenceSessions]) {
3475
+ const touchedAddresses = new Set()
3476
+ for (const [key, session] of [...sessions]) {
3477
+ if (now - session.lastSeen <= CHANNEL_PRESENCE_TIMEOUT_MS) continue
3478
+ touchedAddresses.add(session.address)
3479
+ sessions.delete(key)
3480
+ }
3481
+ if (sessions.size === 0) {
3482
+ this.#channelPresenceSessions.delete(channelKey)
3483
+ }
3484
+ for (const address of touchedAddresses) {
3485
+ if (!this.#isChannelPresenceAddressOnline(channelKey, address)) {
3486
+ const event = this.#emitChannelPresence(channelKey, address, 'offline')
3487
+ if (event) events.push(event)
3488
+ }
3489
+ }
3490
+ }
3491
+ return events
3492
+ }
3493
+
3494
+ #startChannelPresenceSweeper() {
3495
+ if (this.#channelPresenceSweepTimer) return
3496
+ this.#channelPresenceSweepTimer = setInterval(() => {
3497
+ this.#pruneStaleChannelPresence()
3498
+ }, CHANNEL_PRESENCE_HEARTBEAT_MS)
3499
+ this.#channelPresenceSweepTimer.unref?.()
3500
+ }
3501
+
3502
+ #clearChannelPresenceRuntime() {
3503
+ if (this.#channelPresenceSweepTimer) {
3504
+ clearInterval(this.#channelPresenceSweepTimer)
3505
+ this.#channelPresenceSweepTimer = null
3506
+ }
3507
+ this.#channelPresenceSessions.clear()
3508
+ this.#channelPresenceProfiles.clear()
3509
+ }
3510
+
3114
3511
  async #getNextChannelMessageTimestamp(channelKey) {
3115
3512
  const coresMap = this.#channelCores.get(channelKey)
3116
3513
  let maxTimestamp = 0
@@ -4473,6 +4870,40 @@ export class MostBoxEngine extends EventEmitter {
4473
4870
  }
4474
4871
  }
4475
4872
 
4873
+ #sendChannelPresence(stream, event) {
4874
+ if (!stream || stream.destroyed || stream.writableEnded || !event) {
4875
+ this.#channelStreams.delete(stream)
4876
+ return false
4877
+ }
4878
+ try {
4879
+ stream.write(
4880
+ `${JSON.stringify({
4881
+ type: 'channel-presence',
4882
+ peerId: this.getNodeId(),
4883
+ channelId: event.channelId,
4884
+ channelKey: event.channelKey,
4885
+ address: event.address,
4886
+ status: event.status,
4887
+ displayName: event.displayName,
4888
+ avatar: event.avatar,
4889
+ profileUpdatedAt: event.profileUpdatedAt,
4890
+ lastSeen: event.lastSeen || Date.now(),
4891
+ sessionId: event.sessionId || 'default',
4892
+ })}\n`
4893
+ )
4894
+ return true
4895
+ } catch {
4896
+ this.#channelStreams.delete(stream)
4897
+ return false
4898
+ }
4899
+ }
4900
+
4901
+ #broadcastChannelPresence(event) {
4902
+ for (const stream of [...this.#channelStreams]) {
4903
+ this.#sendChannelPresence(stream, event)
4904
+ }
4905
+ }
4906
+
4476
4907
  async #processChannelHelloMessage(msg) {
4477
4908
  if (msg.type !== 'channel-hello') return null
4478
4909
 
@@ -4554,6 +4985,45 @@ export class MostBoxEngine extends EventEmitter {
4554
4985
  return msg.peerId
4555
4986
  }
4556
4987
 
4988
+ #processChannelPresenceMessage(msg) {
4989
+ if (msg.type !== 'channel-presence') return null
4990
+ const peerId = String(msg.peerId || '').trim()
4991
+ if (!peerId || peerId === this.getNodeId()) return null
4992
+ const channelId = normalizeChannelId(msg.channelId || msg.channelKey)
4993
+ const channelKey = buildChannelKey(channelId)
4994
+ const localChannel = this.#channels.find(
4995
+ channel => channel.channelKey === channelKey
4996
+ )
4997
+ if (!localChannel) return peerId
4998
+
4999
+ const address = normalizeOwnerAddress(msg.address)
5000
+ if (!address) return peerId
5001
+
5002
+ const status = String(msg.status || '').trim()
5003
+ const options = {
5004
+ address,
5005
+ sessionId: msg.sessionId,
5006
+ sourcePeerId: peerId,
5007
+ local: false,
5008
+ displayName: msg.displayName,
5009
+ avatar: msg.avatar,
5010
+ profileUpdatedAt: msg.profileUpdatedAt,
5011
+ lastSeen: Number(msg.lastSeen) || Date.now(),
5012
+ }
5013
+
5014
+ if (status === 'online') {
5015
+ this.#upsertChannelPresenceSession(localChannel, options)
5016
+ } else if (status === 'heartbeat') {
5017
+ this.#touchChannelPresenceSession(localChannel, options)
5018
+ } else if (status === 'profile') {
5019
+ this.#updateChannelPresenceProfile(localChannel, options)
5020
+ } else if (status === 'offline') {
5021
+ this.#removeChannelPresenceSessions(localChannel.channelKey, options)
5022
+ }
5023
+
5024
+ return peerId
5025
+ }
5026
+
4557
5027
  async #handleChannelConnection(conn) {
4558
5028
  const stream = conn
4559
5029
  let connectedPeerId = null
@@ -4572,9 +5042,11 @@ export class MostBoxEngine extends EventEmitter {
4572
5042
  newlineIndex = readBuffer.indexOf('\n')
4573
5043
  if (!line) continue
4574
5044
  try {
4575
- const peerId = await this.#processChannelHelloMessage(
4576
- JSON.parse(line)
4577
- )
5045
+ const message = JSON.parse(line)
5046
+ const peerId =
5047
+ message.type === 'channel-presence'
5048
+ ? this.#processChannelPresenceMessage(message)
5049
+ : await this.#processChannelHelloMessage(message)
4578
5050
  if (peerId) connectedPeerId = peerId
4579
5051
  } catch (err) {
4580
5052
  console.warn(`[MostBox] Failed to process channel data:`, err.message)
@@ -4603,6 +5075,7 @@ export class MostBoxEngine extends EventEmitter {
4603
5075
  })
4604
5076
  }
4605
5077
  }
5078
+ this.#removeChannelPresenceSessionsBySource(`peer:${connectedPeerId}`)
4606
5079
  }
4607
5080
  }
4608
5081
 
@@ -1 +0,0 @@
1
- ._page_1yyew_1{background:radial-gradient(circle at 35% 25%, #34c75924, transparent 32%), radial-gradient(circle at 78% 18%, #ff95001f, transparent 28%), var(--bg-primary);flex:1;min-height:0;padding:20px;overflow:auto}._headerActions_1yyew_12{align-items:center;gap:8px;display:flex}._entry_1yyew_18{grid-template-columns:minmax(0,1fr) 360px;align-items:center;gap:48px;max-width:1040px;min-height:calc(100vh - 116px);margin:0 auto;display:grid}._entryBrand_1yyew_28{align-items:center;gap:22px;display:flex}._cardMark_1yyew_34{width:112px;height:152px;box-shadow:var(--shadow-lg);color:#fff;background:linear-gradient(135deg,#1f8f5f,#284f7a);border:8px solid #fff;border-radius:8px;flex:0 0 112px;place-items:center;display:grid}._entryBrand_1yyew_28 h1{color:var(--text-primary);margin:0;font-size:64px;line-height:.95}._entryBrand_1yyew_28 p{max-width:520px;color:var(--text-secondary);margin:18px 0 0;font-size:18px;line-height:1.7}._entryPanel_1yyew_62{border:1px solid var(--glass-border);background:var(--glass-bg);box-shadow:var(--glass-shadow);border-radius:8px;gap:16px;padding:22px;display:grid}._accountLine_1yyew_72{color:var(--text-secondary);justify-content:space-between;align-items:center;gap:10px;font-size:14px;display:flex}._accountLine_1yyew_72 strong{max-width:190px;color:var(--text-primary);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._joinForm_1yyew_89,._joinForm_1yyew_89 label{gap:8px;display:grid}._joinForm_1yyew_89 label{color:var(--text-secondary);font-size:14px}._joinForm_1yyew_89 input{border:1px solid var(--glass-border);background:var(--bg-primary);width:100%;min-height:40px;color:var(--text-primary);border-radius:8px;padding:0 12px}._status_1yyew_110{color:var(--text-secondary);margin:0;font-size:14px}._board_1yyew_116{grid-template-columns:minmax(0,1fr) minmax(320px,380px);gap:18px;min-height:100%;display:grid}._table_1yyew_123{background:linear-gradient(145deg, #0a533ee6, #163550eb), var(--glass-bg);min-height:560px;box-shadow:var(--shadow-lg);border:1px solid #ffffff2e;border-radius:8px;position:relative;overflow:hidden}._table_1yyew_123:before{content:"";pointer-events:none;border:2px solid #ffffff2e;border-radius:8px;position:absolute;inset:24px}._tableCenter_1yyew_144{color:#fff;background:#00000038;border:1px solid #ffffff38;border-radius:50%;flex-direction:column;justify-content:center;align-items:center;gap:5px;width:150px;height:150px;display:flex;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);box-shadow:inset 0 0 24px #00000029}._tableCenter_1yyew_144 span,._tableCenter_1yyew_144 small{opacity:.78;font-size:12px}._tableCenter_1yyew_144 strong{font-size:32px}._finishBanner_1yyew_173{z-index:2;color:#fff;white-space:nowrap;background:linear-gradient(135deg,#30d158e6,#34c759e6);border-radius:20px;align-items:center;gap:8px;padding:10px 20px;font-size:15px;font-weight:700;display:flex;position:absolute;top:16px;left:50%;transform:translate(-50%);box-shadow:0 4px 16px #30d15866}._finishBannerIcon_1yyew_192{font-size:20px}._seats_1yyew_196{z-index:1;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;padding:34px;display:grid;position:relative}._seat_1yyew_196{color:#fff;background:#ffffff1a;border:1px solid #ffffff29;border-radius:8px;grid-template-columns:44px minmax(0,1fr);align-items:center;gap:10px;min-height:88px;padding:12px;display:grid}._seatTurn_1yyew_218{border-color:#ffd666eb;box-shadow:0 0 0 2px #ffd6662e}._seatWinner_1yyew_223{border-color:#30d158eb;box-shadow:0 0 0 2px #30d15833}._seatFolded_1yyew_228{opacity:.5}._seatAvatar_1yyew_232{color:#fff;background:#17211b;border-radius:8px;place-items:center;width:44px;height:44px;font-size:15px;font-weight:900;display:grid;overflow:hidden}._seatAvatar_1yyew_232 img{object-fit:cover;width:100%;height:100%}._seatMain_1yyew_251,._seatMeta_1yyew_252{flex-direction:column;gap:4px;min-width:0;display:flex}._seatMain_1yyew_251 strong,._seatMain_1yyew_251 span,._seatMeta_1yyew_252 span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._seatMain_1yyew_251 strong{font-size:14px}._seatMain_1yyew_251 span,._seatMeta_1yyew_252{opacity:.76;font-size:12px}._seatMeta_1yyew_252{flex-direction:row;grid-column:1/-1;justify-content:space-between}._panel_1yyew_283{flex-direction:column;gap:14px;min-width:0;display:flex}._panelSection_1yyew_290{border:1px solid var(--glass-border);background:var(--glass-bg);-webkit-backdrop-filter:var(--blur-md);box-shadow:var(--glass-shadow);border-radius:8px;padding:16px}._panelTitle_1yyew_300{color:var(--text-primary);align-items:center;gap:8px;margin-bottom:12px;font-size:14px;font-weight:600;display:flex}._hand_1yyew_310{grid-template-columns:repeat(3,62px);justify-content:center;gap:10px;display:grid}._card_1yyew_34{color:#17211b;text-align:left;background:#fff;border:1px solid #dbe1dc;border-radius:8px;grid-template-rows:auto 1fr;width:62px;height:92px;padding:7px;display:grid;box-shadow:0 8px 18px #0e16111f}._card_1yyew_34 span{font-size:15px;font-weight:900}._card_1yyew_34 i{place-self:end;font-size:22px;font-style:normal;font-weight:900}._red_1yyew_344{color:#c93d32}._black_1yyew_348{color:#17211b}._cardBack_1yyew_352{color:#fff;background:repeating-linear-gradient(45deg,#ffffff1f 0 6px,#0000 6px 12px),linear-gradient(135deg,#243f7a,#752f4d);border:1px solid #dbe1dc;border-radius:8px;justify-content:center;align-items:center;width:62px;height:92px;display:flex;box-shadow:0 8px 18px #0e16111f}._handLabel_1yyew_373{text-align:center;color:var(--text-secondary);margin-top:10px;font-size:13px}._actionGrid_1yyew_380,._raiseRow_1yyew_381{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:10px;display:grid}._raiseRow_1yyew_381{grid-template-columns:minmax(0,1fr) 96px}._statusGrid_1yyew_392{grid-template-columns:76px minmax(0,1fr);align-items:start;gap:8px 12px;font-size:13px;display:grid}._statusGrid_1yyew_392 span{color:var(--text-secondary)}._statusGrid_1yyew_392 strong{color:var(--text-primary);overflow-wrap:anywhere;font-weight:600}._muted_1yyew_410{color:var(--text-secondary);margin:0;font-size:13px;line-height:1.6}._compareOverlay_1yyew_417{z-index:100;-webkit-backdrop-filter:blur(4px);background:#0009;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._compareBox_1yyew_429{background:var(--glass-bg);border:1px solid var(--glass-border);box-shadow:var(--shadow-lg);border-radius:16px;flex-direction:column;align-items:center;gap:20px;width:90%;max-width:560px;padding:28px 36px;display:flex}._compareTitle_1yyew_443{color:var(--text-primary);font-size:18px;font-weight:700}._comparePlayers_1yyew_449{align-items:center;gap:20px;width:100%;display:flex}._compareSide_1yyew_456{background:#ffffff0d;border:2px solid #0000;border-radius:12px;flex-direction:column;flex:1;align-items:center;gap:12px;padding:16px;display:flex}._compareWinner_1yyew_468{background:#30d1581a;border-color:#30d158cc}._compareName_1yyew_473{color:var(--text-primary);text-align:center;text-overflow:ellipsis;white-space:nowrap;max-width:100%;font-size:14px;font-weight:600;overflow:hidden}._compareCards_1yyew_484{justify-content:center;gap:8px;display:flex}._compareCards_1yyew_484 ._card_1yyew_34{width:52px;height:76px;padding:5px}._compareCards_1yyew_484 ._card_1yyew_34 span{font-size:13px}._compareCards_1yyew_484 ._card_1yyew_34 i{font-size:18px}._compareCards_1yyew_484 ._cardBack_1yyew_352{width:52px;height:76px}._compareLabel_1yyew_509{color:var(--text-secondary);font-size:12px;font-weight:500}._compareVs_1yyew_515{color:var(--text-secondary);opacity:.6;font-size:20px;font-weight:900}._compareResult_1yyew_522{color:#30d158;font-size:16px;font-weight:700}@media (width<=980px){._entry_1yyew_18{grid-template-columns:1fr;align-content:center}._board_1yyew_116{grid-template-columns:1fr}._table_1yyew_123{min-height:420px}}@media (width<=640px){._page_1yyew_1{padding:12px}._entryBrand_1yyew_28{flex-direction:column;align-items:flex-start}._entryBrand_1yyew_28 h1{font-size:42px}._cardMark_1yyew_34{border-width:6px;flex-basis:100px;width:74px;height:100px}._seats_1yyew_196{grid-template-columns:1fr;padding:22px}._tableCenter_1yyew_144{margin:24px auto 0;position:static;transform:none}._table_1yyew_123:before{inset:14px}._hand_1yyew_310{grid-template-columns:repeat(3,minmax(58px,72px))}._actionGrid_1yyew_380,._raiseRow_1yyew_381{grid-template-columns:1fr}}
@@ -1 +0,0 @@
1
- ._page_p7qf6_1{background:var(--bg-primary);min-height:calc(100vh - 64px);padding:18px}._headerActions_p7qf6_7{align-items:center;gap:8px;display:flex}._entry_p7qf6_13{grid-template-columns:minmax(0,1fr) 360px;align-items:center;gap:48px;max-width:1040px;min-height:calc(100vh - 116px);margin:0 auto;display:grid}._entryBrand_p7qf6_23{align-items:center;gap:22px;display:flex}._cardMark_p7qf6_29{width:112px;height:152px;box-shadow:var(--shadow-lg);color:#fff;background:#c93d32;border:8px solid #fff;border-radius:8px;flex:0 0 112px;place-items:center;font-size:70px;font-weight:900;display:grid}._entryBrand_p7qf6_23 h1{color:var(--text-primary);margin:0;font-size:64px;line-height:.95}._entryBrand_p7qf6_23 p{max-width:520px;color:var(--text-secondary);margin:18px 0 0;font-size:18px;line-height:1.7}._entryPanel_p7qf6_59,._panel_p7qf6_60,._handPanel_p7qf6_61{border:1px solid var(--glass-border);background:var(--glass-bg);box-shadow:var(--glass-shadow);border-radius:8px}._entryPanel_p7qf6_59{gap:16px;padding:22px;display:grid}._accountLine_p7qf6_74,._scoreMeta_p7qf6_75,._roomBar_p7qf6_76,._badges_p7qf6_77,._actions_p7qf6_78{align-items:center;gap:10px;display:flex}._accountLine_p7qf6_74{color:var(--text-secondary);justify-content:space-between;font-size:14px}._accountLine_p7qf6_74 strong{max-width:190px;color:var(--text-primary);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._joinForm_p7qf6_98,._joinForm_p7qf6_98 label{gap:8px;display:grid}._joinForm_p7qf6_98 label{color:var(--text-secondary);font-size:14px}._joinForm_p7qf6_98 input{border:1px solid var(--glass-border);background:var(--bg-primary);width:100%;min-height:40px;color:var(--text-primary);border-radius:8px;padding:0 12px}._status_p7qf6_119{color:var(--text-secondary);margin:0;font-size:14px}._gameGrid_p7qf6_125{grid-template:"table side"minmax(360px,1fr)"hand side"/minmax(0,1fr) 320px;gap:14px;min-height:calc(100vh - 104px);display:grid}._tablePanel_p7qf6_136{color:#fff;background:radial-gradient(circle,#ffffff2e,#0000 46%),#247658;border-radius:8px;grid-area:table;grid-template-rows:auto auto 1fr auto;gap:14px;min-height:360px;padding:16px;display:grid}._roomBar_p7qf6_76{justify-content:space-between}._roomBar_p7qf6_76 div:first-child{gap:2px;display:grid}._roomBar_p7qf6_76 span,._badges_p7qf6_77 span{color:#ffffffc7;font-size:13px}._badges_p7qf6_77{flex-wrap:wrap;justify-content:flex-end}._seats_p7qf6_170{grid-template-columns:repeat(auto-fit,minmax(168px,1fr));gap:10px;display:grid}._player_p7qf6_176{color:#17211b;background:#ffffffeb;border:1px solid #17211b1f;border-radius:8px;align-items:center;gap:10px;min-width:0;padding:8px;display:flex}._tablePanel_p7qf6_136 ._player_p7qf6_176{color:#fff;background:#ffffff2e}._active_p7qf6_193{outline:3px solid #f3cf5a}._winner_p7qf6_197{outline:3px solid #7be39e}._avatar_p7qf6_201{color:#fff;background:#17211b;border-radius:50%;flex:0 0 40px;place-items:center;width:40px;height:40px;font-size:13px;font-weight:900;display:grid;overflow:hidden}._avatar_p7qf6_201 img{object-fit:cover;width:100%;height:100%}._player_p7qf6_176 strong,._player_p7qf6_176 span{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}._player_p7qf6_176 span{opacity:.76;max-width:190px;font-size:12px}._centerTable_p7qf6_235{grid-template-columns:120px minmax(0,1fr) 120px;align-items:center;gap:14px;display:grid}._deckBox_p7qf6_242,._playedBox_p7qf6_243{text-align:center;background:#0b221942;border-radius:8px}._deckBox_p7qf6_242{gap:6px;padding:18px 8px;display:grid}._deckBox_p7qf6_242 span{opacity:.75;font-size:13px}._deckBox_p7qf6_242 strong{font-size:30px}._playedBox_p7qf6_243{place-items:center;min-height:150px;padding:16px;display:grid}._playedCards_p7qf6_271{flex-wrap:wrap;justify-content:center;gap:8px;margin-top:10px;display:flex}._notice_p7qf6_279{text-align:center;background:#0000002e;border-radius:8px;padding:10px 12px;font-weight:800}._resultDetail_p7qf6_287{gap:4px;font-weight:600;display:grid}._resultDetail_p7qf6_287 p{margin:0;font-size:14px}._sidePanel_p7qf6_298{grid-area:side;align-content:start;gap:12px;display:grid}._panel_p7qf6_60{gap:12px;padding:14px;display:grid}._panel_p7qf6_60 h3{color:var(--text-primary);margin:0;font-size:15px}._scoreMeta_p7qf6_75{color:var(--text-secondary);justify-content:space-between;font-size:13px}._scoreRow_p7qf6_323{border-top:1px solid var(--glass-border);color:var(--text-primary);justify-content:space-between;padding-top:8px;display:flex}._logPanel_p7qf6_331{max-height:280px;overflow:auto}._logPanel_p7qf6_331 p{color:var(--text-secondary);margin:0;font-size:13px;line-height:1.5}._handPanel_p7qf6_61{grid-area:hand;gap:12px;padding:12px;display:grid}._hand_p7qf6_61{gap:8px;min-height:118px;padding:4px 2px 10px;display:flex;overflow-x:auto}._cardButton_p7qf6_358{background:0 0;border:0;border-radius:8px;flex:none;min-height:auto;padding:0;transition:transform .16s}._picked_p7qf6_368{transform:translateY(-14px)}._card_p7qf6_29{color:#17211b;text-align:left;background:#fff;border:1px solid #dbe1dc;border-radius:8px;grid-template-rows:auto 1fr;width:62px;height:92px;padding:7px;display:grid;box-shadow:0 8px 18px #0e16111f}._card_p7qf6_29 span{font-size:15px;font-weight:900}._card_p7qf6_29 i{place-self:end;font-size:22px;font-style:normal;font-weight:900}._red_p7qf6_399{color:#c93d32}._black_p7qf6_403{color:#17211b}._small_p7qf6_407{width:46px;height:66px;padding:5px}._small_p7qf6_407 span{font-size:12px}._small_p7qf6_407 i{font-size:15px}._actions_p7qf6_78{flex-wrap:wrap}._preview_p7qf6_425{border:1px solid var(--glass-border);background:var(--bg-primary);min-height:46px;color:var(--text-secondary);border-radius:8px;flex:260px;padding:7px 10px;display:grid}._preview_p7qf6_425 strong{color:var(--text-primary);font-size:14px}._preview_p7qf6_425 span{text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._valid_p7qf6_448{border-color:#176b4d70}@media (width<=980px){._entry_p7qf6_13{grid-template-columns:1fr;align-content:center}._gameGrid_p7qf6_125{grid-template-columns:1fr;grid-template-areas:"table""hand""side"}}@media (width<=640px){._page_p7qf6_1{padding:10px}._entryBrand_p7qf6_23{flex-direction:column;align-items:flex-start}._entryBrand_p7qf6_23 h1{font-size:42px}._cardMark_p7qf6_29{border-width:6px;flex-basis:100px;width:74px;height:100px;font-size:46px}._centerTable_p7qf6_235{grid-template-columns:1fr}._actions_p7qf6_78>button{flex:110px}}
@@ -1 +0,0 @@
1
- import{n as e,o as t,t as n}from"./jsx-runtime-Bt-cYkS5.js";import{C as r,T as i,d as a,l as o,s,u as c}from"./ui-BpDGD3IR.js";import{a as l}from"./i18n-NjvOSuV6.js";import{n as u,r as d}from"./LanguageToggle-CSQtRZ3A.js";import{t as f}from"./copy-DEzFUzeX.js";import{n as p}from"./SidebarHomeLink-DXv0c9Ob.js";import{t as m}from"./play-B-BjsTZz.js";import{a as h,i as g,n as _,r as ee,t as te}from"./useGameRoom-cs9YpYt7.js";import{E as v,N as ne,S as y,b as re,c as b,i as ie,w as x,x as S}from"./index-BI8oNSVC.js";var ae=r(`refresh-ccw`,[[`path`,{d:`M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`,key:`14sxne`}],[`path`,{d:`M3 3v5h5`,key:`1xhq8a`}],[`path`,{d:`M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16`,key:`1hlbsb`}],[`path`,{d:`M16 16h5v5`,key:`ccwih5`}]]),C=t(e(),1),oe=[10,20,50,100],w=[`2`,`3`,`4`,`5`,`6`,`7`,`8`,`9`,`10`,`J`,`Q`,`K`,`A`],T=[`D`,`C`,`H`,`S`],E=new Map(w.map((e,t)=>[e,t+2])),D=new Map(T.map((e,t)=>[e,t+1])),O={high:1,pair:2,straight:3,flush:4,straightFlush:5,triple:6},se={[O.high]:`散牌`,[O.pair]:`对子`,[O.straight]:`顺子`,[O.flush]:`金花`,[O.straightFlush]:`顺金`,[O.triple]:`豹子`};function k(e){return x(e)}function ce(e=`zhajinhua`){return re(e)}function le(){return ce(`round`)}function ue(){let e=[];for(let t of T)for(let n of w)e.push(`${n}${t}`);return e}function A(e=ue(),t=Math.random){let n=[...e];for(let e=n.length-1;e>0;e--){let r=Math.floor(t()*(e+1));[n[e],n[r]]=[n[r],n[e]]}return n}function j(e){let t=String(e||``).trim().toUpperCase(),n=t.slice(-1),r=t.slice(0,-1);if(!E.has(r)||!D.has(n))throw Error(`Invalid card: ${e}`);return{id:`${r}${n}`,rank:r,suit:n,rankValue:E.get(r),suitValue:D.get(n)}}function M(e,t){let n=Math.max(e.length,t.length);for(let r=0;r<n;r++){let n=e[r]||0,i=t[r]||0;if(n!==i)return n-i}return 0}function N(e){let t=[...new Set(e)].sort((e,t)=>e-t);return t.length===3?t[0]===2&&t[1]===3&&t[2]===14?3:t[0]+1===t[1]&&t[1]+1===t[2]?t[2]:0:0}function P(e){if(!Array.isArray(e)||e.length!==3)throw Error(`炸金花手牌必须是 3 张牌`);let t=e.map(j),n=t.map(e=>e.rankValue),r=new Map;for(let e of n)r.set(e,(r.get(e)||0)+1);let i=new Set(t.map(e=>e.suit)).size===1,a=N(n),o=[...n].sort((e,t)=>t-e),s=Math.max(...t.map(e=>e.suitValue)),c=O.high,l=o;return r.size===1?(c=O.triple,l=[o[0]]):a&&i?(c=O.straightFlush,l=[a]):i?(c=O.flush,l=o):a?(c=O.straight,l=[a]):r.size===2&&(c=O.pair,l=[[...r.entries()].find(([,e])=>e===2)[0],[...r.entries()].find(([,e])=>e===1)[0]]),{category:c,label:se[c],tiebreakers:l,tieSuit:s,cards:t.map(e=>e.id)}}function F(e,t){let n=P(e),r=P(t);if(n.category!==r.category)return n.category-r.category;let i=M(n.tiebreakers,r.tiebreakers);return i===0?n.tieSuit-r.tieSuit:i}function de(e){return P(e).label}function I({roundId:e,action:t,amount:n,target:r}){return{roundId:e,eventId:ce(`action`),action:t,amount:n,target:r}}function fe(e=[]){let t=e.filter(e=>Number(e.chips)>=10);return t.length>=2&&t.length<=5}function pe({roomCode:e,players:t,hostAddress:n,previousSeq:r=0,previousWinner:i=``,roundId:a=le(),random:o=Math.random}){let s=t.filter(e=>Number(e.chips)>=10).slice(0,5);if(s.length<2)throw Error(`至少需要 2 名有足够筹码的玩家`);let c=A(ue(),o),l={},u=s.map((e,t)=>{let n=c.slice(t*3,t*3+3);return l[e.address]=n,{address:e.address,name:e.name,avatar:y(e.avatar),publicKey:e.publicKey,chips:Number(e.chips)-10,status:`active`,looked:!1,bet:10}}),d,f=S(i),p=f?u.findIndex(e=>S(e.address)===f):-1;return d=p>=0?u[p].address:u[Math.floor(o()*u.length)].address,{roomCode:k(e),roundId:a,status:`playing`,host:S(n),seq:r+1,startedAt:Date.now(),ante:10,pot:10*u.length,currentBet:10,turnAddress:d,players:u,lastAction:`本局开始`,winner:null,showdown:null,appliedEventIds:[],hands:l,actionCounts:{},compareReveal:{}}}function me(e){if(!e)return null;let t={...e};return delete t.hands,K(t)}function he(e,t){let n=L(e);return!n||!t||typeof t!=`object`?null:{...n,hands:K(t)}}function L(e){return!e||typeof e!=`object`||!e.roundId||!Array.isArray(e.players)?null:{roomCode:k(e.roomCode),roundId:String(e.roundId),status:e.status===`finished`?`finished`:`playing`,host:S(e.host),seq:Number(e.seq)||1,startedAt:Number(e.startedAt)||Date.now(),ante:Number(e.ante)||10,pot:Math.max(0,Number(e.pot)||0),currentBet:Math.max(0,Number(e.currentBet)||10),turnAddress:S(e.turnAddress),players:e.players.map(ve).filter(Boolean),lastAction:String(e.lastAction||``),winner:e.winner?S(e.winner):null,showdown:e.showdown&&typeof e.showdown==`object`?K(e.showdown):null,appliedEventIds:Array.isArray(e.appliedEventIds)?e.appliedEventIds.map(String):[],actionCounts:e.actionCounts&&typeof e.actionCounts==`object`?K(e.actionCounts):{},compareReveal:e.compareReveal&&typeof e.compareReveal==`object`?K(e.compareReveal):{},lastCompare:e.lastCompare&&typeof e.lastCompare==`object`?K(e.lastCompare):null,finishedAt:e.finishedAt?Number(e.finishedAt):null}}function R(e,t,n){let r=L(e);if(!r||r.status!==`playing`)return{ok:!1,error:`当前没有进行中的牌局`};let i=S(n),a=r.players.find(e=>e.address===i);if(!a||a.status!==`active`)return{ok:!1,error:`玩家不在本轮牌局中`};if(r.turnAddress!==i)return{ok:!1,error:`还没有轮到你操作`};let o=t?.action;if(![`look`,`call`,`raise`,`compare`,`fold`].includes(o))return{ok:!1,error:`未知操作`};if(o===`look`)return a.looked?{ok:!1,error:`已经看过牌`}:{ok:!0};if(o===`call`)return a.chips>=10?{ok:!0}:{ok:!1,error:`筹码不足,不能跟注`};if(o===`raise`){let e=Number(t.amount);if(!oe.includes(e))return{ok:!1,error:`加注档位无效`};let n=r.currentBet+e-a.bet;return a.chips>=n?{ok:!0}:{ok:!1,error:`筹码不足,不能加注`}}if(o===`compare`){let e=S(t.target),n=r.players.find(t=>t.address===e);if(!e||e===i||!n||n.status!==`active`)return{ok:!1,error:`请选择有效的比牌对象`};if(!r.players.filter(e=>e.status===`active`).every(e=>(r.actionCounts?.[e.address]||0)>=1))return{ok:!1,error:`第一轮不能比牌,请先跟注或加注`};let o=Math.max(0,r.currentBet-a.bet);return a.chips>=o?{ok:!0}:{ok:!1,error:`筹码不足,不能比牌`}}return{ok:!0}}function z(e,t,n){let r=R(e,t,n);if(!r.ok)return{ok:!1,error:r.error,state:e};let i=K(e),a=S(n),o=i.players.find(e=>e.address===a),s=String(t.eventId);if(i.appliedEventIds?.includes(s))return{ok:!0,state:i,duplicate:!0};if(Array.isArray(i.appliedEventIds)||(i.appliedEventIds=[]),i.appliedEventIds.push(s),t.action===`look`)return o.looked=!0,i.lastAction=`${o.name} 看牌`,G(i);if(t.action===`fold`)return o.status=`folded`,i.lastAction=`${o.name} 弃牌`,H(i,a),U(i,a);if(t.action===`call`)return o.chips-=10,o.bet+=10,i.pot+=10,i.lastAction=`${o.name} 跟注`,H(i,a),U(i,a);if(t.action===`raise`){let e=Number(t.amount);return i.currentBet+=e,V(i,o),i.lastAction=`${o.name} 加注 ${e}`,H(i,a),U(i,a)}if(t.action===`compare`){V(i,o);let e=S(t.target),n=i.players.find(t=>t.address===e),r=o.looked,s=n.looked,c=F(i.hands?.[a]||[],i.hands?.[e]||[]),l=c>=0?o:n,u=c>=0?n:o;u.status=`folded`,u.looked=!0,i.compareReveal||={};let d=r&&s,f=!r&&!s;if(d)i.compareReveal[a]=i.hands?.[e]||[],i.compareReveal[e]=i.hands?.[a]||[];else if(f)i.compareReveal[u.address]=i.hands?.[l.address]||[];else{let e=r?o:n,t=r?n:o;i.compareReveal[e.address]=i.hands?.[t.address]||[],i.compareReveal[u.address]=i.hands?.[l.address]||[]}return i.lastCompare={initiator:a,target:e,winner:l.address,loser:u.address,initiatorLooked:r,targetLooked:s,timestamp:Date.now()},i.lastAction=`${o.name} 与 ${n.name} 比牌,${u.name} 出局`,H(i,a),U(i,a)}return{ok:!1,error:`未知操作`,state:e}}function B(e){return(e?.players||[]).filter(e=>e.status===`active`)}function ge(e,t){let n=(e?.players||[]).find(e=>e.address===S(t));if(!e||e.status!==`playing`||!n||n.status!==`active`||e.turnAddress!==n.address)return[];let r=[];return n.looked||r.push(`look`),R(e,{action:`call`},n.address).ok&&r.push(`call`),oe.some(t=>R(e,{action:`raise`,amount:t},n.address).ok)&&r.push(`raise`),B(e).length>1&&e.players.filter(e=>e.status===`active`).every(t=>(e.actionCounts?.[t.address]||0)>=1)&&r.push(`compare`),r.push(`fold`),r}function V(e,t){let n=Math.max(0,e.currentBet-t.bet);t.chips-=n,t.bet+=n,e.pot+=n}function H(e,t){e.actionCounts||={},e.actionCounts[t]=(e.actionCounts[t]||0)+1}function U(e,t){let n=B(e);return n.length===1?_e(e,n[0].address):(e.turnAddress=W(e,t),G(e))}function _e(e,t){let n=e.players.find(e=>e.address===t),r=e.pot;return n&&(n.chips+=e.pot),e.status=`finished`,e.winner=t,e.winAmount=r,e.turnAddress=``,e.showdown=e.hands?K(e.hands):null,e.lastAction=n?`${n.name} 赢得 ${r} 筹码`:`本局结束`,e.pot=0,e.finishedAt=Date.now(),G(e)}function W(e,t){let n=e.players,r=Math.max(0,n.findIndex(e=>e.address===S(t)));for(let e=1;e<=n.length;e++){let t=n[(r+e)%n.length];if(t.status===`active`)return t.address}return``}function G(e){return e.seq=Number(e.seq||0)+1,{ok:!0,state:e}}function ve(e){if(!e||typeof e!=`object`)return null;let t=S(e.address);return t?{address:t,name:String(e.name||v(t)).slice(0,50),avatar:y(e.avatar),publicKey:String(e.publicKey||``),chips:Math.max(0,Number(e.chips)||0),status:e.status===`folded`?`folded`:`active`,looked:e.looked===!0,bet:Math.max(0,Number(e.bet)||0)}:null}function K(e){return JSON.parse(JSON.stringify(e))}var q={page:`_page_1yyew_1`,headerActions:`_headerActions_1yyew_12`,entry:`_entry_1yyew_18`,entryBrand:`_entryBrand_1yyew_28`,cardMark:`_cardMark_1yyew_34`,entryPanel:`_entryPanel_1yyew_62`,accountLine:`_accountLine_1yyew_72`,joinForm:`_joinForm_1yyew_89`,status:`_status_1yyew_110`,board:`_board_1yyew_116`,table:`_table_1yyew_123`,tableCenter:`_tableCenter_1yyew_144`,finishBanner:`_finishBanner_1yyew_173`,finishBannerIcon:`_finishBannerIcon_1yyew_192`,seats:`_seats_1yyew_196`,seat:`_seat_1yyew_196`,seatTurn:`_seatTurn_1yyew_218`,seatWinner:`_seatWinner_1yyew_223`,seatFolded:`_seatFolded_1yyew_228`,seatAvatar:`_seatAvatar_1yyew_232`,seatMain:`_seatMain_1yyew_251`,seatMeta:`_seatMeta_1yyew_252`,panel:`_panel_1yyew_283`,panelSection:`_panelSection_1yyew_290`,panelTitle:`_panelTitle_1yyew_300`,hand:`_hand_1yyew_310`,card:`_card_1yyew_34`,red:`_red_1yyew_344`,black:`_black_1yyew_348`,cardBack:`_cardBack_1yyew_352`,handLabel:`_handLabel_1yyew_373`,actionGrid:`_actionGrid_1yyew_380`,raiseRow:`_raiseRow_1yyew_381`,statusGrid:`_statusGrid_1yyew_392`,muted:`_muted_1yyew_410`,compareOverlay:`_compareOverlay_1yyew_417`,compareBox:`_compareBox_1yyew_429`,compareTitle:`_compareTitle_1yyew_443`,comparePlayers:`_comparePlayers_1yyew_449`,compareSide:`_compareSide_1yyew_456`,compareWinner:`_compareWinner_1yyew_468`,compareName:`_compareName_1yyew_473`,compareCards:`_compareCards_1yyew_484`,compareLabel:`_compareLabel_1yyew_509`,compareVs:`_compareVs_1yyew_515`,compareResult:`_compareResult_1yyew_522`},J=n(),ye=`zhajinhua`;function Y(...e){return e.filter(Boolean).join(` `)}function X(e,t){return String(e||``).toLowerCase()===String(t||``).toLowerCase()}function be(e=``){return e?`${e.slice(0,6)}...${e.slice(-4)}`:``}function xe(e){return e.displayName||e.username}function Se(e){let t=String(e||``),n=t.slice(-1);return{rank:t.slice(0,-1),suit:{S:`♠`,H:`♥`,C:`♣`,D:`♦`}[n]||n,color:n===`H`||n===`D`?`red`:`black`}}function Z(){let{t:e}=l(),t=b(e=>e.isDarkMode),n=b(e=>e.setIsDarkMode),r=b(e=>e.addToast),i=s(e=>e.initializeUser),[v,y]=(0,C.useState)(``),[re,x]=(0,C.useState)(!1),S=(0,C.useRef)(``),w=(0,C.useRef)(!1),[T,E]=(0,C.useState)(20),[D,O]=(0,C.useState)(``),[se,k]=(0,C.useState)({}),[ce,le]=(0,C.useState)(!0),[ue,A]=(0,C.useState)(!1),j=(0,C.useRef)(null),M=(0,C.useRef)(new Set),N=te({gameId:ye,onError:e=>r(e,`error`),getPlayerPayload:e=>{let t=o(e.danger);return{address:e.address,name:xe(e),avatar:e.avatar||``,publicKey:t.public_key,joinedAt:Date.now()}}}),P=(0,C.useMemo)(()=>N.userIdentity?.danger?o(N.userIdentity.danger):null,[N.userIdentity?.danger]);(0,C.useEffect)(()=>{i()},[i]),(0,C.useEffect)(()=>{let e=new URLSearchParams(window.location.search).get(`room`)?.toUpperCase();e&&(y(e),S.current=e)},[]),(0,C.useEffect)(()=>{let t=S.current;!t||w.current||!N.isBackendReady||!N.userIdentity||(w.current=!0,S.current=``,N.joinRoom(t).then(t=>{t&&r(e(`game.toast.joinedRoom`),`success`)}))},[N.isBackendReady,N.userIdentity,N.joinRoom,r,e]),(0,C.useEffect)(()=>{if(!N.roomCode)return;let e=`/game/zhajinhua?room=${N.roomCode}`;`${window.location.pathname}${window.location.search}`!==e&&window.history.replaceState(null,``,e)},[N.roomCode]);let F=(0,C.useMemo)(()=>_(N.messages,{gameId:ye,roomCode:N.roomCode}),[N.messages,N.roomCode]),L=(0,C.useMemo)(()=>ee(N.messages,{gameId:ye,roomCode:N.roomCode}),[N.messages,N.roomCode])?.payload?.round||null,R=L?.host||F.hostAddress,V=X(R,N.userIdentity?.address),H=N.userIdentity?.address?.toLowerCase()||``,U=L?.players.find(e=>X(e.address,H)),_e=U||F.players.find(e=>X(e.address,H)),W=se[L?.roundId||``]||null,G=L?.showdown&&H?L.showdown[H]:null,ve=L?.compareReveal&&H?L.compareReveal[H]:null,K=G||ve||(U?.looked?W:null),Z=ge(L,H),we=B(L),Te=V&&fe(F.players.map(e=>({...e,chips:L?.players.find(t=>X(t.address,e.address))?.chips??1e3}))),Ee=N.roomCode&&typeof window<`u`?`${window.location.origin}/game/zhajinhua?room=${N.roomCode}`:``;(0,C.useEffect)(()=>{if(!(!L||!N.userIdentity||!P||!R))for(let e of N.roomEvents){let t=e.event;if(!t||t.event!==`deal:private`)continue;let n=t.payload;if(n.roundId!==L.roundId||!X(n.recipient,N.userIdentity.address))continue;let r=c(n.encrypted||``,{senderPublicKey:n.senderPublicKey||``,recipientPrivateKey:P.private_key});if(r)try{let e=JSON.parse(r);Array.isArray(e)&&k(t=>({...t,[L.roundId]:e.map(String)}))}catch{}}},[L?.roundId,N.roomEvents,N.userIdentity,R,P?.private_key]),(0,C.useEffect)(()=>{if(L?.status===`finished`&&L.winner){le(!0);let e=setTimeout(()=>le(!1),5e3);return()=>clearTimeout(e)}},[L?.finishedAt,L?.status,L?.winner]),(0,C.useEffect)(()=>{if(L?.lastCompare?.timestamp){A(!0);let e=setTimeout(()=>A(!1),5e3);return()=>clearTimeout(e)}else A(!1)},[L?.lastCompare?.timestamp,L?.roundId]),(0,C.useEffect)(()=>{if(!V||!L||L.status!==`playing`)return;let e=j.current;if(!e)return;let t=N.roomEvents.filter(e=>{let t=e.event;if(!t||t.event!==`player:action`)return!1;let n=t.payload.actionEvent;return!(!n||n.roundId!==L.roundId||M.current.has(n.eventId)||L.appliedEventIds?.includes(n.eventId))});if(t.length===0)return;let n=!1;async function r(){let r=he(L,e);if(r)for(let e of t){if(n)return;let t=(e.event?.payload).actionEvent,i=z(r,t,e.message.author);if(!i.ok){let n=e.message.author;if(!X(n,N.userIdentity?.address)&&r.status===`playing`&&X(r.turnAddress,n)){let e=z(r,I({roundId:r.roundId,action:`call`,amount:void 0,target:void 0}),n);if(e.ok)i=e;else{let e=z(r,I({roundId:r.roundId,action:`fold`,amount:void 0,target:void 0}),n);e.ok&&(i=e)}}if(!i.ok){M.current.add(t.eventId);continue}}if(r=i.state,j.current=i.state.hands,await Ae(i.state)&&M.current.add(t.eventId),i.state.status===`finished`)break}}return r(),()=>{n=!0}},[L?.roundId,L?.seq,L?.status,N.roomEvents,V]);async function De(){await N.createRoom()&&r(e(`game.toast.roomCreated`),`success`)}async function Oe(t){t.preventDefault(),await N.joinRoom(v)&&r(e(`game.toast.joinedRoom`),`success`)}async function ke(){Ee&&(await navigator.clipboard.writeText(Ee),x(!0),setTimeout(()=>x(!1),1400))}async function Ae(e){return N.sendRoomEvent(`room:state`,{round:me(e),seq:e.seq})}async function je(){if(!N.userIdentity||!P||!Te){r(e(`game.zhajinhua.error.notEnoughPlayers`),`error`);return}try{let t=F.players.map(e=>({...e,chips:L?.players.find(t=>X(t.address,e.address))?.chips??1e3})),n=pe({roomCode:N.roomCode,players:t,hostAddress:N.userIdentity.address,previousSeq:L?.seq||0,previousWinner:L?.winner||``});j.current=n.hands,M.current.clear(),await Ae(n);for(let e of n.players){let t=a(JSON.stringify(n.hands[e.address]),{senderPrivateKey:P.private_key,recipientPublicKey:e.publicKey});await N.sendRoomEvent(`deal:private`,{roundId:n.roundId,recipient:e.address,senderPublicKey:P.public_key,encrypted:t})}k(e=>({...e,[n.roundId]:n.hands[N.userIdentity.address.toLowerCase()]})),r(e(`game.zhajinhua.toast.roundStarted`),`success`)}catch(t){r(t instanceof Error?t.message:e(`game.zhajinhua.error.startFailed`),`error`)}}async function $(e,t={}){if(!L||!N.userIdentity)return;let n=I({roundId:L.roundId,action:e,amount:t.amount,target:t.target});await N.sendRoomEvent(`player:action`,{actionEvent:n})}let Me=L?.status===`playing`?X(L.turnAddress,H)?e(`game.zhajinhua.status.yourTurn`):L.turnAddress?e(`game.zhajinhua.status.waitingPlayer`,{player:Q(L.turnAddress,F.players)}):e(`game.status.playing`):L?.status===`finished`?e(`game.status.finished`):e(`game.status.waitingStart`),Ne=we.filter(e=>!X(e.address,H));return(0,J.jsx)(p,{sidebar:({closeSidebar:e})=>(0,J.jsx)(g,{activeGame:`zhajinhua`,closeSidebar:e}),headerTitle:(0,J.jsx)(`h2`,{className:`header-title`,children:e(`game.zhajinhua.title`)}),headerRight:(0,J.jsxs)(`div`,{className:q.headerActions,children:[N.roomCode&&(0,J.jsxs)(`button`,{className:`btn btn-sm`,onClick:ke,children:[(0,J.jsx)(f,{size:14}),e(re?`common.copied`:`game.action.shareRoom`)]}),(0,J.jsx)(`button`,{className:`btn btn-icon`,onClick:()=>n(!t),title:e(`common.theme.toggle`),children:t?(0,J.jsx)(u,{size:16}):(0,J.jsx)(d,{size:16})})]}),children:(0,J.jsx)(`main`,{className:q.page,children:N.roomCode?(0,J.jsxs)(`div`,{className:q.board,children:[(0,J.jsxs)(`section`,{className:q.table,children:[L?.status===`finished`&&L.winner&&ce&&(0,J.jsxs)(`div`,{className:q.finishBanner,children:[(0,J.jsx)(`span`,{className:q.finishBannerIcon,children:`🏆`}),(0,J.jsx)(`span`,{children:e(`game.zhajinhua.result.winChips`,{player:Q(L.winner,F.players),amount:L.winAmount??0})})]}),L?.lastCompare&&ue&&(0,J.jsx)(Ce,{lastCompare:L.lastCompare,compareReveal:L.compareReveal||{},myAddress:H,myDealtHand:W,lobbyPlayers:F.players,onClose:()=>A(!1)}),(0,J.jsxs)(`div`,{className:q.tableCenter,children:[(0,J.jsx)(`span`,{children:e(`game.zhajinhua.pot`)}),(0,J.jsx)(`strong`,{children:L?.pot||0}),(0,J.jsx)(`small`,{children:e(`game.zhajinhua.currentBet`,{amount:L?.currentBet||0})})]}),(0,J.jsx)(`div`,{className:q.seats,children:F.players.map(t=>{let n=L?.players.find(e=>X(e.address,t.address)),r=L?.turnAddress===t.address,i=L?.winner===t.address,a=n?.status===`folded`;return(0,J.jsxs)(`div`,{className:Y(q.seat,r&&q.seatTurn,i&&q.seatWinner,a&&q.seatFolded),children:[(0,J.jsx)(`div`,{className:q.seatAvatar,children:(0,J.jsx)(`img`,{src:ie(t.address,t.avatar),alt:``})}),(0,J.jsxs)(`div`,{className:q.seatMain,children:[(0,J.jsx)(`strong`,{translate:`no`,children:t.name}),(0,J.jsx)(`span`,{translate:`no`,children:be(t.address)})]}),(0,J.jsxs)(`div`,{className:q.seatMeta,children:[(0,J.jsx)(`span`,{children:e(`game.zhajinhua.chips`,{amount:n?.chips??1e3})}),(0,J.jsx)(`span`,{children:n?.status===`folded`?e(`game.zhajinhua.player.folded`):n?.looked?e(`game.zhajinhua.player.looked`):e(L?`game.zhajinhua.player.inRound`:`game.status.waitingStart`)})]})]},t.address)})})]}),(0,J.jsxs)(`aside`,{className:q.panel,children:[(0,J.jsxs)(`div`,{className:q.panelSection,children:[(0,J.jsxs)(`div`,{className:q.panelTitle,children:[(0,J.jsx)(h,{size:16}),(0,J.jsx)(`span`,{children:e(`game.zhajinhua.myHand`)})]}),(0,J.jsx)(`div`,{className:q.hand,children:K?K.map(e=>{let t=Se(e),n=`${t.suit}${t.rank}`;return(0,J.jsxs)(`div`,{className:Y(q.card,q[t.color]),children:[(0,J.jsx)(`span`,{children:n}),(0,J.jsx)(`i`,{children:t.suit})]},e)}):[0,1,2].map(e=>(0,J.jsx)(`div`,{className:q.cardBack,children:(0,J.jsx)(h,{size:18})},e))}),(0,J.jsx)(`p`,{className:q.handLabel,children:K?de(K):e(W?`game.zhajinhua.hand.unlooked`:`game.zhajinhua.hand.waitingDeal`)})]}),(0,J.jsxs)(`div`,{className:q.panelSection,children:[(0,J.jsxs)(`div`,{className:q.panelTitle,children:[(0,J.jsx)(m,{size:16}),(0,J.jsx)(`span`,{children:e(`game.actions`)})]}),V?(0,J.jsxs)(`button`,{className:`btn btn-primary btn-full`,onClick:je,disabled:!Te||L?.status===`playing`,children:[(0,J.jsx)(ae,{size:16}),L?.status===`finished`?e(`game.action.nextRound`):e(`game.action.startRound`)]}):(0,J.jsx)(`p`,{className:q.muted,children:e(`game.waitingHost`)}),V&&L?.status===`finished`&&!Te&&(0,J.jsx)(`p`,{className:q.muted,children:e(`game.zhajinhua.error.tooManyLowChips`)}),(0,J.jsxs)(`div`,{className:q.actionGrid,children:[(0,J.jsxs)(`button`,{className:`btn btn-secondary`,disabled:!Z.includes(`look`),onClick:()=>$(`look`),children:[(0,J.jsx)(ne,{size:16}),e(`game.zhajinhua.action.look`)]}),(0,J.jsx)(`button`,{className:`btn btn-secondary`,disabled:!Z.includes(`call`),onClick:()=>$(`call`),children:e(`game.zhajinhua.action.call`)}),(0,J.jsx)(`button`,{className:`btn btn-secondary`,disabled:!Z.includes(`fold`),onClick:()=>$(`fold`),children:e(`game.zhajinhua.action.fold`)})]}),(0,J.jsxs)(`div`,{className:q.raiseRow,children:[(0,J.jsx)(`select`,{className:`input input-compact`,value:T,onChange:e=>E(Number(e.target.value)),children:oe.map(t=>(0,J.jsx)(`option`,{value:t,children:e(`game.zhajinhua.action.raiseOption`,{amount:t})},t))}),(0,J.jsx)(`button`,{className:`btn btn-secondary`,disabled:!Z.includes(`raise`),onClick:()=>$(`raise`,{amount:T}),children:e(`game.zhajinhua.action.raise`)})]}),(0,J.jsxs)(`div`,{className:q.raiseRow,children:[(0,J.jsxs)(`select`,{className:`input input-compact`,value:D,onChange:e=>O(e.target.value),children:[(0,J.jsx)(`option`,{value:``,children:e(`game.zhajinhua.action.comparePlaceholder`)}),Ne.map(e=>(0,J.jsx)(`option`,{value:e.address,children:e.name},e.address))]}),(0,J.jsx)(`button`,{className:`btn btn-secondary`,disabled:!Z.includes(`compare`)||!D,onClick:()=>$(`compare`,{target:D}),children:e(`game.zhajinhua.action.compare`)})]})]}),(0,J.jsxs)(`div`,{className:q.panelSection,children:[(0,J.jsx)(`div`,{className:q.panelTitle,children:e(`game.status.title`)}),(0,J.jsxs)(`div`,{className:q.statusGrid,children:[(0,J.jsx)(`span`,{children:e(`game.host`)}),(0,J.jsx)(`strong`,{children:R?be(R):`-`}),(0,J.jsx)(`span`,{children:e(`game.zhajinhua.myChips`)}),(0,J.jsx)(`strong`,{children:_e?.chips??1e3}),(0,J.jsx)(`span`,{children:e(`game.round`)}),(0,J.jsx)(`strong`,{children:Me}),(0,J.jsx)(`span`,{children:e(`game.hint`)}),(0,J.jsx)(`strong`,{children:L?.lastAction||e(`game.waitingPlayers`)})]})]})]})]}):(0,J.jsxs)(`section`,{className:q.entry,children:[(0,J.jsxs)(`div`,{className:q.entryBrand,children:[(0,J.jsx)(`div`,{className:q.cardMark,children:(0,J.jsx)(h,{size:48})}),(0,J.jsxs)(`div`,{children:[(0,J.jsx)(`h1`,{children:e(`game.zhajinhua.hero.title`)}),(0,J.jsx)(`p`,{children:e(`game.hero.desc`)})]})]}),(0,J.jsxs)(`div`,{className:q.entryPanel,children:[(0,J.jsxs)(`div`,{className:q.accountLine,children:[(0,J.jsx)(`span`,{children:e(`game.currentAccount`)}),(0,J.jsx)(`strong`,{translate:`no`,children:N.userIdentity?.displayName||e(`web3.notSignedIn`)})]}),(0,J.jsxs)(`button`,{className:`btn btn-primary`,disabled:N.joining,onClick:De,children:[(0,J.jsx)(m,{size:16}),e(`game.action.createRoom`)]}),(0,J.jsxs)(`form`,{onSubmit:Oe,className:q.joinForm,children:[(0,J.jsxs)(`label`,{children:[e(`game.roomCode`),(0,J.jsx)(`input`,{value:v,maxLength:8,onChange:e=>y(e.target.value.toUpperCase()),placeholder:e(`game.roomCode.placeholder`)})]}),(0,J.jsx)(`button`,{className:`btn`,disabled:N.joining||!v,children:e(`game.action.joinRoom`)})]}),(0,J.jsx)(`p`,{className:q.status,children:N.isBackendReady?e(`game.status.nodeConnected`):e(`game.status.nodeConnecting`)})]})]})})})}function Ce({lastCompare:e,compareReveal:t,myAddress:n,myDealtHand:r,lobbyPlayers:i,onClose:a}){let{t:o}=l(),s=Q(e.initiator,i),c=Q(e.target,i),u=Q(e.winner,i),d=X(n,e.initiator),f=X(n,e.target),p=d||f;function m(e,i){return t[e]||(p&&X(e,n)&&i&&r?r:null)}let g=m(e.initiator,e.initiatorLooked),_=m(e.target,e.targetLooked);return(0,J.jsx)(`div`,{className:q.compareOverlay,onClick:a,children:(0,J.jsxs)(`div`,{className:q.compareBox,onClick:e=>e.stopPropagation(),children:[(0,J.jsx)(`div`,{className:q.compareTitle,children:o(`game.zhajinhua.compareResult`)}),(0,J.jsxs)(`div`,{className:q.comparePlayers,children:[(0,J.jsxs)(`div`,{className:Y(q.compareSide,X(e.winner,e.initiator)&&q.compareWinner),children:[(0,J.jsx)(`div`,{className:q.compareName,children:s}),(0,J.jsx)(`div`,{className:q.compareCards,children:g?g.map(e=>{let t=Se(e);return(0,J.jsxs)(`div`,{className:Y(q.card,q[t.color]),children:[(0,J.jsx)(`span`,{children:t.rank}),(0,J.jsx)(`i`,{children:t.suit})]},e)}):[0,1,2].map(e=>(0,J.jsx)(`div`,{className:q.cardBack,children:(0,J.jsx)(h,{size:18})},e))}),g&&(0,J.jsx)(`div`,{className:q.compareLabel,children:de(g)})]}),(0,J.jsx)(`div`,{className:q.compareVs,children:`VS`}),(0,J.jsxs)(`div`,{className:Y(q.compareSide,X(e.winner,e.target)&&q.compareWinner),children:[(0,J.jsx)(`div`,{className:q.compareName,children:c}),(0,J.jsx)(`div`,{className:q.compareCards,children:_?_.map(e=>{let t=Se(e);return(0,J.jsxs)(`div`,{className:Y(q.card,q[t.color]),children:[(0,J.jsx)(`span`,{children:t.rank}),(0,J.jsx)(`i`,{children:t.suit})]},e)}):[0,1,2].map(e=>(0,J.jsx)(`div`,{className:q.cardBack,children:(0,J.jsx)(h,{size:18})},e))}),_&&(0,J.jsx)(`div`,{className:q.compareLabel,children:de(_)})]})]}),(0,J.jsx)(`div`,{className:q.compareResult,children:o(`game.zhajinhua.result.winner`,{player:u})}),(0,J.jsx)(`button`,{className:`btn btn-sm`,onClick:a,children:o(`common.close`)})]})})}function Q(e,t){let n=e.toLowerCase();return t.find(e=>e.address===n)?.name||be(e)}var we=i(`/game/zhajinhua/`)({component:Z});export{we as Route};