@minicode-dev/chat 0.1.5 → 0.1.7

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 (53) hide show
  1. package/README.md +6 -2
  2. package/assets/sounds/tanks/big_explosion.mp3 +0 -0
  3. package/assets/sounds/tanks/big_explosion.ogg +0 -0
  4. package/assets/sounds/tanks/cannon_fire.mp3 +0 -0
  5. package/assets/sounds/tanks/cannon_fire.ogg +0 -0
  6. package/assets/sounds/tanks/defeat.mp3 +0 -0
  7. package/assets/sounds/tanks/defeat.ogg +0 -0
  8. package/assets/sounds/tanks/engine_loop.mp3 +0 -0
  9. package/assets/sounds/tanks/engine_loop.ogg +0 -0
  10. package/assets/sounds/tanks/engine_start.mp3 +0 -0
  11. package/assets/sounds/tanks/engine_start.ogg +0 -0
  12. package/assets/sounds/tanks/projectile_whistle.mp3 +0 -0
  13. package/assets/sounds/tanks/projectile_whistle.ogg +0 -0
  14. package/assets/sounds/tanks/tank_destroyed.mp3 +0 -0
  15. package/assets/sounds/tanks/tank_destroyed.ogg +0 -0
  16. package/assets/sounds/tanks/tank_hit.mp3 +0 -0
  17. package/assets/sounds/tanks/tank_hit.ogg +0 -0
  18. package/assets/sounds/tanks/terrain_impact.mp3 +0 -0
  19. package/assets/sounds/tanks/terrain_impact.ogg +0 -0
  20. package/assets/sounds/tanks/tracks_loop.mp3 +0 -0
  21. package/assets/sounds/tanks/tracks_loop.ogg +0 -0
  22. package/assets/sounds/tanks/turret_loop.mp3 +0 -0
  23. package/assets/sounds/tanks/turret_loop.ogg +0 -0
  24. package/assets/sounds/tanks/victory.mp3 +0 -0
  25. package/assets/sounds/tanks/victory.ogg +0 -0
  26. package/dist/chunks/{BattleshipGameCard-CW_A8Nim.js → BattleshipGameCard-CAWHxqJE.js} +307 -306
  27. package/dist/chunks/{BattleshipGameCard-CW_A8Nim.js.map → BattleshipGameCard-CAWHxqJE.js.map} +1 -1
  28. package/dist/chunks/{BattleshipPrototype-Duz-S1JE.js → BattleshipPrototype-CmQd5AUO.js} +55 -54
  29. package/dist/chunks/{BattleshipPrototype-Duz-S1JE.js.map → BattleshipPrototype-CmQd5AUO.js.map} +1 -1
  30. package/dist/chunks/ChatContext-CUiEAMKK.js +90 -0
  31. package/dist/chunks/ChatContext-CUiEAMKK.js.map +1 -0
  32. package/dist/chunks/TanksGameCard-BEnXTz_J.js +1705 -0
  33. package/dist/chunks/TanksGameCard-BEnXTz_J.js.map +1 -0
  34. package/dist/chunks/{battleship-DXKuIZ1I.js → board-DXKuIZ1I.js} +1 -1
  35. package/dist/chunks/board-DXKuIZ1I.js.map +1 -0
  36. package/dist/chunks/connect4Sounds-BDXQAvU1.js +3199 -0
  37. package/dist/chunks/connect4Sounds-BDXQAvU1.js.map +1 -0
  38. package/dist/chunks/core-geo-UJY-tH9L.js +361 -0
  39. package/dist/chunks/core-geo-UJY-tH9L.js.map +1 -0
  40. package/dist/chunks/createBattleScene-L4ZRWDED.js +1265 -0
  41. package/dist/chunks/createBattleScene-L4ZRWDED.js.map +1 -0
  42. package/dist/chunks/createTankScene-BuliAxwR.js +2150 -0
  43. package/dist/chunks/createTankScene-BuliAxwR.js.map +1 -0
  44. package/dist/index.js +3677 -3644
  45. package/dist/index.js.map +1 -1
  46. package/dist/styles.css +1 -1
  47. package/index.d.ts +4 -0
  48. package/package.json +1 -1
  49. package/dist/chunks/battleship-DXKuIZ1I.js.map +0 -1
  50. package/dist/chunks/connect4Sounds-Do3nvVFx.js +0 -1847
  51. package/dist/chunks/connect4Sounds-Do3nvVFx.js.map +0 -1
  52. package/dist/chunks/createBattleScene-HIAvjixn.js +0 -1621
  53. package/dist/chunks/createBattleScene-HIAvjixn.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"BattleshipPrototype-Duz-S1JE.js","names":["createBoard","isShipSunk","fireShot","placeShip","FLEET","hasWebGL","ROWS","SIZE","QUALITY_ORDER","QUALITY_LABEL","cellLabel","cell","sameCell","a","b","isCell","SHIP_NAMES","DOUBLE_SELECT_MS","randomFleet","board","ship","attempt","placed","targetView","pendingShipId","prefersReducedMotion","autoQuality","coarse","weakCpu","BattleshipPrototype","stageRef","useRef","sceneRef","toastTimer","hintId","useId","status","setStatus","useState","errorMessage","setErrorMessage","selected","setSelected","hovered","setHovered","radar","setRadar","quality","setQuality","stats","setStats","result","setResult","toastVisible","setToastVisible","boardRef","busyRef","lastSelectRef","fireRef","ready","useEffect","container","disposed","scene","createBattleScene","next","now","last","error","showResult","useCallback","label","text","extra","kind","fire","target","shot","prevShots","prev","select","toggleRadar","cycleQuality","resetGame","handleStageKeyDown","event","moves","dr","dc","base","handleRootKeyDown","onClose","accuracy","qualityLabel","compact","index","Waves","TriangleAlert","X","Crosshair","Radar","LocateFixed","Gauge","RotateCcw"],"sources":["../../src/games/battleship/BattleshipPrototype.jsx"],"sourcesContent":["/* eslint-disable react/prop-types */\nimport { useCallback, useEffect, useId, useRef, useState } from 'react';\nimport { Crosshair, Gauge, LocateFixed, Radar, RotateCcw, TriangleAlert, Waves, X } from 'lucide-react';\nimport { FLEET, createBoard, fireShot, isShipSunk, placeShip } from './logic';\nimport { hasWebGL } from './webglSupport';\nimport './battleship.css';\n\nconst ROWS = 'ABCDEFGHIJ';\nconst SIZE = 10;\nconst QUALITY_ORDER = ['auto', 'high', 'low'];\nconst QUALITY_LABEL = { auto: 'Auto', high: 'Alta', low: 'Baja' };\n\nconst cellLabel = (cell) => (cell ? `${ROWS[cell.row]}${cell.col + 1}` : '—');\nconst sameCell = (a, b) => a === b || (a && b && a.row === b.row && a.col === b.col);\nconst isCell = (cell) => cell && Number.isInteger(cell.row) && Number.isInteger(cell.col);\nconst SHIP_NAMES = { carrier: 'portaaviones', battleship: 'acorazado', cruiser: 'crucero', submarine: 'submarino', destroyer: 'destructor' };\nconst DOUBLE_SELECT_MS = 400;\n\n// Local single-player demo: a random hidden enemy fleet resolved with the shared rules.\nfunction randomFleet() {\n let board = createBoard();\n for (const ship of FLEET) {\n for (let attempt = 0; attempt < 500; attempt += 1) {\n const placed = placeShip(board, {\n shipId: ship.id, orientation: Math.random() < 0.5 ? 'horizontal' : 'vertical',\n row: Math.floor(Math.random() * SIZE), col: Math.floor(Math.random() * SIZE),\n });\n if (placed.ok) { board = placed.board; break; }\n }\n }\n return board;\n}\n\n// `pendingShipId` keeps a just-sunk ship visible but afloat so the scene can sink it on impact.\nfunction targetView(board, interactive, pendingShipId = null) {\n return {\n mode: 'target',\n ships: board.ships\n .filter((ship) => ship.id === pendingShipId || isShipSunk(board, ship.id))\n .map((ship) => ({ ...ship, sunk: ship.id !== pendingShipId && isShipSunk(board, ship.id) })),\n shots: board.shots,\n preview: null,\n interactive,\n };\n}\n\nfunction prefersReducedMotion() {\n return typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n}\n\n// \"Auto\" has no scene API of its own once running, so resolve it to a concrete level.\nfunction autoQuality() {\n const coarse = window.matchMedia?.('(pointer: coarse)').matches;\n const weakCpu = (navigator.hardwareConcurrency || 8) <= 4;\n return coarse || weakCpu ? 'low' : 'high';\n}\n\nexport default function BattleshipPrototype({ onClose, compact = false }) {\n const stageRef = useRef(null);\n const sceneRef = useRef(null);\n const toastTimer = useRef(null);\n const hintId = useId();\n const [status, setStatus] = useState('loading');\n const [errorMessage, setErrorMessage] = useState('');\n const [selected, setSelected] = useState(null);\n const [hovered, setHovered] = useState(null);\n const [radar, setRadar] = useState(false);\n const [quality, setQuality] = useState('auto');\n const [stats, setStats] = useState({ shots: 0, hits: 0 });\n const [result, setResult] = useState(null);\n const [toastVisible, setToastVisible] = useState(false);\n const boardRef = useRef(null);\n const busyRef = useRef(false);\n const lastSelectRef = useRef({ cell: null, time: 0 });\n const fireRef = useRef(null);\n const ready = status === 'ready';\n if (!boardRef.current) boardRef.current = randomFleet();\n\n useEffect(() => {\n const container = stageRef.current;\n if (!container) return undefined;\n if (!hasWebGL()) {\n setStatus('error');\n setErrorMessage('Tu navegador o dispositivo no tiene WebGL disponible, necesario para la vista 3D.');\n return undefined;\n }\n let disposed = false;\n let scene = null;\n import('./scene/createBattleScene.js')\n .then(({ createBattleScene }) => {\n // StrictMode mounts twice: a late resolve for an unmounted instance must not create a scene.\n if (disposed) return;\n scene = createBattleScene(container, {\n quality: 'auto',\n reducedMotion: prefersReducedMotion(),\n onCellHover: (cell) => { if (!disposed) setHovered(isCell(cell) ? { row: cell.row, col: cell.col } : null); },\n onCellSelect: (cell) => {\n if (disposed || !isCell(cell)) return;\n const next = { row: cell.row, col: cell.col };\n const now = Date.now();\n const last = lastSelectRef.current;\n lastSelectRef.current = { cell: next, time: now };\n if (sameCell(last.cell, next) && now - last.time < DOUBLE_SELECT_MS) { fireRef.current?.(next); return; }\n setSelected(next);\n scene?.selectCell(next);\n },\n });\n sceneRef.current = scene;\n scene.setView(targetView(boardRef.current, true));\n setStatus('ready');\n })\n .catch((error) => {\n if (disposed) return;\n console.error('[battleship] No se pudo iniciar la escena', error);\n setStatus('error');\n setErrorMessage('No se pudo iniciar la escena 3D. Inténtalo de nuevo más tarde.');\n });\n return () => {\n disposed = true;\n sceneRef.current = null;\n try { scene?.dispose(); } catch (error) { console.error('[battleship] Error al liberar la escena', error); }\n };\n }, []);\n\n useEffect(() => () => clearTimeout(toastTimer.current), []);\n\n const showResult = useCallback((kind, cell, extra) => {\n const label = cellLabel(cell);\n const text = extra || (kind === 'hit' ? `¡Impacto en ${label}!` : kind === 'miss' ? `Agua en ${label}` : `Ya disparaste en ${label}`);\n setResult({ kind, text, id: Date.now() });\n setToastVisible(true);\n clearTimeout(toastTimer.current);\n toastTimer.current = setTimeout(() => setToastVisible(false), 2800);\n }, []);\n\n const fire = useCallback((target = selected) => {\n const scene = sceneRef.current;\n if (!scene || !isCell(target) || busyRef.current) return;\n const shot = fireShot(boardRef.current, target);\n if (!shot.ok) { showResult(shot.reason === 'repeat_shot' ? 'repeat' : 'miss', target, shot.reason === 'battle_finished' ? 'Flota enemiga hundida · reinicia para jugar otra vez' : undefined); return; }\n const prevShots = boardRef.current.shots;\n boardRef.current = shot.board;\n busyRef.current = true;\n setSelected(null);\n scene.selectCell(null);\n setStats((prev) => ({ shots: prev.shots + 1, hits: prev.hits + (shot.result === 'hit' ? 1 : 0) }));\n const text = shot.defeated ? '¡Flota enemiga hundida!' : shot.sunk ? `¡Hundiste el ${SHIP_NAMES[shot.shipId] || 'barco'}!` : undefined;\n // Pre-impact view: newly sunk ship afloat and the new shot not yet marked.\n if (shot.sunk) scene.setView({ ...targetView(shot.board, false, shot.shipId), shots: prevShots });\n Promise.resolve(scene.playShot({ row: target.row, col: target.col, result: shot.result, sunk: shot.sunk, incoming: false }))\n .catch(() => {})\n .then(() => {\n busyRef.current = false;\n if (sceneRef.current !== scene) return;\n scene.setView(targetView(boardRef.current, !shot.defeated));\n showResult(shot.result, target, text);\n });\n }, [selected, showResult]);\n fireRef.current = fire;\n\n const select = useCallback((cell) => {\n setSelected(cell);\n sceneRef.current?.selectCell(cell);\n }, []);\n\n const toggleRadar = () => {\n const next = !radar;\n setRadar(next);\n sceneRef.current?.setRadarVisible(next);\n };\n\n const cycleQuality = () => {\n const next = QUALITY_ORDER[(QUALITY_ORDER.indexOf(quality) + 1) % QUALITY_ORDER.length];\n setQuality(next);\n sceneRef.current?.setQuality(next === 'auto' ? autoQuality() : next);\n };\n\n const resetGame = () => {\n boardRef.current = randomFleet();\n busyRef.current = false;\n sceneRef.current?.setView(targetView(boardRef.current, true));\n sceneRef.current?.selectCell(null);\n setSelected(null);\n setHovered(null);\n setStats({ shots: 0, hits: 0 });\n setResult(null);\n setToastVisible(false);\n stageRef.current?.focus();\n };\n\n const handleStageKeyDown = (event) => {\n if (!ready || event.target !== event.currentTarget || event.altKey || event.ctrlKey || event.metaKey) return;\n const moves = { ArrowUp: [-1, 0], ArrowDown: [1, 0], ArrowLeft: [0, -1], ArrowRight: [0, 1] };\n if (moves[event.key]) {\n event.preventDefault();\n const [dr, dc] = moves[event.key];\n const base = selected || hovered;\n const next = base\n ? { row: Math.min(SIZE - 1, Math.max(0, base.row + dr)), col: Math.min(SIZE - 1, Math.max(0, base.col + dc)) }\n : { row: 0, col: 0 };\n if (!sameCell(next, selected)) select(next);\n } else if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n fire();\n } else if (event.key === 'r' || event.key === 'R') {\n event.preventDefault();\n sceneRef.current?.resetCamera();\n }\n };\n\n const handleRootKeyDown = (event) => {\n if (event.key === 'Escape' && !event.defaultPrevented) {\n event.preventDefault();\n event.stopPropagation();\n onClose?.();\n }\n };\n\n const accuracy = stats.shots ? Math.round((stats.hits / stats.shots) * 100) : 0;\n const qualityLabel = QUALITY_LABEL[quality];\n\n return (\n <section className={`bs-proto${compact ? ' bs-proto--compact' : ''}`} aria-label=\"Batalla Naval, vista previa 3D sin rival\" onKeyDown={handleRootKeyDown}>\n <div\n ref={stageRef}\n className=\"bs-proto__stage\"\n tabIndex={0}\n role=\"application\"\n aria-label=\"Tablero 3D de Batalla Naval. Flechas para mover la selección, Enter o Espacio para disparar, R para centrar la cámara, Escape para cerrar.\"\n aria-describedby={hintId}\n aria-busy={status === 'loading'}\n onKeyDown={handleStageKeyDown}\n />\n\n {status === 'loading' && (\n <div className=\"bs-proto__state\" role=\"status\">\n <div className=\"bs-proto__skeleton\" aria-hidden=\"true\">\n {Array.from({ length: 25 }, (_, index) => <span key={index} />)}\n </div>\n <p><Waves size={16} aria-hidden=\"true\" /> Preparando el océano…</p>\n </div>\n )}\n {status === 'error' && (\n <div className=\"bs-proto__state bs-proto__state--error\" role=\"alert\">\n <TriangleAlert size={28} aria-hidden=\"true\" />\n <strong>Vista 3D no disponible</strong>\n <p>{errorMessage}</p>\n <button type=\"button\" className=\"bs-proto__btn\" onClick={onClose}>Volver al chat</button>\n </div>\n )}\n\n <header className=\"bs-proto__top\">\n <div className=\"bs-proto__panel bs-proto__brand\">\n <span className=\"bs-proto__title\">Batalla Naval <span className=\"bs-proto__title-sub\">· Sin rival</span></span>\n <span className=\"bs-proto__badge\">Vista previa 3D</span>\n </div>\n {ready && (\n <div className=\"bs-proto__panel bs-proto__info\">\n <dl className=\"bs-proto__cells\">\n <div><dt>Objetivo</dt><dd className=\"bs-proto__cell-selected\">{cellLabel(selected)}</dd></div>\n <div className=\"bs-proto__cells-hover\"><dt>Cursor</dt><dd>{cellLabel(hovered)}</dd></div>\n </dl>\n <dl className=\"bs-proto__stats\">\n <div><dt>Disparos</dt><dd>{stats.shots}</dd></div>\n <div><dt>Impactos</dt><dd>{stats.hits}</dd></div>\n <div><dt>Precisión</dt><dd>{accuracy}%</dd></div>\n </dl>\n </div>\n )}\n <button type=\"button\" className=\"bs-proto__icon-btn bs-proto__close\" onClick={onClose} aria-label=\"Cerrar Batalla Naval\" title=\"Cerrar (Esc)\">\n <X size={18} aria-hidden=\"true\" />\n </button>\n </header>\n\n <div className=\"bs-proto__live\" aria-live=\"polite\" aria-atomic=\"true\">\n {result && (\n <span key={result.id} className={`bs-proto__toast bs-proto__toast--${result.kind}${toastVisible ? ' is-visible' : ''}`}>\n {result.text}\n </span>\n )}\n </div>\n <span className=\"bs-proto__sr\" aria-live=\"polite\">{selected ? `Casilla seleccionada ${cellLabel(selected)}` : ''}</span>\n\n {ready && (\n <footer className=\"bs-proto__bottom\">\n <p id={hintId} className=\"bs-proto__hint\">Arrastra para rotar · Rueda/pellizco para zoom · Clic para apuntar · doble clic para disparar</p>\n <div className=\"bs-proto__panel bs-proto__actions\" role=\"toolbar\" aria-label=\"Acciones de Batalla Naval\">\n <button type=\"button\" className=\"bs-proto__btn bs-proto__btn--fire\" onClick={() => fire()} disabled={!selected}\n title={selected ? `Disparar en ${cellLabel(selected)} (Enter)` : 'Selecciona una casilla para disparar'}>\n <Crosshair size={18} aria-hidden=\"true\" /><span>Disparar</span>\n </button>\n <button type=\"button\" className=\"bs-proto__btn\" onClick={toggleRadar} aria-pressed={radar} title=\"Mostrar u ocultar radar\">\n <Radar size={18} aria-hidden=\"true\" /><span className=\"bs-proto__btn-label\">Radar</span>\n </button>\n <button type=\"button\" className=\"bs-proto__btn\" onClick={() => sceneRef.current?.resetCamera()} title=\"Centrar cámara (R)\">\n <LocateFixed size={18} aria-hidden=\"true\" /><span className=\"bs-proto__btn-label\">Centrar cámara</span>\n </button>\n <button type=\"button\" className=\"bs-proto__btn\" onClick={cycleQuality} aria-label={`Calidad: ${qualityLabel}. Cambiar calidad`} title=\"Cambiar calidad de render\">\n <Gauge size={18} aria-hidden=\"true\" /><span className=\"bs-proto__btn-label\">Calidad: {qualityLabel}</span>\n </button>\n <button type=\"button\" className=\"bs-proto__btn\" onClick={resetGame} title=\"Reiniciar tablero\">\n <RotateCcw size={18} aria-hidden=\"true\" /><span className=\"bs-proto__btn-label\">Reiniciar</span>\n </button>\n </div>\n </footer>\n )}\n {!ready && <p id={hintId} className=\"bs-proto__sr\">Arrastra para rotar · Rueda/pellizco para zoom · Clic para seleccionar</p>}\n </section>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BattleshipPrototype-CmQd5AUO.js","names":["createBoard","isShipSunk","fireShot","placeShip","FLEET","hasWebGL","ROWS","SIZE","QUALITY_ORDER","QUALITY_LABEL","cellLabel","cell","sameCell","a","b","isCell","SHIP_NAMES","DOUBLE_SELECT_MS","randomFleet","board","ship","attempt","placed","targetView","pendingShipId","prefersReducedMotion","autoQuality","coarse","weakCpu","BattleshipPrototype","stageRef","useRef","sceneRef","toastTimer","hintId","useId","status","setStatus","useState","errorMessage","setErrorMessage","selected","setSelected","hovered","setHovered","radar","setRadar","quality","setQuality","stats","setStats","result","setResult","toastVisible","setToastVisible","boardRef","busyRef","lastSelectRef","fireRef","ready","useEffect","container","disposed","scene","createBattleScene","next","now","last","error","showResult","useCallback","label","text","extra","kind","fire","target","shot","prevShots","prev","select","toggleRadar","cycleQuality","resetGame","handleStageKeyDown","event","moves","dr","dc","base","handleRootKeyDown","onClose","accuracy","qualityLabel","compact","index","Waves","TriangleAlert","X","Crosshair","Radar","LocateFixed","Gauge","RotateCcw"],"sources":["../../src/games/battleship/BattleshipPrototype.jsx"],"sourcesContent":["/* eslint-disable react/prop-types */\nimport { useCallback, useEffect, useId, useRef, useState } from 'react';\nimport { Crosshair, Gauge, LocateFixed, Radar, RotateCcw, TriangleAlert, Waves, X } from 'lucide-react';\nimport { FLEET, createBoard, fireShot, isShipSunk, placeShip } from './logic';\nimport { hasWebGL } from './webglSupport';\nimport './battleship.css';\n\nconst ROWS = 'ABCDEFGHIJ';\nconst SIZE = 10;\nconst QUALITY_ORDER = ['auto', 'high', 'low'];\nconst QUALITY_LABEL = { auto: 'Auto', high: 'Alta', low: 'Baja' };\n\nconst cellLabel = (cell) => (cell ? `${ROWS[cell.row]}${cell.col + 1}` : '—');\nconst sameCell = (a, b) => a === b || (a && b && a.row === b.row && a.col === b.col);\nconst isCell = (cell) => cell && Number.isInteger(cell.row) && Number.isInteger(cell.col);\nconst SHIP_NAMES = { carrier: 'portaaviones', battleship: 'acorazado', cruiser: 'crucero', submarine: 'submarino', destroyer: 'destructor' };\nconst DOUBLE_SELECT_MS = 400;\n\n// Local single-player demo: a random hidden enemy fleet resolved with the shared rules.\nfunction randomFleet() {\n let board = createBoard();\n for (const ship of FLEET) {\n for (let attempt = 0; attempt < 500; attempt += 1) {\n const placed = placeShip(board, {\n shipId: ship.id, orientation: Math.random() < 0.5 ? 'horizontal' : 'vertical',\n row: Math.floor(Math.random() * SIZE), col: Math.floor(Math.random() * SIZE),\n });\n if (placed.ok) { board = placed.board; break; }\n }\n }\n return board;\n}\n\n// `pendingShipId` keeps a just-sunk ship visible but afloat so the scene can sink it on impact.\nfunction targetView(board, interactive, pendingShipId = null) {\n return {\n mode: 'target',\n ships: board.ships\n .filter((ship) => ship.id === pendingShipId || isShipSunk(board, ship.id))\n .map((ship) => ({ ...ship, sunk: ship.id !== pendingShipId && isShipSunk(board, ship.id) })),\n shots: board.shots,\n preview: null,\n interactive,\n };\n}\n\nfunction prefersReducedMotion() {\n return typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n}\n\n// \"Auto\" has no scene API of its own once running, so resolve it to a concrete level.\nfunction autoQuality() {\n const coarse = window.matchMedia?.('(pointer: coarse)').matches;\n const weakCpu = (navigator.hardwareConcurrency || 8) <= 4;\n return coarse || weakCpu ? 'low' : 'high';\n}\n\nexport default function BattleshipPrototype({ onClose, compact = false }) {\n const stageRef = useRef(null);\n const sceneRef = useRef(null);\n const toastTimer = useRef(null);\n const hintId = useId();\n const [status, setStatus] = useState('loading');\n const [errorMessage, setErrorMessage] = useState('');\n const [selected, setSelected] = useState(null);\n const [hovered, setHovered] = useState(null);\n const [radar, setRadar] = useState(false);\n const [quality, setQuality] = useState('auto');\n const [stats, setStats] = useState({ shots: 0, hits: 0 });\n const [result, setResult] = useState(null);\n const [toastVisible, setToastVisible] = useState(false);\n const boardRef = useRef(null);\n const busyRef = useRef(false);\n const lastSelectRef = useRef({ cell: null, time: 0 });\n const fireRef = useRef(null);\n const ready = status === 'ready';\n if (!boardRef.current) boardRef.current = randomFleet();\n\n useEffect(() => {\n const container = stageRef.current;\n if (!container) return undefined;\n if (!hasWebGL()) {\n setStatus('error');\n setErrorMessage('Tu navegador o dispositivo no tiene WebGL disponible, necesario para la vista 3D.');\n return undefined;\n }\n let disposed = false;\n let scene = null;\n import('./scene/createBattleScene.js')\n .then(({ createBattleScene }) => {\n // StrictMode mounts twice: a late resolve for an unmounted instance must not create a scene.\n if (disposed) return;\n scene = createBattleScene(container, {\n quality: 'auto',\n reducedMotion: prefersReducedMotion(),\n onCellHover: (cell) => { if (!disposed) setHovered(isCell(cell) ? { row: cell.row, col: cell.col } : null); },\n onCellSelect: (cell) => {\n if (disposed || !isCell(cell)) return;\n const next = { row: cell.row, col: cell.col };\n const now = Date.now();\n const last = lastSelectRef.current;\n lastSelectRef.current = { cell: next, time: now };\n if (sameCell(last.cell, next) && now - last.time < DOUBLE_SELECT_MS) { fireRef.current?.(next); return; }\n setSelected(next);\n scene?.selectCell(next);\n },\n });\n sceneRef.current = scene;\n scene.setView(targetView(boardRef.current, true));\n setStatus('ready');\n })\n .catch((error) => {\n if (disposed) return;\n console.error('[battleship] No se pudo iniciar la escena', error);\n setStatus('error');\n setErrorMessage('No se pudo iniciar la escena 3D. Inténtalo de nuevo más tarde.');\n });\n return () => {\n disposed = true;\n sceneRef.current = null;\n try { scene?.dispose(); } catch (error) { console.error('[battleship] Error al liberar la escena', error); }\n };\n }, []);\n\n useEffect(() => () => clearTimeout(toastTimer.current), []);\n\n const showResult = useCallback((kind, cell, extra) => {\n const label = cellLabel(cell);\n const text = extra || (kind === 'hit' ? `¡Impacto en ${label}!` : kind === 'miss' ? `Agua en ${label}` : `Ya disparaste en ${label}`);\n setResult({ kind, text, id: Date.now() });\n setToastVisible(true);\n clearTimeout(toastTimer.current);\n toastTimer.current = setTimeout(() => setToastVisible(false), 2800);\n }, []);\n\n const fire = useCallback((target = selected) => {\n const scene = sceneRef.current;\n if (!scene || !isCell(target) || busyRef.current) return;\n const shot = fireShot(boardRef.current, target);\n if (!shot.ok) { showResult(shot.reason === 'repeat_shot' ? 'repeat' : 'miss', target, shot.reason === 'battle_finished' ? 'Flota enemiga hundida · reinicia para jugar otra vez' : undefined); return; }\n const prevShots = boardRef.current.shots;\n boardRef.current = shot.board;\n busyRef.current = true;\n setSelected(null);\n scene.selectCell(null);\n setStats((prev) => ({ shots: prev.shots + 1, hits: prev.hits + (shot.result === 'hit' ? 1 : 0) }));\n const text = shot.defeated ? '¡Flota enemiga hundida!' : shot.sunk ? `¡Hundiste el ${SHIP_NAMES[shot.shipId] || 'barco'}!` : undefined;\n // Pre-impact view: newly sunk ship afloat and the new shot not yet marked.\n if (shot.sunk) scene.setView({ ...targetView(shot.board, false, shot.shipId), shots: prevShots });\n Promise.resolve(scene.playShot({ row: target.row, col: target.col, result: shot.result, sunk: shot.sunk, incoming: false }))\n .catch(() => {})\n .then(() => {\n busyRef.current = false;\n if (sceneRef.current !== scene) return;\n scene.setView(targetView(boardRef.current, !shot.defeated));\n showResult(shot.result, target, text);\n });\n }, [selected, showResult]);\n fireRef.current = fire;\n\n const select = useCallback((cell) => {\n setSelected(cell);\n sceneRef.current?.selectCell(cell);\n }, []);\n\n const toggleRadar = () => {\n const next = !radar;\n setRadar(next);\n sceneRef.current?.setRadarVisible(next);\n };\n\n const cycleQuality = () => {\n const next = QUALITY_ORDER[(QUALITY_ORDER.indexOf(quality) + 1) % QUALITY_ORDER.length];\n setQuality(next);\n sceneRef.current?.setQuality(next === 'auto' ? autoQuality() : next);\n };\n\n const resetGame = () => {\n boardRef.current = randomFleet();\n busyRef.current = false;\n sceneRef.current?.setView(targetView(boardRef.current, true));\n sceneRef.current?.selectCell(null);\n setSelected(null);\n setHovered(null);\n setStats({ shots: 0, hits: 0 });\n setResult(null);\n setToastVisible(false);\n stageRef.current?.focus();\n };\n\n const handleStageKeyDown = (event) => {\n if (!ready || event.target !== event.currentTarget || event.altKey || event.ctrlKey || event.metaKey) return;\n const moves = { ArrowUp: [-1, 0], ArrowDown: [1, 0], ArrowLeft: [0, -1], ArrowRight: [0, 1] };\n if (moves[event.key]) {\n event.preventDefault();\n const [dr, dc] = moves[event.key];\n const base = selected || hovered;\n const next = base\n ? { row: Math.min(SIZE - 1, Math.max(0, base.row + dr)), col: Math.min(SIZE - 1, Math.max(0, base.col + dc)) }\n : { row: 0, col: 0 };\n if (!sameCell(next, selected)) select(next);\n } else if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n fire();\n } else if (event.key === 'r' || event.key === 'R') {\n event.preventDefault();\n sceneRef.current?.resetCamera();\n }\n };\n\n const handleRootKeyDown = (event) => {\n if (event.key === 'Escape' && !event.defaultPrevented) {\n event.preventDefault();\n event.stopPropagation();\n onClose?.();\n }\n };\n\n const accuracy = stats.shots ? Math.round((stats.hits / stats.shots) * 100) : 0;\n const qualityLabel = QUALITY_LABEL[quality];\n\n return (\n <section className={`bs-proto${compact ? ' bs-proto--compact' : ''}`} aria-label=\"Batalla Naval, vista previa 3D sin rival\" onKeyDown={handleRootKeyDown}>\n <div\n ref={stageRef}\n className=\"bs-proto__stage\"\n tabIndex={0}\n role=\"application\"\n aria-label=\"Tablero 3D de Batalla Naval. Flechas para mover la selección, Enter o Espacio para disparar, R para centrar la cámara, Escape para cerrar.\"\n aria-describedby={hintId}\n aria-busy={status === 'loading'}\n onKeyDown={handleStageKeyDown}\n />\n\n {status === 'loading' && (\n <div className=\"bs-proto__state\" role=\"status\">\n <div className=\"bs-proto__skeleton\" aria-hidden=\"true\">\n {Array.from({ length: 25 }, (_, index) => <span key={index} />)}\n </div>\n <p><Waves size={16} aria-hidden=\"true\" /> Preparando el océano…</p>\n </div>\n )}\n {status === 'error' && (\n <div className=\"bs-proto__state bs-proto__state--error\" role=\"alert\">\n <TriangleAlert size={28} aria-hidden=\"true\" />\n <strong>Vista 3D no disponible</strong>\n <p>{errorMessage}</p>\n <button type=\"button\" className=\"bs-proto__btn\" onClick={onClose}>Volver al chat</button>\n </div>\n )}\n\n <header className=\"bs-proto__top\">\n <div className=\"bs-proto__panel bs-proto__brand\">\n <span className=\"bs-proto__title\">Batalla Naval <span className=\"bs-proto__title-sub\">· Sin rival</span></span>\n <span className=\"bs-proto__badge\">Vista previa 3D</span>\n </div>\n {ready && (\n <div className=\"bs-proto__panel bs-proto__info\">\n <dl className=\"bs-proto__cells\">\n <div><dt>Objetivo</dt><dd className=\"bs-proto__cell-selected\">{cellLabel(selected)}</dd></div>\n <div className=\"bs-proto__cells-hover\"><dt>Cursor</dt><dd>{cellLabel(hovered)}</dd></div>\n </dl>\n <dl className=\"bs-proto__stats\">\n <div><dt>Disparos</dt><dd>{stats.shots}</dd></div>\n <div><dt>Impactos</dt><dd>{stats.hits}</dd></div>\n <div><dt>Precisión</dt><dd>{accuracy}%</dd></div>\n </dl>\n </div>\n )}\n <button type=\"button\" className=\"bs-proto__icon-btn bs-proto__close\" onClick={onClose} aria-label=\"Cerrar Batalla Naval\" title=\"Cerrar (Esc)\">\n <X size={18} aria-hidden=\"true\" />\n </button>\n </header>\n\n <div className=\"bs-proto__live\" aria-live=\"polite\" aria-atomic=\"true\">\n {result && (\n <span key={result.id} className={`bs-proto__toast bs-proto__toast--${result.kind}${toastVisible ? ' is-visible' : ''}`}>\n {result.text}\n </span>\n )}\n </div>\n <span className=\"bs-proto__sr\" aria-live=\"polite\">{selected ? `Casilla seleccionada ${cellLabel(selected)}` : ''}</span>\n\n {ready && (\n <footer className=\"bs-proto__bottom\">\n <p id={hintId} className=\"bs-proto__hint\">Arrastra para rotar · Rueda/pellizco para zoom · Clic para apuntar · doble clic para disparar</p>\n <div className=\"bs-proto__panel bs-proto__actions\" role=\"toolbar\" aria-label=\"Acciones de Batalla Naval\">\n <button type=\"button\" className=\"bs-proto__btn bs-proto__btn--fire\" onClick={() => fire()} disabled={!selected}\n title={selected ? `Disparar en ${cellLabel(selected)} (Enter)` : 'Selecciona una casilla para disparar'}>\n <Crosshair size={18} aria-hidden=\"true\" /><span>Disparar</span>\n </button>\n <button type=\"button\" className=\"bs-proto__btn\" onClick={toggleRadar} aria-pressed={radar} title=\"Mostrar u ocultar radar\">\n <Radar size={18} aria-hidden=\"true\" /><span className=\"bs-proto__btn-label\">Radar</span>\n </button>\n <button type=\"button\" className=\"bs-proto__btn\" onClick={() => sceneRef.current?.resetCamera()} title=\"Centrar cámara (R)\">\n <LocateFixed size={18} aria-hidden=\"true\" /><span className=\"bs-proto__btn-label\">Centrar cámara</span>\n </button>\n <button type=\"button\" className=\"bs-proto__btn\" onClick={cycleQuality} aria-label={`Calidad: ${qualityLabel}. Cambiar calidad`} title=\"Cambiar calidad de render\">\n <Gauge size={18} aria-hidden=\"true\" /><span className=\"bs-proto__btn-label\">Calidad: {qualityLabel}</span>\n </button>\n <button type=\"button\" className=\"bs-proto__btn\" onClick={resetGame} title=\"Reiniciar tablero\">\n <RotateCcw size={18} aria-hidden=\"true\" /><span className=\"bs-proto__btn-label\">Reiniciar</span>\n </button>\n </div>\n </footer>\n )}\n {!ready && <p id={hintId} className=\"bs-proto__sr\">Arrastra para rotar · Rueda/pellizco para zoom · Clic para seleccionar</p>}\n </section>\n );\n}\n"],"mappings":";;;;;;;AAOA,IAAMM,IAAO,cACPC,IAAO,IACPC,IAAgB;CAAC;CAAQ;CAAQ;AAAK,GACtCC,KAAgB;CAAE,MAAM;CAAQ,MAAM;CAAQ,KAAK;AAAO,GAE1DC,KAAa,MAAUC,IAAO,GAAGL,EAAKK,EAAK,OAAOA,EAAK,MAAM,MAAM,KACnEC,KAAY,GAAG,MAAMC,MAAMC,KAAMD,KAAKC,KAAKD,EAAE,QAAQC,EAAE,OAAOD,EAAE,QAAQC,EAAE,KAC1EC,KAAU,MAASJ,KAAQ,OAAO,UAAUA,EAAK,GAAG,KAAK,OAAO,UAAUA,EAAK,GAAG,GAClFK,IAAa;CAAE,SAAS;CAAgB,YAAY;CAAa,SAAS;CAAW,WAAW;CAAa,WAAW;AAAa,GACrIC,IAAmB;AAGzB,SAASC,IAAc;CACrB,IAAIC,IAAQnB,EAAY;CACxB,KAAK,IAAMoB,KAAQhB,GACjB,KAAK,IAAIiB,IAAU,GAAGA,IAAU,KAAK,KAAW,GAAG;EACjD,IAAMC,IAASnB,EAAUgB,GAAO;GAC9B,QAAQC,EAAK;GAAI,aAAa,KAAK,OAAO,IAAI,KAAM,eAAe;GACnE,KAAK,KAAK,MAAM,KAAK,OAAO,IAAIb,CAAI;GAAG,KAAK,KAAK,MAAM,KAAK,OAAO,IAAIA,CAAI;EAC7E,CAAC;EACD,IAAIe,EAAO,IAAI;GAAE,IAAQA,EAAO;GAAO;EAAO;CAChD;CAEF,OAAOH;AACT;AAGA,SAASI,EAAW,GAAO,GAAa,IAAgB,MAAM;CAC5D,OAAO;EACL,MAAM;EACN,OAAOJ,EAAM,MACV,QAAQ,MAASC,EAAK,OAAOI,KAAiBvB,EAAWkB,GAAOC,EAAK,EAAE,CAAC,CAAC,CACzE,KAAK,OAAU;GAAE,GAAGA;GAAM,MAAMA,EAAK,OAAOI,KAAiBvB,EAAWkB,GAAOC,EAAK,EAAE;EAAE,EAAE;EAC7F,OAAOD,EAAM;EACb,SAAS;EACT;CACF;AACF;AAEA,SAASM,IAAuB;CAC9B,OAAO,OAAO,SAAW,OAAe,OAAO,aAAa,kCAAkC,CAAC,CAAC;AAClG;AAGA,SAASC,IAAc;CACrB,IAAMC,IAAS,OAAO,aAAa,mBAAmB,CAAC,CAAC,SAClDC,KAAW,UAAU,uBAAuB,MAAM;CACxD,OAAOD,KAAUC,IAAU,QAAQ;AACrC;AAEA,SAAwBC,EAAoB,EAAE,YAAS,aAAU,MAAS;CACxE,IAAMC,IAAWC,EAAO,IAAI,GACtBC,IAAWD,EAAO,IAAI,GACtBE,IAAaF,EAAO,IAAI,GACxBG,IAASC,EAAM,GACf,CAACC,GAAQC,KAAaC,EAAS,SAAS,GACxC,CAACC,IAAcC,KAAmBF,EAAS,EAAE,GAC7C,CAACG,GAAUC,KAAeJ,EAAS,IAAI,GACvC,CAACK,GAASC,KAAcN,EAAS,IAAI,GACrC,CAACO,GAAOC,KAAYR,EAAS,EAAK,GAClC,CAACS,GAASC,KAAcV,EAAS,MAAM,GACvC,CAACW,GAAOC,KAAYZ,EAAS;EAAE,OAAO;EAAG,MAAM;CAAE,CAAC,GAClD,CAACa,GAAQC,KAAad,EAAS,IAAI,GACnC,CAACe,GAAcC,KAAmBhB,EAAS,EAAK,GAChDiB,IAAWxB,EAAO,IAAI,GACtByB,IAAUzB,EAAO,EAAK,GACtB0B,IAAgB1B,EAAO;EAAE,MAAM;EAAM,MAAM;CAAE,CAAC,GAC9C2B,IAAU3B,EAAO,IAAI,GACrB4B,IAAQvB,MAAW;CAiDzB,AAhDKmB,EAAS,YAAS,EAAS,UAAUrC,EAAY,IAEtD0C,QAAgB;EACd,IAAMC,IAAY/B,EAAS;EAC3B,IAAI,CAAC+B,GAAW;EAChB,IAAI,CAACxD,EAAS,GAAG;GAEf,AADAgC,EAAU,OAAO,GACjBG,EAAgB,mFAAmF;GACnG;EACF;EACA,IAAIsB,IAAW,IACXC,IAAQ;EA8BZ,OA7BA,OAAO,kCAA+B,CACnC,MAAM,EAAE,2BAAwB;GAE/B,AAAID,MACJ,IAAQE,EAAkBH,GAAW;IACnC,SAAS;IACT,eAAepC,EAAqB;IACpC,cAAc,MAAS;KAAE,AAAKqC,KAAUlB,EAAW7B,EAAOJ,CAAI,IAAI;MAAE,KAAKA,EAAK;MAAK,KAAKA,EAAK;KAAI,IAAI,IAAI;IAAG;IAC5G,eAAe,MAAS;KACtB,IAAImD,KAAY,CAAC/C,EAAOJ,CAAI,GAAG;KAC/B,IAAMsD,IAAO;MAAE,KAAKtD,EAAK;MAAK,KAAKA,EAAK;KAAI,GACtCuD,IAAM,KAAK,IAAI,GACfC,IAAOV,EAAc;KAE3B,AADA,EAAc,UAAU;MAAE,MAAMQ;MAAM,MAAMC;KAAI,GAC5CtD,EAASuD,EAAK,MAAMF,CAAI,KAAKC,IAAMC,EAAK,OAAOlD,IAAoByC,EAAQ,UAAUO,CAAI,KAC7FvB,EAAYuB,CAAI,GAChBF,GAAO,WAAWE,CAAI;IACxB;GACF,CAAC,GACD,EAAS,UAAUF,GACnBA,EAAM,QAAQxC,EAAWgC,EAAS,SAAS,EAAI,CAAC,GAChDlB,EAAU,OAAO;EACnB,CAAC,CAAC,CACD,OAAO,MAAU;GAChB,AAAIyB,MACJ,QAAQ,MAAM,6CAA6CM,CAAK,GAChE/B,EAAU,OAAO,GACjBG,EAAgB,gEAAgE;EAClF,CAAC,SACU;GAEX,AADA,IAAW,IACX,EAAS,UAAU;GACnB,IAAI;IAAE,GAAO,QAAQ;GAAG,SAAS4B,GAAO;IAAE,QAAQ,MAAM,2CAA2CA,CAAK;GAAG;EAC7G;CACF,GAAG,CAAC,CAAC,GAELR,cAAsB,aAAa3B,EAAW,OAAO,GAAG,CAAC,CAAC;CAE1D,IAAMoC,IAAaC,GAAa,GAAM,GAAM,MAAU;EACpD,IAAMC,IAAQ7D,EAAUC,CAAI,GACtB6D,IAAOC,MAAUC,MAAS,QAAQ,eAAeH,EAAM,KAAKG,MAAS,SAAS,WAAWH,MAAU,oBAAoBA;EAI7H,AAHAnB,EAAU;GAAE;GAAM;GAAM,IAAI,KAAK,IAAI;EAAE,CAAC,GACxCE,EAAgB,EAAI,GACpB,aAAarB,EAAW,OAAO,GAC/B,EAAW,UAAU,iBAAiBqB,EAAgB,EAAK,GAAG,IAAI;CACpE,GAAG,CAAC,CAAC,GAECqB,IAAOL,GAAa,IAAS7B,MAAa;EAC9C,IAAMsB,IAAQ/B,EAAS;EACvB,IAAI,CAAC+B,KAAS,CAAChD,EAAO6D,CAAM,KAAKpB,EAAQ,SAAS;EAClD,IAAMqB,IAAO3E,EAASqD,EAAS,SAASqB,CAAM;EAC9C,IAAI,CAACC,EAAK,IAAI;GAAE,EAAWA,EAAK,WAAW,gBAAgB,WAAW,QAAQD,GAAQC,EAAK,WAAW,oBAAoB,yDAAyD,KAAA,CAAS;GAAG;EAAQ;EACvM,IAAMC,IAAYvB,EAAS,QAAQ;EAKnC,AAJA,EAAS,UAAUsB,EAAK,OACxB,EAAQ,UAAU,IAClBnC,EAAY,IAAI,GAChBqB,EAAM,WAAW,IAAI,GACrBb,GAAU,OAAU;GAAE,OAAO6B,EAAK,QAAQ;GAAG,MAAMA,EAAK,OAAQ,IAAK,WAAW;EAAe,EAAE;EACjG,IAAMP,IAAOK,EAAK,WAAW,4BAA4BA,EAAK,OAAO,gBAAgB7D,EAAW6D,EAAK,WAAW,QAAQ,KAAK,KAAA;EAG7H,AADIA,EAAK,QAAMd,EAAM,QAAQ;GAAE,GAAGxC,EAAWsD,EAAK,OAAO,IAAOA,EAAK,MAAM;GAAG,OAAOC;EAAU,CAAC,GAChG,QAAQ,QAAQf,EAAM,SAAS;GAAE,KAAKa,EAAO;GAAK,KAAKA,EAAO;GAAK,QAAQC,EAAK;GAAQ,MAAMA,EAAK;GAAM,UAAU;EAAM,CAAC,CAAC,CAAC,CACzH,YAAY,CAAC,CAAC,CAAC,CACf,WAAW;GAEV,AADA,EAAQ,UAAU,IACd7C,EAAS,YAAY+B,MACzBA,EAAM,QAAQxC,EAAWgC,EAAS,SAAS,CAACsB,EAAK,QAAQ,CAAC,GAC1DR,EAAWQ,EAAK,QAAQD,GAAQJ,CAAI;EACtC,CAAC;CACL,GAAG,CAAC/B,GAAU4B,CAAU,CAAC;CACzB,EAAQ,UAAUM;CAElB,IAAMK,KAASV,GAAa,MAAS;EAEnC,AADA5B,EAAY/B,CAAI,GAChBqB,EAAS,SAAS,WAAWrB,CAAI;CACnC,GAAG,CAAC,CAAC,GAECsE,WAAoB;EACxB,IAAMhB,IAAO,CAACpB;EAEd,AADAC,EAASmB,CAAI,GACbjC,EAAS,SAAS,gBAAgBiC,CAAI;CACxC,GAEMiB,WAAqB;EACzB,IAAMjB,IAAOzD,GAAeA,EAAc,QAAQuC,CAAO,IAAI,KAAKvC,EAAc;EAEhF,AADAwC,EAAWiB,CAAI,GACfjC,EAAS,SAAS,WAAWiC,MAAS,SAASvC,EAAY,IAAIuC,CAAI;CACrE,GAEMkB,WAAkB;EAUtB,AATA,EAAS,UAAUjE,EAAY,GAC/B,EAAQ,UAAU,IAClBc,EAAS,SAAS,QAAQT,EAAWgC,EAAS,SAAS,EAAI,CAAC,GAC5DvB,EAAS,SAAS,WAAW,IAAI,GACjCU,EAAY,IAAI,GAChBE,EAAW,IAAI,GACfM,EAAS;GAAE,OAAO;GAAG,MAAM;EAAE,CAAC,GAC9BE,EAAU,IAAI,GACdE,EAAgB,EAAK,GACrBxB,EAAS,SAAS,MAAM;CAC1B,GAEMsD,KAAsB,MAAU;EACpC,IAAI,CAACzB,KAAS0B,EAAM,WAAWA,EAAM,iBAAiBA,EAAM,UAAUA,EAAM,WAAWA,EAAM,SAAS;EACtG,IAAMC,IAAQ;GAAE,SAAS,CAAC,IAAI,CAAC;GAAG,WAAW,CAAC,GAAG,CAAC;GAAG,WAAW,CAAC,GAAG,EAAE;GAAG,YAAY,CAAC,GAAG,CAAC;EAAE;EAC5F,IAAIA,EAAMD,EAAM,MAAM;GACpB,EAAM,eAAe;GACrB,IAAM,CAACE,GAAIC,KAAMF,EAAMD,EAAM,MACvBI,IAAOhD,KAAYE,GACnBsB,IAAOwB,IACT;IAAE,KAAK,KAAK,IAAI,GAAU,KAAK,IAAI,GAAGA,EAAK,MAAMF,CAAE,CAAC;IAAG,KAAK,KAAK,IAAI,GAAU,KAAK,IAAI,GAAGE,EAAK,MAAMD,CAAE,CAAC;GAAE,IAC3G;IAAE,KAAK;IAAG,KAAK;GAAE;GACrB,AAAK5E,EAASqD,GAAMxB,CAAQ,KAAGuC,GAAOf,CAAI;EAC5C,OAAO,AAAIoB,EAAM,QAAQ,WAAWA,EAAM,QAAQ,OAChDA,EAAM,eAAe,GACrBV,EAAK,MACIU,EAAM,QAAQ,OAAOA,EAAM,QAAQ,SAC5CA,EAAM,eAAe,GACrBrD,EAAS,SAAS,YAAY;CAElC,GAEM0D,MAAqB,MAAU;EACnC,AAAIL,EAAM,QAAQ,YAAY,CAACA,EAAM,qBACnCA,EAAM,eAAe,GACrBA,EAAM,gBAAgB,GACtBM,IAAU;CAEd,GAEMC,KAAW3C,EAAM,QAAQ,KAAK,MAAOA,EAAM,OAAOA,EAAM,QAAS,GAAG,IAAI,GACxE4C,IAAepF,GAAcsC;CAEnC,OACE,kBAAC,WAAD;EAAS,WAAW,WAAW+C,IAAU,uBAAuB;EAAM,cAAW;EAA2C,WAAWJ;EAAvI,UAAA;GACE,kBAAC,OAAD;IACE,KAAK5D;IACL,WAAU;IACV,UAAU;IACV,MAAK;IACL,cAAW;IACX,oBAAkBI;IAClB,aAAWE,MAAW;IACtB,WAAWgD;GACZ,CAAA;GAEAhD,MAAW,aACV,kBAAC,OAAD;IAAK,WAAU;IAAkB,MAAK;IAAtC,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;KAAqB,eAAY;KAC7C,UAAA,MAAM,KAAK,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAU,kBAAC,QAAD,CAAmB,GAAR2D,CAAQ,CAAC;IAC3D,CAAA,GACL,kBAAC,KAAD,EAAA,UAAA,CAAG,kBAACC,IAAD;KAAO,MAAM;KAAI,eAAY;IAAQ,CAAA,GAAC,wBAAyB,EAAA,CAAA,CAC/D;;GAEN5D,MAAW,WACV,kBAAC,OAAD;IAAK,WAAU;IAAyC,MAAK;IAA7D,UAAA;KACE,kBAAC6D,IAAD;MAAe,MAAM;MAAI,eAAY;KAAQ,CAAA;KAC7C,kBAAC,UAAD,EAAA,UAAQ,yBAA8B,CAAA;KACtC,kBAAC,KAAD,EAAA,UAAI1D,GAAgB,CAAA;KACpB,kBAAC,UAAD;MAAQ,MAAK;MAAS,WAAU;MAAgB,SAASoD;MAAS,UAAA;KAAsB,CAAA;IACrF;;GAGP,kBAAC,UAAD;IAAQ,WAAU;IAAlB,UAAA;KACE,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,QAAD;OAAM,WAAU;OAAhB,UAAA,CAAkC,kBAAc,kBAAC,QAAD;QAAM,WAAU;QAAsB,UAAA;OAAiB,CAAA,CAAO;MAC9G,CAAA,GAAA,kBAAC,QAAD;OAAM,WAAU;OAAkB,UAAA;MAAqB,CAAA,CACpD;;KACJhC,KACC,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;OAAd,UAAA,CACE,kBAAC,OAAD,EAAA,UAAA,CAAK,kBAAC,MAAD,EAAA,UAAI,WAAY,CAAA,GAAC,kBAAC,MAAD;QAAI,WAAU;QAA2B,UAAAjD,EAAU+B,CAAQ;OAAM,CAAA,CAAM,EAAA,CAAA,GAC7F,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CAAuC,kBAAC,MAAD,EAAA,UAAI,SAAU,CAAA,GAAC,kBAAC,MAAD,EAAA,UAAK/B,EAAUiC,CAAO,EAAM,CAAA,CAAM;OACtF,CAAA,CAAA;MACJ,CAAA,GAAA,kBAAC,MAAD;OAAI,WAAU;OAAd,UAAA;QACE,kBAAC,OAAD,EAAA,UAAA,CAAK,kBAAC,MAAD,EAAA,UAAI,WAAY,CAAA,GAAC,kBAAC,MAAD,EAAA,UAAKM,EAAM,MAAU,CAAA,CAAM,EAAA,CAAA;QACjD,kBAAC,OAAD,EAAA,UAAA,CAAK,kBAAC,MAAD,EAAA,UAAI,WAAY,CAAA,GAAC,kBAAC,MAAD,EAAA,UAAKA,EAAM,KAAS,CAAA,CAAM,EAAA,CAAA;QAChD,kBAAC,OAAD,EAAA,UAAA,CAAK,kBAAC,MAAD,EAAA,UAAI,YAAa,CAAA,GAAC,kBAAC,MAAD,EAAA,UAAA,CAAK2C,IAAS,GAAK,EAAA,CAAA,CAAM,EAAA,CAAA;OAC9C;MACD,CAAA,CAAA;;KAEP,kBAAC,UAAD;MAAQ,MAAK;MAAS,WAAU;MAAqC,SAASD;MAAS,cAAW;MAAuB,OAAM;MAC7H,UAAA,kBAACO,GAAD;OAAG,MAAM;OAAI,eAAY;MAAQ,CAAA;KAC3B,CAAA;IACF;;GAER,kBAAC,OAAD;IAAK,WAAU;IAAiB,aAAU;IAAS,eAAY;IAC5D,UAAA/C,KACC,kBAAC,QAAD;KAAsB,WAAW,oCAAoCA,EAAO,OAAOE,IAAe,gBAAgB;KAC/G,UAAAF,EAAO;IACJ,GAFKA,EAAO,EAEZ;GAEL,CAAA;GACL,kBAAC,QAAD;IAAM,WAAU;IAAe,aAAU;IAAU,UAAAV,IAAW,wBAAwB/B,EAAU+B,CAAQ,MAAM;GAAS,CAAA;GAEtHkB,KACC,kBAAC,UAAD;IAAQ,WAAU;IAAlB,UAAA,CACE,kBAAC,KAAD;KAAG,IAAIzB;KAAQ,WAAU;KAAiB,UAAA;IAAgG,CAAA,GAC1I,kBAAC,OAAD;KAAK,WAAU;KAAoC,MAAK;KAAU,cAAW;KAA7E,UAAA;MACE,kBAAC,UAAD;OAAQ,MAAK;OAAS,WAAU;OAAoC,eAAeyC,EAAK;OAAG,UAAU,CAAClC;OACpG,OAAOA,IAAW,eAAe/B,EAAU+B,CAAQ,EAAE,YAAY;OADnE,UAAA,CAEE,kBAAC0D,GAAD;QAAW,MAAM;QAAI,eAAY;OAAQ,CAAA,GAAC,kBAAC,QAAD,EAAA,UAAM,WAAc,CAAA,CACxD;;MACR,kBAAC,UAAD;OAAQ,MAAK;OAAS,WAAU;OAAgB,SAASlB;OAAa,gBAAcpC;OAAO,OAAM;OAAjG,UAAA,CACE,kBAACuD,IAAD;QAAO,MAAM;QAAI,eAAY;OAAQ,CAAA,GAAC,kBAAC,QAAD;QAAM,WAAU;QAAsB,UAAA;OAAW,CAAA,CACjF;;MACR,kBAAC,UAAD;OAAQ,MAAK;OAAS,WAAU;OAAgB,eAAepE,EAAS,SAAS,YAAY;OAAG,OAAM;OAAtG,UAAA,CACE,kBAACqE,IAAD;QAAa,MAAM;QAAI,eAAY;OAAQ,CAAA,GAAC,kBAAC,QAAD;QAAM,WAAU;QAAsB,UAAA;OAAoB,CAAA,CAChG;;MACR,kBAAC,UAAD;OAAQ,MAAK;OAAS,WAAU;OAAgB,SAASnB;OAAc,cAAY,YAAYW,EAAa;OAAoB,OAAM;OAAtI,UAAA,CACE,kBAACS,GAAD;QAAO,MAAM;QAAI,eAAY;OAAQ,CAAA,GAAC,kBAAC,QAAD;QAAM,WAAU;QAAhB,UAAA,CAAsC,aAAUT,CAAmB;OACnG,CAAA,CAAA;;MACR,kBAAC,UAAD;OAAQ,MAAK;OAAS,WAAU;OAAgB,SAASV;OAAW,OAAM;OAA1E,UAAA,CACE,kBAACoB,IAAD;QAAW,MAAM;QAAI,eAAY;OAAQ,CAAA,GAAC,kBAAC,QAAD;QAAM,WAAU;QAAsB,UAAA;OAAe,CAAA,CACzF;;KACL;IACC,CAAA,CAAA;;GAET,CAAC5C,KAAS,kBAAC,KAAD;IAAG,IAAIzB;IAAQ,WAAU;IAAe,UAAA;GAAyE,CAAA;EACrH;;AAEb"}
