@minicode-dev/chat 0.1.15 → 0.1.16
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.
- package/CHANGELOG.md +10 -0
- package/dist/chunks/{BattleshipGameCard-8H-sBCsv.js → BattleshipGameCard-DwNzgH5G.js} +199 -199
- package/dist/chunks/{BattleshipGameCard-8H-sBCsv.js.map → BattleshipGameCard-DwNzgH5G.js.map} +1 -1
- package/dist/chunks/{BattleshipPrototype-CmQd5AUO.js → BattleshipPrototype-CSQxQJt5.js} +52 -52
- package/dist/chunks/{BattleshipPrototype-CmQd5AUO.js.map → BattleshipPrototype-CSQxQJt5.js.map} +1 -1
- package/dist/chunks/{ChatContext-CVW58bzC.js → ChatContext-D5rqvt5D.js} +2 -2
- package/dist/chunks/{ChatContext-CVW58bzC.js.map → ChatContext-D5rqvt5D.js.map} +1 -1
- package/dist/chunks/RacingLiveGameCard-_wvhDRlG.js +1873 -0
- package/dist/chunks/RacingLiveGameCard-_wvhDRlG.js.map +1 -0
- package/dist/chunks/{TanksGameCard-DnCufSH6.js → TanksGameCard-B-gtDYYg.js} +137 -137
- package/dist/chunks/{TanksGameCard-DnCufSH6.js.map → TanksGameCard-B-gtDYYg.js.map} +1 -1
- package/dist/chunks/{TanksLiveGameCard-D_27Jomp.js → TanksLiveGameCard-DXgd1J2Y.js} +392 -392
- package/dist/chunks/{TanksLiveGameCard-D_27Jomp.js.map → TanksLiveGameCard-DXgd1J2Y.js.map} +1 -1
- package/dist/chunks/{avalancheScene3d-CIE2WKQn.js → avalancheScene3d-CNbQyGUd.js} +2 -2
- package/dist/chunks/{avalancheScene3d-CIE2WKQn.js.map → avalancheScene3d-CNbQyGUd.js.map} +1 -1
- package/dist/chunks/{connect4Sounds-BqEHULjv.js → connect4Sounds-3a6h7gUC.js} +8 -8
- package/dist/chunks/{connect4Sounds-BqEHULjv.js.map → connect4Sounds-3a6h7gUC.js.map} +1 -1
- package/dist/chunks/createLiveScene-jrzyQogh.js +756 -0
- package/dist/chunks/{createLiveScene-MTGRv8rx.js.map → createLiveScene-jrzyQogh.js.map} +1 -1
- package/dist/chunks/createRaceScene-C-aw0dlX.js +291 -0
- package/dist/chunks/createRaceScene-C-aw0dlX.js.map +1 -0
- package/dist/chunks/{createTankScene-yhX6la27.js → createTankScene-6asWHKFf.js} +123 -125
- package/dist/chunks/{createTankScene-yhX6la27.js.map → createTankScene-6asWHKFf.js.map} +1 -1
- package/dist/chunks/fx-BNJvJYOu.js +871 -0
- package/dist/chunks/fx-BNJvJYOu.js.map +1 -0
- package/dist/chunks/{games-Bl_8raG6.js → games-D_9SiSBT.js} +708 -697
- package/dist/chunks/games-D_9SiSBT.js.map +1 -0
- package/dist/chunks/tank-DIn6Jyt8.js +605 -0
- package/dist/chunks/tank-DIn6Jyt8.js.map +1 -0
- package/dist/chunks/{tanks-live-BcKmrhnV.js → tanks-live-BRhYW4MG.js} +36 -36
- package/dist/chunks/{tanks-live-BcKmrhnV.js.map → tanks-live-BRhYW4MG.js.map} +1 -1
- package/dist/chunks/{tanksSounds-DTnidbiq.js → tanksSounds-tYPzje61.js} +4 -4
- package/dist/chunks/{tanksSounds-DTnidbiq.js.map → tanksSounds-tYPzje61.js.map} +1 -1
- package/dist/chunks/track-C1TWWT19.js +557 -0
- package/dist/chunks/track-C1TWWT19.js.map +1 -0
- package/dist/index.js +4234 -4018
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/index.d.ts +2 -0
- package/package.json +1 -1
- package/dist/chunks/RacingLiveGameCard-8j9QLnxW.js +0 -1654
- package/dist/chunks/RacingLiveGameCard-8j9QLnxW.js.map +0 -1
- package/dist/chunks/createLiveScene-MTGRv8rx.js +0 -758
- package/dist/chunks/createRaceScene-BiEtQsVs.js +0 -306
- package/dist/chunks/createRaceScene-BiEtQsVs.js.map +0 -1
- package/dist/chunks/decor-CyyCnmct.js +0 -418
- package/dist/chunks/decor-CyyCnmct.js.map +0 -1
- package/dist/chunks/fx-1ZwzmPWj.js +0 -458
- package/dist/chunks/fx-1ZwzmPWj.js.map +0 -1
- package/dist/chunks/games-Bl_8raG6.js.map +0 -1
- package/dist/chunks/rolldown-runtime-CGnMp29N.js +0 -10
- package/dist/chunks/tank-DjEQ4FXF.js +0 -553
- package/dist/chunks/tank-DjEQ4FXF.js.map +0 -1
- package/dist/chunks/track-BcnnpnAm.js +0 -259
- package/dist/chunks/track-BcnnpnAm.js.map +0 -1
- package/dist/chunks/util-DwI9bfS7.js +0 -57
- package/dist/chunks/util-DwI9bfS7.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as e, t } from "./games-
|
|
1
|
+
import { a as e, t } from "./games-D_9SiSBT.js";
|
|
2
2
|
import { useSyncExternalStore as n } from "react";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
4
|
+
import { Volume2 as a, VolumeX as o } from "lucide-react";
|
|
5
5
|
// src/audio/useSoundSettings.js
|
|
6
6
|
function s(e) {
|
|
7
7
|
return n(e.subscribe, e.getSettings, e.getSettings);
|
|
@@ -9,17 +9,17 @@ function s(e) {
|
|
|
9
9
|
// src/games/shared/GameSoundControls.jsx
|
|
10
10
|
function c({ manager: e }) {
|
|
11
11
|
let { volume: t, muted: n } = s(e), c = n || t === 0;
|
|
12
|
-
return /* @__PURE__ */
|
|
12
|
+
return /* @__PURE__ */ i("div", {
|
|
13
13
|
className: "chat-game-sound",
|
|
14
|
-
children: [/* @__PURE__ */
|
|
14
|
+
children: [/* @__PURE__ */ r("button", {
|
|
15
15
|
type: "button",
|
|
16
16
|
className: "chat-game-sound-toggle",
|
|
17
17
|
onClick: e.toggleMuted,
|
|
18
18
|
"aria-pressed": c,
|
|
19
19
|
"aria-label": c ? "Activar sonido del juego" : "Silenciar sonido del juego",
|
|
20
20
|
title: c ? "Activar sonido" : "Silenciar",
|
|
21
|
-
children:
|
|
22
|
-
}), /* @__PURE__ */
|
|
21
|
+
children: r(c ? o : a, { size: 15 })
|
|
22
|
+
}), /* @__PURE__ */ r("input", {
|
|
23
23
|
type: "range",
|
|
24
24
|
min: "0",
|
|
25
25
|
max: "100",
|
|
@@ -232,4 +232,4 @@ function k() {
|
|
|
232
232
|
}
|
|
233
233
|
export { g as a, k as i, O as n, c as o, T as r, _ as t };
|
|
234
234
|
|
|
235
|
-
//# sourceMappingURL=connect4Sounds-
|
|
235
|
+
//# sourceMappingURL=connect4Sounds-3a6h7gUC.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect4Sounds-BqEHULjv.js","names":["useChatGames","useSoundSettings","useSyncExternalStore","manager","GameSoundControls","silent","muted","volume","jsx","VolumeX","Volume2","event","GESTURES","MAX_KEYS","MAX_LATENCY_MS","clamp","value","readSettings","stored","storageKey","defaults","LOOP_FADE_IN_S","LOOP_FADE_OUT_S","createSoundManager","id","AudioContextClass","settings","settingsFrom","defaultVolume","context","gain","unlocked","raw","buffers","missing","playedKeys","listeners","wantedLoops","loops","loopOptions","started","listening","unsubscribeSettings","urlFor","baseUrl","sounds","name","fetchRaw","response","ensureContext","loadBuffer","ctx","data","resolve","buffer","levelFor","levels","beginLoop","token","level","fadeIn","source","trim","endLoop","loop","fadeOut","now","unlock","removeGestureListeners","type","remember","key","emit","listener","applySettings","next","setSettings","patch","start","names","requestedAt","node","options","connect4Sounds","chatSoundUrl","FOLLOW_UP_MS","pendingDrops","followUps","stopSounds","same","left","right","reducedMotion","playMoveOutcome","game","eventId","settleConnect4Drop","entry","timer","scheduleDrop","delay","onGameChange","before","isConnect4SoundsRunning","startConnect4Sounds","unsubscribe","state","previous","stop"],"sources":["../../src/audio/useSoundSettings.js","../../src/games/shared/GameSoundControls.jsx","../../src/audio/createSoundManager.js","../../src/games/connect4/connect4Sounds.js"],"sourcesContent":["import { useSyncExternalStore } from 'react';\n\n// React binding for a sound manager's volume/mute settings.\nexport default function useSoundSettings(manager) {\n return useSyncExternalStore(manager.subscribe, manager.getSettings, manager.getSettings);\n}\n","/* eslint-disable react/prop-types */\nimport { Volume2, VolumeX } from 'lucide-react';\nimport useSoundSettings from '../../audio/useSoundSettings';\n\nexport default function GameSoundControls({ manager }) {\n const { volume, muted } = useSoundSettings(manager);\n const silent = muted || volume === 0;\n return (\n <div className=\"chat-game-sound\">\n <button type=\"button\" className=\"chat-game-sound-toggle\" onClick={manager.toggleMuted}\n aria-pressed={silent} aria-label={silent ? 'Activar sonido del juego' : 'Silenciar sonido del juego'} title={silent ? 'Activar sonido' : 'Silenciar'}>\n {silent ? <VolumeX size={15} /> : <Volume2 size={15} />}\n </button>\n <input type=\"range\" min=\"0\" max=\"100\" step=\"5\" value={Math.round((muted ? 0 : volume) * 100)}\n onChange={(event) => manager.setVolume(Number(event.target.value) / 100)} aria-label=\"Volumen del juego\" />\n </div>\n );\n}\n","// Reusable game sound manager built on Web Audio (no dependencies).\n// - Lazy: files are fetched on preload()/first play and decoded once.\n// - Autoplay-safe: the AudioContext is created/resumed only after a user\n// gesture; sounds requested before that are dropped, never queued.\n// - Fault-tolerant: a missing or undecodable file is marked unavailable and\n// every later play() of it is a silent no-op.\n// - Deduplicated: play(name, { key }) ignores keys already played recently.\n// - Optional per-sound levels, looped beds (startLoop/stopLoop) and settings\n// shared with another manager (settingsFrom) so games keep one preference.\n// - Side-effect free at creation (SSR-safe): gesture listeners and the\n// settingsFrom subscription are registered on start() or the first\n// preload()/play()/startLoop()/subscribe(); dispose() tears everything down.\n\nconst GESTURES = ['pointerdown', 'keydown', 'touchend'];\nconst MAX_KEYS = 300;\n// Sounds still loading when requested are dropped if they would play too late.\nconst MAX_LATENCY_MS = 800;\n\nconst clamp = (value) => Math.min(1, Math.max(0, Number(value) || 0));\n\nfunction readSettings(storageKey, defaults) {\n try {\n const stored = JSON.parse(localStorage.getItem(storageKey) ?? 'null');\n if (stored && typeof stored === 'object') {\n return { volume: clamp(stored.volume ?? defaults.volume), muted: Boolean(stored.muted) };\n }\n } catch { /* storage unavailable */ }\n return { volume: clamp(defaults.volume), muted: false };\n}\n\nconst LOOP_FADE_IN_S = 1.5;\nconst LOOP_FADE_OUT_S = 0.6;\n\nexport function createSoundManager({ id, baseUrl, sounds, levels = {}, defaultVolume = 0.6, settingsFrom = null }) {\n const storageKey = `lira.sound.${id}`;\n const AudioContextClass = typeof window !== 'undefined' ? (window.AudioContext || window.webkitAudioContext) : null;\n let settings = settingsFrom ? settingsFrom.getSettings() : readSettings(storageKey, { volume: defaultVolume });\n let context = null;\n let gain = null;\n let unlocked = false;\n const raw = new Map(); // name -> Promise<ArrayBuffer | null>\n const buffers = new Map(); // name -> Promise<AudioBuffer | null>\n const missing = new Set();\n const playedKeys = new Set();\n const listeners = new Set();\n const wantedLoops = new Set();\n const loops = new Map(); // name -> { source, level } once playing\n const loopOptions = new Map(); // name -> { fadeIn, fadeOut, trim } from startLoop\n let started = false;\n let listening = false;\n let unsubscribeSettings = null;\n\n // baseUrl may be a function so hosts can configure asset locations after import.\n const urlFor = (name) => `${String(typeof baseUrl === 'function' ? baseUrl() : baseUrl).replace(/\\/$/, '')}/${sounds[name]}`;\n\n const fetchRaw = (name) => {\n if (!raw.has(name)) {\n raw.set(name, fetch(urlFor(name))\n .then((response) => (response.ok ? response.arrayBuffer() : null))\n .catch(() => null));\n }\n return raw.get(name);\n };\n\n const ensureContext = () => {\n if (!AudioContextClass) return null;\n if (!context) {\n try {\n context = new AudioContextClass();\n gain = context.createGain();\n gain.gain.value = settings.muted ? 0 : settings.volume;\n gain.connect(context.destination);\n } catch {\n context = null;\n return null;\n }\n }\n if (context.state === 'suspended') void context.resume().catch(() => {});\n return context;\n };\n\n const loadBuffer = (name) => {\n if (missing.has(name) || !sounds[name]) return Promise.resolve(null);\n if (!buffers.has(name)) {\n const ctx = ensureContext();\n if (!ctx) return Promise.resolve(null);\n buffers.set(name, fetchRaw(name).then((data) => {\n // decodeAudioData detaches the buffer, so decode a copy.\n if (!data) return null;\n return new Promise((resolve) => {\n // Modern browsers also return a promise that rejects alongside the error\n // callback; without this catch a missing/HTML file is an unhandled rejection.\n ctx.decodeAudioData(data.slice(0), resolve, () => resolve(null))?.catch?.(() => resolve(null));\n });\n }).then((buffer) => {\n if (!buffer) missing.add(name);\n return buffer;\n }).catch(() => {\n missing.add(name);\n return null;\n }));\n }\n return buffers.get(name);\n };\n\n const levelFor = (name) => clamp(levels[name] ?? 1);\n\n const beginLoop = (name) => {\n if (!wantedLoops.has(name) || loops.has(name) || !unlocked) return;\n const ctx = ensureContext();\n if (!ctx) return;\n const token = {}; // a stop/start while loading must not start a stale copy\n loops.set(name, token);\n void loadBuffer(name).then((buffer) => {\n if (loops.get(name) !== token) return;\n if (!buffer || ctx.state === 'closed') { loops.delete(name); return; }\n try {\n const { fadeIn = LOOP_FADE_IN_S, trim = 0 } = loopOptions.get(name) ?? {};\n const level = ctx.createGain();\n level.gain.setValueAtTime(0, ctx.currentTime);\n level.gain.linearRampToValueAtTime(levelFor(name), ctx.currentTime + fadeIn);\n level.connect(gain);\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n source.loop = true;\n // Skips encoder padding at both ends (MP3) so the loop has no audible gap.\n if (trim > 0 && buffer.duration > trim * 4) {\n source.loopStart = trim;\n source.loopEnd = buffer.duration - trim;\n }\n source.connect(level);\n source.start(0, trim > 0 && buffer.duration > trim * 4 ? trim : 0);\n loops.set(name, { source, level });\n } catch {\n loops.delete(name);\n }\n });\n };\n\n const endLoop = (name) => {\n const loop = loops.get(name);\n loops.delete(name);\n if (!loop?.source || !context) return;\n try {\n const fadeOut = loopOptions.get(name)?.fadeOut ?? LOOP_FADE_OUT_S;\n const now = context.currentTime;\n loop.level.gain.cancelScheduledValues(now);\n loop.level.gain.setValueAtTime(loop.level.gain.value, now);\n loop.level.gain.linearRampToValueAtTime(0, now + fadeOut);\n loop.source.stop(now + fadeOut + 0.05);\n } catch { /* already stopped */ }\n };\n\n const unlock = () => {\n if (unlocked) return;\n unlocked = true;\n removeGestureListeners();\n if (ensureContext()) Object.keys(sounds).forEach((name) => { if (raw.has(name)) void loadBuffer(name); });\n wantedLoops.forEach(beginLoop);\n };\n\n function removeGestureListeners() {\n if (!listening) return;\n listening = false;\n GESTURES.forEach((type) => window.removeEventListener(type, unlock, true));\n }\n\n const remember = (key) => {\n if (playedKeys.has(key)) return false;\n playedKeys.add(key);\n if (playedKeys.size > MAX_KEYS) playedKeys.delete(playedKeys.values().next().value);\n return true;\n };\n\n const emit = () => listeners.forEach((listener) => listener());\n\n const applySettings = (next) => {\n settings = next;\n if (gain) gain.gain.value = settings.muted ? 0 : settings.volume;\n emit();\n };\n\n const setSettings = (patch) => {\n const next = { volume: clamp(patch.volume ?? settings.volume), muted: patch.muted ?? settings.muted };\n try { localStorage.setItem(storageKey, JSON.stringify(next)); } catch { /* storage unavailable */ }\n applySettings(next);\n };\n\n // Idempotent; registers the unlock listeners and the shared-settings link.\n const start = () => {\n if (started) return;\n started = true;\n if (settingsFrom) {\n settings = settingsFrom.getSettings();\n unsubscribeSettings = settingsFrom.subscribe(() => applySettings(settingsFrom.getSettings()));\n }\n if (typeof window === 'undefined' || unlocked) return;\n // Sticky activation: a gesture already happened before the manager started.\n if (typeof navigator !== 'undefined' && navigator.userActivation?.hasBeenActive) unlocked = true;\n else {\n listening = true;\n GESTURES.forEach((type) => window.addEventListener(type, unlock, { capture: true, passive: true }));\n }\n };\n\n // Removes listeners, unlinks settings, stops loops and closes the context.\n // A later start()/play() starts the manager again.\n const dispose = () => {\n removeGestureListeners();\n if (unsubscribeSettings) { unsubscribeSettings(); unsubscribeSettings = null; }\n started = false;\n wantedLoops.clear();\n loops.forEach((loop) => { try { loop?.source?.stop(); } catch { /* already stopped */ } });\n loops.clear();\n buffers.clear();\n const ctx = context;\n context = null;\n gain = null;\n if (ctx && ctx.state !== 'closed') void ctx.close().catch(() => {});\n };\n\n return {\n start,\n dispose,\n // Fetches files ahead of time without touching the AudioContext.\n preload(names = Object.keys(sounds)) {\n start();\n names.forEach((name) => {\n if (!sounds[name] || missing.has(name)) return;\n void fetchRaw(name);\n if (context) void loadBuffer(name);\n });\n },\n // Returns true when the sound was scheduled (not muted, unlocked, not a duplicate).\n play(name, { key } = {}) {\n start();\n if (key != null && !remember(`${name}:${key}`)) return false;\n if (!sounds[name] || missing.has(name) || !unlocked || settings.muted || settings.volume === 0) return false;\n const ctx = ensureContext();\n if (!ctx) return false;\n const requestedAt = Date.now();\n void loadBuffer(name).then((buffer) => {\n if (!buffer || ctx.state === 'closed' || Date.now() - requestedAt > MAX_LATENCY_MS) return;\n try {\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n const level = levelFor(name);\n if (level === 1) source.connect(gain);\n else {\n const node = ctx.createGain();\n node.gain.value = level;\n source.connect(node);\n node.connect(gain);\n }\n source.start();\n } catch { /* the game must keep working without audio */ }\n });\n return true;\n },\n // Marks a key as handled without playing (e.g. replays that must stay silent).\n markPlayed(name, key) { remember(`${name}:${key}`); },\n // Looped bed through the master gain, so mute/volume apply live. Requested\n // before the first gesture it starts on unlock; stopLoop fades it out.\n // Optional { fadeIn, fadeOut, trim } seconds; defaults keep the slow ambient fades.\n startLoop(name, options) {\n start();\n if (!sounds[name] || missing.has(name)) return;\n if (options) loopOptions.set(name, options);\n wantedLoops.add(name);\n beginLoop(name);\n },\n stopLoop(name) {\n wantedLoops.delete(name);\n endLoop(name);\n },\n // Before start() a follower reads the shared settings directly.\n getSettings: () => (settingsFrom && !started ? settingsFrom.getSettings() : settings),\n setVolume: settingsFrom ? settingsFrom.setVolume : (volume) => setSettings({ volume, muted: clamp(volume) === 0 ? settings.muted : false }),\n setMuted: settingsFrom ? settingsFrom.setMuted : (muted) => setSettings({ muted: Boolean(muted) }),\n toggleMuted: settingsFrom ? settingsFrom.toggleMuted : () => setSettings({ muted: !settings.muted }),\n subscribe(listener) {\n start();\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n}\n\nexport default createSoundManager;\n","import { createSoundManager } from '../../audio/createSoundManager';\nimport { chatSoundUrl } from '../../assets/chatAssets';\nimport useChatGames from '../shared/useChatGames';\n\nexport const connect4Sounds = createSoundManager({\n id: 'connect4',\n baseUrl: () => chatSoundUrl('connect4'), // resolved lazily at fetch time\n sounds: {\n invite: 'invite.ogg', start: 'start.ogg', drop: 'drop.ogg', turn: 'turn.ogg',\n win: 'win.ogg', lose: 'lose.ogg', draw: 'draw.ogg', close: 'close.ogg',\n },\n});\n\n// Keep in sync with .chat-game-piece--new in games.css.\nconst DROP_ANIMATION_MS = 360;\n// Gap between the piece landing and the follow-up cue (turn/win/lose/draw).\nconst FOLLOW_UP_MS = 140;\nconst pendingDrops = new Map(); // eventId -> { game, timer }\nconst followUps = new Set(); // outcome timers scheduled after a drop\nlet stopSounds = null;\n\nconst same = (left, right) => left != null && right != null && String(left) === String(right);\nconst reducedMotion = () => typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\nfunction playMoveOutcome(game, eventId) {\n if (game.status === 'won') connect4Sounds.play(same(game.winnerId, game.ownId) ? 'win' : 'lose', { key: eventId });\n else if (game.status === 'draw') connect4Sounds.play('draw', { key: eventId });\n // turnId is the next player, so this only fires after the rival's move.\n else if (game.status === 'active' && same(game.turnId, game.ownId)) connect4Sounds.play('turn', { key: eventId });\n}\n\n// Called when the piece animation ends (or by the fallback timer when the card\n// is not on screen). Plays the impact, then the outcome of that move.\nexport function settleConnect4Drop(eventId) {\n const entry = pendingDrops.get(eventId);\n if (!entry) return;\n clearTimeout(entry.timer);\n pendingDrops.delete(eventId);\n connect4Sounds.play('drop', { key: eventId });\n const timer = setTimeout(() => {\n followUps.delete(timer);\n playMoveOutcome(entry.game, eventId);\n }, FOLLOW_UP_MS);\n followUps.add(timer);\n}\n\nfunction scheduleDrop(game) {\n const eventId = game.lastMove?.eventId;\n if (!eventId || pendingDrops.has(eventId)) return;\n const delay = reducedMotion() ? 0 : DROP_ANIMATION_MS + 80;\n pendingDrops.set(eventId, { game, timer: setTimeout(() => settleConnect4Drop(eventId), delay) });\n}\n\nfunction onGameChange(game, before) {\n if (!before) {\n if (game.status === 'invited' && same(game.guestId, game.ownId)) connect4Sounds.play('invite', { key: game.inviteEventId });\n return;\n }\n if (before.status === 'invited' && game.status === 'active') {\n connect4Sounds.play('start', { key: game.lastEventId });\n return;\n }\n if (game.ply > before.ply) {\n scheduleDrop(game);\n return;\n }\n // Competing invitations are closed locally without closedBy: stay silent.\n const ended = (game.status === 'closed' && game.closedBy != null) || game.status === 'declined';\n if (ended && before.status !== game.status) connect4Sounds.play('close', { key: game.gameId });\n}\n\n// Sounds derive from store transitions, so WebSocket replays already rejected\n// by the store (seen event_id) never reach here; keys guard the rest.\n// Idempotent: repeated calls return the same stop(), which unsubscribes and\n// clears pending timers.\n/** True when startConnect4Sounds() is active (someone else owns its stop()). */\nexport const isConnect4SoundsRunning = () => stopSounds != null;\n\nexport function startConnect4Sounds() {\n if (stopSounds) return stopSounds;\n connect4Sounds.start();\n const unsubscribe = useChatGames.subscribe((state, previous) => {\n if (state.games === previous.games) return;\n Object.entries(state.games).forEach(([key, game]) => {\n const before = previous.games[key];\n // Other games (e.g. Battleship) have their own cues.\n if (game.game && game.game !== 'connect4') return;\n if (game !== before && game.ownId != null) onGameChange(game, before);\n });\n });\n const stop = () => {\n if (stopSounds !== stop) return;\n stopSounds = null;\n unsubscribe();\n pendingDrops.forEach(({ timer }) => clearTimeout(timer));\n pendingDrops.clear();\n followUps.forEach(clearTimeout);\n followUps.clear();\n };\n stopSounds = stop;\n return stop;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"connect4Sounds-3a6h7gUC.js","names":["useChatGames","useSoundSettings","useSyncExternalStore","manager","GameSoundControls","silent","muted","volume","jsx","VolumeX","Volume2","event","GESTURES","MAX_KEYS","MAX_LATENCY_MS","clamp","value","readSettings","stored","storageKey","defaults","LOOP_FADE_IN_S","LOOP_FADE_OUT_S","createSoundManager","id","AudioContextClass","settings","settingsFrom","defaultVolume","context","gain","unlocked","raw","buffers","missing","playedKeys","listeners","wantedLoops","loops","loopOptions","started","listening","unsubscribeSettings","urlFor","baseUrl","sounds","name","fetchRaw","response","ensureContext","loadBuffer","ctx","data","resolve","buffer","levelFor","levels","beginLoop","token","level","fadeIn","source","trim","endLoop","loop","fadeOut","now","unlock","removeGestureListeners","type","remember","key","emit","listener","applySettings","next","setSettings","patch","start","names","requestedAt","node","options","connect4Sounds","chatSoundUrl","FOLLOW_UP_MS","pendingDrops","followUps","stopSounds","same","left","right","reducedMotion","playMoveOutcome","game","eventId","settleConnect4Drop","entry","timer","scheduleDrop","delay","onGameChange","before","isConnect4SoundsRunning","startConnect4Sounds","unsubscribe","state","previous","stop"],"sources":["../../src/audio/useSoundSettings.js","../../src/games/shared/GameSoundControls.jsx","../../src/audio/createSoundManager.js","../../src/games/connect4/connect4Sounds.js"],"sourcesContent":["import { useSyncExternalStore } from 'react';\n\n// React binding for a sound manager's volume/mute settings.\nexport default function useSoundSettings(manager) {\n return useSyncExternalStore(manager.subscribe, manager.getSettings, manager.getSettings);\n}\n","/* eslint-disable react/prop-types */\nimport { Volume2, VolumeX } from 'lucide-react';\nimport useSoundSettings from '../../audio/useSoundSettings';\n\nexport default function GameSoundControls({ manager }) {\n const { volume, muted } = useSoundSettings(manager);\n const silent = muted || volume === 0;\n return (\n <div className=\"chat-game-sound\">\n <button type=\"button\" className=\"chat-game-sound-toggle\" onClick={manager.toggleMuted}\n aria-pressed={silent} aria-label={silent ? 'Activar sonido del juego' : 'Silenciar sonido del juego'} title={silent ? 'Activar sonido' : 'Silenciar'}>\n {silent ? <VolumeX size={15} /> : <Volume2 size={15} />}\n </button>\n <input type=\"range\" min=\"0\" max=\"100\" step=\"5\" value={Math.round((muted ? 0 : volume) * 100)}\n onChange={(event) => manager.setVolume(Number(event.target.value) / 100)} aria-label=\"Volumen del juego\" />\n </div>\n );\n}\n","// Reusable game sound manager built on Web Audio (no dependencies).\n// - Lazy: files are fetched on preload()/first play and decoded once.\n// - Autoplay-safe: the AudioContext is created/resumed only after a user\n// gesture; sounds requested before that are dropped, never queued.\n// - Fault-tolerant: a missing or undecodable file is marked unavailable and\n// every later play() of it is a silent no-op.\n// - Deduplicated: play(name, { key }) ignores keys already played recently.\n// - Optional per-sound levels, looped beds (startLoop/stopLoop) and settings\n// shared with another manager (settingsFrom) so games keep one preference.\n// - Side-effect free at creation (SSR-safe): gesture listeners and the\n// settingsFrom subscription are registered on start() or the first\n// preload()/play()/startLoop()/subscribe(); dispose() tears everything down.\n\nconst GESTURES = ['pointerdown', 'keydown', 'touchend'];\nconst MAX_KEYS = 300;\n// Sounds still loading when requested are dropped if they would play too late.\nconst MAX_LATENCY_MS = 800;\n\nconst clamp = (value) => Math.min(1, Math.max(0, Number(value) || 0));\n\nfunction readSettings(storageKey, defaults) {\n try {\n const stored = JSON.parse(localStorage.getItem(storageKey) ?? 'null');\n if (stored && typeof stored === 'object') {\n return { volume: clamp(stored.volume ?? defaults.volume), muted: Boolean(stored.muted) };\n }\n } catch { /* storage unavailable */ }\n return { volume: clamp(defaults.volume), muted: false };\n}\n\nconst LOOP_FADE_IN_S = 1.5;\nconst LOOP_FADE_OUT_S = 0.6;\n\nexport function createSoundManager({ id, baseUrl, sounds, levels = {}, defaultVolume = 0.6, settingsFrom = null }) {\n const storageKey = `lira.sound.${id}`;\n const AudioContextClass = typeof window !== 'undefined' ? (window.AudioContext || window.webkitAudioContext) : null;\n let settings = settingsFrom ? settingsFrom.getSettings() : readSettings(storageKey, { volume: defaultVolume });\n let context = null;\n let gain = null;\n let unlocked = false;\n const raw = new Map(); // name -> Promise<ArrayBuffer | null>\n const buffers = new Map(); // name -> Promise<AudioBuffer | null>\n const missing = new Set();\n const playedKeys = new Set();\n const listeners = new Set();\n const wantedLoops = new Set();\n const loops = new Map(); // name -> { source, level } once playing\n const loopOptions = new Map(); // name -> { fadeIn, fadeOut, trim } from startLoop\n let started = false;\n let listening = false;\n let unsubscribeSettings = null;\n\n // baseUrl may be a function so hosts can configure asset locations after import.\n const urlFor = (name) => `${String(typeof baseUrl === 'function' ? baseUrl() : baseUrl).replace(/\\/$/, '')}/${sounds[name]}`;\n\n const fetchRaw = (name) => {\n if (!raw.has(name)) {\n raw.set(name, fetch(urlFor(name))\n .then((response) => (response.ok ? response.arrayBuffer() : null))\n .catch(() => null));\n }\n return raw.get(name);\n };\n\n const ensureContext = () => {\n if (!AudioContextClass) return null;\n if (!context) {\n try {\n context = new AudioContextClass();\n gain = context.createGain();\n gain.gain.value = settings.muted ? 0 : settings.volume;\n gain.connect(context.destination);\n } catch {\n context = null;\n return null;\n }\n }\n if (context.state === 'suspended') void context.resume().catch(() => {});\n return context;\n };\n\n const loadBuffer = (name) => {\n if (missing.has(name) || !sounds[name]) return Promise.resolve(null);\n if (!buffers.has(name)) {\n const ctx = ensureContext();\n if (!ctx) return Promise.resolve(null);\n buffers.set(name, fetchRaw(name).then((data) => {\n // decodeAudioData detaches the buffer, so decode a copy.\n if (!data) return null;\n return new Promise((resolve) => {\n // Modern browsers also return a promise that rejects alongside the error\n // callback; without this catch a missing/HTML file is an unhandled rejection.\n ctx.decodeAudioData(data.slice(0), resolve, () => resolve(null))?.catch?.(() => resolve(null));\n });\n }).then((buffer) => {\n if (!buffer) missing.add(name);\n return buffer;\n }).catch(() => {\n missing.add(name);\n return null;\n }));\n }\n return buffers.get(name);\n };\n\n const levelFor = (name) => clamp(levels[name] ?? 1);\n\n const beginLoop = (name) => {\n if (!wantedLoops.has(name) || loops.has(name) || !unlocked) return;\n const ctx = ensureContext();\n if (!ctx) return;\n const token = {}; // a stop/start while loading must not start a stale copy\n loops.set(name, token);\n void loadBuffer(name).then((buffer) => {\n if (loops.get(name) !== token) return;\n if (!buffer || ctx.state === 'closed') { loops.delete(name); return; }\n try {\n const { fadeIn = LOOP_FADE_IN_S, trim = 0 } = loopOptions.get(name) ?? {};\n const level = ctx.createGain();\n level.gain.setValueAtTime(0, ctx.currentTime);\n level.gain.linearRampToValueAtTime(levelFor(name), ctx.currentTime + fadeIn);\n level.connect(gain);\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n source.loop = true;\n // Skips encoder padding at both ends (MP3) so the loop has no audible gap.\n if (trim > 0 && buffer.duration > trim * 4) {\n source.loopStart = trim;\n source.loopEnd = buffer.duration - trim;\n }\n source.connect(level);\n source.start(0, trim > 0 && buffer.duration > trim * 4 ? trim : 0);\n loops.set(name, { source, level });\n } catch {\n loops.delete(name);\n }\n });\n };\n\n const endLoop = (name) => {\n const loop = loops.get(name);\n loops.delete(name);\n if (!loop?.source || !context) return;\n try {\n const fadeOut = loopOptions.get(name)?.fadeOut ?? LOOP_FADE_OUT_S;\n const now = context.currentTime;\n loop.level.gain.cancelScheduledValues(now);\n loop.level.gain.setValueAtTime(loop.level.gain.value, now);\n loop.level.gain.linearRampToValueAtTime(0, now + fadeOut);\n loop.source.stop(now + fadeOut + 0.05);\n } catch { /* already stopped */ }\n };\n\n const unlock = () => {\n if (unlocked) return;\n unlocked = true;\n removeGestureListeners();\n if (ensureContext()) Object.keys(sounds).forEach((name) => { if (raw.has(name)) void loadBuffer(name); });\n wantedLoops.forEach(beginLoop);\n };\n\n function removeGestureListeners() {\n if (!listening) return;\n listening = false;\n GESTURES.forEach((type) => window.removeEventListener(type, unlock, true));\n }\n\n const remember = (key) => {\n if (playedKeys.has(key)) return false;\n playedKeys.add(key);\n if (playedKeys.size > MAX_KEYS) playedKeys.delete(playedKeys.values().next().value);\n return true;\n };\n\n const emit = () => listeners.forEach((listener) => listener());\n\n const applySettings = (next) => {\n settings = next;\n if (gain) gain.gain.value = settings.muted ? 0 : settings.volume;\n emit();\n };\n\n const setSettings = (patch) => {\n const next = { volume: clamp(patch.volume ?? settings.volume), muted: patch.muted ?? settings.muted };\n try { localStorage.setItem(storageKey, JSON.stringify(next)); } catch { /* storage unavailable */ }\n applySettings(next);\n };\n\n // Idempotent; registers the unlock listeners and the shared-settings link.\n const start = () => {\n if (started) return;\n started = true;\n if (settingsFrom) {\n settings = settingsFrom.getSettings();\n unsubscribeSettings = settingsFrom.subscribe(() => applySettings(settingsFrom.getSettings()));\n }\n if (typeof window === 'undefined' || unlocked) return;\n // Sticky activation: a gesture already happened before the manager started.\n if (typeof navigator !== 'undefined' && navigator.userActivation?.hasBeenActive) unlocked = true;\n else {\n listening = true;\n GESTURES.forEach((type) => window.addEventListener(type, unlock, { capture: true, passive: true }));\n }\n };\n\n // Removes listeners, unlinks settings, stops loops and closes the context.\n // A later start()/play() starts the manager again.\n const dispose = () => {\n removeGestureListeners();\n if (unsubscribeSettings) { unsubscribeSettings(); unsubscribeSettings = null; }\n started = false;\n wantedLoops.clear();\n loops.forEach((loop) => { try { loop?.source?.stop(); } catch { /* already stopped */ } });\n loops.clear();\n buffers.clear();\n const ctx = context;\n context = null;\n gain = null;\n if (ctx && ctx.state !== 'closed') void ctx.close().catch(() => {});\n };\n\n return {\n start,\n dispose,\n // Fetches files ahead of time without touching the AudioContext.\n preload(names = Object.keys(sounds)) {\n start();\n names.forEach((name) => {\n if (!sounds[name] || missing.has(name)) return;\n void fetchRaw(name);\n if (context) void loadBuffer(name);\n });\n },\n // Returns true when the sound was scheduled (not muted, unlocked, not a duplicate).\n play(name, { key } = {}) {\n start();\n if (key != null && !remember(`${name}:${key}`)) return false;\n if (!sounds[name] || missing.has(name) || !unlocked || settings.muted || settings.volume === 0) return false;\n const ctx = ensureContext();\n if (!ctx) return false;\n const requestedAt = Date.now();\n void loadBuffer(name).then((buffer) => {\n if (!buffer || ctx.state === 'closed' || Date.now() - requestedAt > MAX_LATENCY_MS) return;\n try {\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n const level = levelFor(name);\n if (level === 1) source.connect(gain);\n else {\n const node = ctx.createGain();\n node.gain.value = level;\n source.connect(node);\n node.connect(gain);\n }\n source.start();\n } catch { /* the game must keep working without audio */ }\n });\n return true;\n },\n // Marks a key as handled without playing (e.g. replays that must stay silent).\n markPlayed(name, key) { remember(`${name}:${key}`); },\n // Looped bed through the master gain, so mute/volume apply live. Requested\n // before the first gesture it starts on unlock; stopLoop fades it out.\n // Optional { fadeIn, fadeOut, trim } seconds; defaults keep the slow ambient fades.\n startLoop(name, options) {\n start();\n if (!sounds[name] || missing.has(name)) return;\n if (options) loopOptions.set(name, options);\n wantedLoops.add(name);\n beginLoop(name);\n },\n stopLoop(name) {\n wantedLoops.delete(name);\n endLoop(name);\n },\n // Before start() a follower reads the shared settings directly.\n getSettings: () => (settingsFrom && !started ? settingsFrom.getSettings() : settings),\n setVolume: settingsFrom ? settingsFrom.setVolume : (volume) => setSettings({ volume, muted: clamp(volume) === 0 ? settings.muted : false }),\n setMuted: settingsFrom ? settingsFrom.setMuted : (muted) => setSettings({ muted: Boolean(muted) }),\n toggleMuted: settingsFrom ? settingsFrom.toggleMuted : () => setSettings({ muted: !settings.muted }),\n subscribe(listener) {\n start();\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n}\n\nexport default createSoundManager;\n","import { createSoundManager } from '../../audio/createSoundManager';\nimport { chatSoundUrl } from '../../assets/chatAssets';\nimport useChatGames from '../shared/useChatGames';\n\nexport const connect4Sounds = createSoundManager({\n id: 'connect4',\n baseUrl: () => chatSoundUrl('connect4'), // resolved lazily at fetch time\n sounds: {\n invite: 'invite.ogg', start: 'start.ogg', drop: 'drop.ogg', turn: 'turn.ogg',\n win: 'win.ogg', lose: 'lose.ogg', draw: 'draw.ogg', close: 'close.ogg',\n },\n});\n\n// Keep in sync with .chat-game-piece--new in games.css.\nconst DROP_ANIMATION_MS = 360;\n// Gap between the piece landing and the follow-up cue (turn/win/lose/draw).\nconst FOLLOW_UP_MS = 140;\nconst pendingDrops = new Map(); // eventId -> { game, timer }\nconst followUps = new Set(); // outcome timers scheduled after a drop\nlet stopSounds = null;\n\nconst same = (left, right) => left != null && right != null && String(left) === String(right);\nconst reducedMotion = () => typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\nfunction playMoveOutcome(game, eventId) {\n if (game.status === 'won') connect4Sounds.play(same(game.winnerId, game.ownId) ? 'win' : 'lose', { key: eventId });\n else if (game.status === 'draw') connect4Sounds.play('draw', { key: eventId });\n // turnId is the next player, so this only fires after the rival's move.\n else if (game.status === 'active' && same(game.turnId, game.ownId)) connect4Sounds.play('turn', { key: eventId });\n}\n\n// Called when the piece animation ends (or by the fallback timer when the card\n// is not on screen). Plays the impact, then the outcome of that move.\nexport function settleConnect4Drop(eventId) {\n const entry = pendingDrops.get(eventId);\n if (!entry) return;\n clearTimeout(entry.timer);\n pendingDrops.delete(eventId);\n connect4Sounds.play('drop', { key: eventId });\n const timer = setTimeout(() => {\n followUps.delete(timer);\n playMoveOutcome(entry.game, eventId);\n }, FOLLOW_UP_MS);\n followUps.add(timer);\n}\n\nfunction scheduleDrop(game) {\n const eventId = game.lastMove?.eventId;\n if (!eventId || pendingDrops.has(eventId)) return;\n const delay = reducedMotion() ? 0 : DROP_ANIMATION_MS + 80;\n pendingDrops.set(eventId, { game, timer: setTimeout(() => settleConnect4Drop(eventId), delay) });\n}\n\nfunction onGameChange(game, before) {\n if (!before) {\n if (game.status === 'invited' && same(game.guestId, game.ownId)) connect4Sounds.play('invite', { key: game.inviteEventId });\n return;\n }\n if (before.status === 'invited' && game.status === 'active') {\n connect4Sounds.play('start', { key: game.lastEventId });\n return;\n }\n if (game.ply > before.ply) {\n scheduleDrop(game);\n return;\n }\n // Competing invitations are closed locally without closedBy: stay silent.\n const ended = (game.status === 'closed' && game.closedBy != null) || game.status === 'declined';\n if (ended && before.status !== game.status) connect4Sounds.play('close', { key: game.gameId });\n}\n\n// Sounds derive from store transitions, so WebSocket replays already rejected\n// by the store (seen event_id) never reach here; keys guard the rest.\n// Idempotent: repeated calls return the same stop(), which unsubscribes and\n// clears pending timers.\n/** True when startConnect4Sounds() is active (someone else owns its stop()). */\nexport const isConnect4SoundsRunning = () => stopSounds != null;\n\nexport function startConnect4Sounds() {\n if (stopSounds) return stopSounds;\n connect4Sounds.start();\n const unsubscribe = useChatGames.subscribe((state, previous) => {\n if (state.games === previous.games) return;\n Object.entries(state.games).forEach(([key, game]) => {\n const before = previous.games[key];\n // Other games (e.g. Battleship) have their own cues.\n if (game.game && game.game !== 'connect4') return;\n if (game !== before && game.ownId != null) onGameChange(game, before);\n });\n });\n const stop = () => {\n if (stopSounds !== stop) return;\n stopSounds = null;\n unsubscribe();\n pendingDrops.forEach(({ timer }) => clearTimeout(timer));\n pendingDrops.clear();\n followUps.forEach(clearTimeout);\n followUps.clear();\n };\n stopSounds = stop;\n return stop;\n}\n"],"mappings":"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"}
|