@@ -0,0 +1,90 @@
1
+ import { y as e } from "./connect4Sounds-BDXQAvU1.js";
2
+ import { createContext as t, useContext as n } from "react";
3
+ // src/notifications/chatSounds.js
4
+ var r = null, i = null, a = null, o = !1, s = !0, c = /* @__PURE__ */ new Set();
5
+ function l(e) {
6
+ return c.add(e), () => c.delete(e);
7
+ }
8
+ var u = 50, d = /* @__PURE__ */ new Set();
9
+ function f() {
10
+ try {
11
+ s = localStorage.getItem("lira-chat-sound") !== "false";
12
+ } catch {
13
+ s = !0;
14
+ }
15
+ }
16
+ function p(e) {
17
+ try {
18
+ localStorage.setItem("lira-chat-sound", e ? "true" : "false");
19
+ } catch {}
20
+ }
21
+ function m(t) {
22
+ if (t === "ting") {
23
+ if (!i) {
24
+ i = new Audio(e("ting.mp3")), i.preload = "auto";
25
+ let t = document.createElement("source");
26
+ t.src = e("ting.ogg"), t.type = "audio/ogg", i.appendChild(t);
27
+ }
28
+ return i;
29
+ }
30
+ if (!r) {
31
+ r = new Audio(e("notification.mp3")), r.preload = "auto";
32
+ let t = document.createElement("source");
33
+ t.src = e("notification.ogg"), t.type = "audio/ogg", r.appendChild(t);
34
+ }
35
+ return r;
36
+ }
37
+ function h(e) {
38
+ if (s) try {
39
+ let t = m(e);
40
+ t.currentTime = 0;
41
+ let n = t.play();
42
+ n && n.catch(() => {});
43
+ } catch {}
44
+ }
45
+ function g() {
46
+ typeof window < "u" && window.BroadcastChannel && (a || (a = new BroadcastChannel("lira-chat-sound"), a.onmessage = (e) => {
47
+ let t = e.data;
48
+ if (t && t.type === "PLAY" && !(t.msgId && d.has(t.msgId)) && (h(t.sound), t.msgId && (d.add(t.msgId), d.size > u))) {
49
+ let e = d.values().next().value;
50
+ d.delete(e);
51
+ }
52
+ }));
53
+ }
54
+ function _() {
55
+ o || (o = !0, f(), g());
56
+ }
57
+ function v(e) {
58
+ if (o || _(), s && !(e && d.has(e))) {
59
+ if (e && (d.add(e), d.size > u)) {
60
+ let e = d.values().next().value;
61
+ d.delete(e);
62
+ }
63
+ h("notification"), a && e && a.postMessage({
64
+ type: "PLAY",
65
+ sound: "notification",
66
+ msgId: e
67
+ });
68
+ }
69
+ }
70
+ function y(e) {
71
+ s = !!e, p(s), c.forEach((e) => e());
72
+ }
73
+ function b() {
74
+ return s;
75
+ }
76
+ function x() {
77
+ let e = !s;
78
+ return y(e), e;
79
+ }
80
+ // src/provider/ChatContext.js
81
+ var S = t(null);
82
+ S.displayName = "LiraChatContext";
83
+ function C() {
84
+ let e = n(S);
85
+ if (!e) throw Error("LiraChatProvider requerido");
86
+ return e;
87
+ }
88
+ export { v as a, b as i, C as n, l as o, _ as r, x as s, S as t };
89
+
90
+ //# sourceMappingURL=ChatContext-CUiEAMKK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatContext-CUiEAMKK.js","names":["chatSoundUrl","audioNotification","audioTing","channel","initialized","soundEnabled","soundSubscribers","subscribeSoundPreference","listener","DEDUP_MAX","recentMsgIds","loadPreference","savePreference","enabled","getAudio","sound","source","playSound","audio","promise","initBroadcastChannel","data","first","initChatSounds","requestNotification","msgId","setSoundEnabled","isSoundEnabled","toggleSound","next","ChatContext","createContext","useChatContext","context","useContext"],"sources":["../../src/notifications/chatSounds.js","../../src/provider/ChatContext.js"],"sourcesContent":["import { chatSoundUrl } from '../assets/chatAssets';\n\nlet audioNotification = null;\nlet audioTing = null;\nlet channel = null;\nlet initialized = false;\nlet soundEnabled = true;\nconst soundSubscribers = new Set();\nexport function subscribeSoundPreference(listener) {\n soundSubscribers.add(listener);\n return () => soundSubscribers.delete(listener);\n}\nlet lastPlayedMsgId = null;\nconst DEDUP_MAX = 50;\nconst recentMsgIds = new Set();\n\nfunction loadPreference() {\n try {\n const stored = localStorage.getItem('lira-chat-sound');\n soundEnabled = stored !== 'false';\n } catch {\n soundEnabled = true;\n }\n}\n\nfunction savePreference(enabled) {\n try {\n localStorage.setItem('lira-chat-sound', enabled ? 'true' : 'false');\n } catch {}\n}\n\nfunction getAudio(sound) {\n if (sound === 'ting') {\n if (!audioTing) {\n audioTing = new Audio(chatSoundUrl('ting.mp3'));\n audioTing.preload = 'auto';\n const source = document.createElement('source');\n source.src = chatSoundUrl('ting.ogg');\n source.type = 'audio/ogg';\n audioTing.appendChild(source);\n }\n return audioTing;\n }\n if (!audioNotification) {\n audioNotification = new Audio(chatSoundUrl('notification.mp3'));\n audioNotification.preload = 'auto';\n const source = document.createElement('source');\n source.src = chatSoundUrl('notification.ogg');\n source.type = 'audio/ogg';\n audioNotification.appendChild(source);\n }\n return audioNotification;\n}\n\nfunction playSound(sound) {\n if (!soundEnabled) return;\n try {\n const audio = getAudio(sound);\n audio.currentTime = 0;\n const promise = audio.play();\n if (promise) promise.catch(() => {});\n } catch {}\n}\n\nfunction initBroadcastChannel() {\n if (typeof window === 'undefined' || !window.BroadcastChannel) return;\n if (channel) return;\n channel = new BroadcastChannel('lira-chat-sound');\n channel.onmessage = (e) => {\n const data = e.data;\n if (!data || data.type !== 'PLAY') return;\n if (data.msgId && recentMsgIds.has(data.msgId)) return;\n playSound(data.sound);\n if (data.msgId) {\n recentMsgIds.add(data.msgId);\n if (recentMsgIds.size > DEDUP_MAX) {\n const first = recentMsgIds.values().next().value;\n recentMsgIds.delete(first);\n }\n }\n };\n}\n\nexport function initChatSounds() {\n if (initialized) return;\n initialized = true;\n loadPreference();\n initBroadcastChannel();\n}\n\nexport function requestNotification(msgId) {\n if (!initialized) initChatSounds();\n if (!soundEnabled) return;\n if (msgId && recentMsgIds.has(msgId)) return;\n if (msgId) {\n lastPlayedMsgId = msgId;\n recentMsgIds.add(msgId);\n if (recentMsgIds.size > DEDUP_MAX) {\n const first = recentMsgIds.values().next().value;\n recentMsgIds.delete(first);\n }\n }\n playSound('notification');\n if (channel && msgId) {\n channel.postMessage({ type: 'PLAY', sound: 'notification', msgId });\n }\n}\n\nexport function setSoundEnabled(enabled) {\n soundEnabled = !!enabled;\n savePreference(soundEnabled);\n soundSubscribers.forEach((listener) => listener());\n}\n\nexport function isSoundEnabled() {\n return soundEnabled;\n}\n\nexport function toggleSound() {\n const next = !soundEnabled;\n setSoundEnabled(next);\n return next;\n}\n\nexport function cleanup() {\n if (channel) {\n channel.close();\n channel = null;\n }\n if (audioNotification) {\n audioNotification.src = '';\n audioNotification.load();\n audioNotification = null;\n }\n if (audioTing) {\n audioTing.src = '';\n audioTing.load();\n audioTing = null;\n }\n initialized = false;\n}\n","import { createContext, useContext } from 'react';\n\n// Value: { config, transport, chatStore, stores }.\nexport const ChatContext = createContext(null);\nChatContext.displayName = 'LiraChatContext';\n\nexport function useChatContext() {\n const context = useContext(ChatContext);\n if (!context) throw new Error('LiraChatProvider requerido');\n return context;\n}\n"],"mappings":"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"}