bc/public/js/ui.js
2025-05-09 12:11:07 +00:00

465 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// /public/js/ui.js
// Этот файл отвечает за обновление DOM на основе состояния игры,
// полученного от client.js (который, в свою очередь, получает его от сервера).
// console.log("Loading ui.js...");
// GAME_CONFIG и gameData теперь предполагается, что будут установлены в window
// из client.js после получения данных от сервера.
// Это не лучшая практика (глобальные переменные), но для адаптации
// существующего кода это может быть проще на первом этапе.
// В идеале, все функции updateUI должны принимать gameState и gameData как параметры.
(function() {
// --- DOM Элементы (остаются как были) ---
const uiElements = {
player: {
panel: document.getElementById('player-panel'),
name: document.getElementById('player-name'), // Для обновления имени в PvP
avatar: document.getElementById('player-panel')?.querySelector('.player-avatar'),
hpFill: document.getElementById('player-hp-fill'), hpText: document.getElementById('player-hp-text'), maxHpSpan: document.getElementById('player-max-hp'),
resourceFill: document.getElementById('player-resource-fill'), resourceText: document.getElementById('player-resource-text'), maxResourceSpan: document.getElementById('player-max-resource'),
status: document.getElementById('player-status'),
effectsContainer: document.getElementById('player-effects'),
buffsList: document.getElementById('player-effects')?.querySelector('.player-buffs'),
debuffsList: document.getElementById('player-effects')?.querySelector('.player-debuffs')
},
opponent: {
panel: document.getElementById('opponent-panel'),
name: document.getElementById('opponent-name'), // Для обновления имени в PvP
avatar: document.getElementById('opponent-panel')?.querySelector('.opponent-avatar'),
hpFill: document.getElementById('opponent-hp-fill'), hpText: document.getElementById('opponent-hp-text'), maxHpSpan: document.getElementById('opponent-max-hp'),
resourceFill: document.getElementById('opponent-resource-fill'), resourceText: document.getElementById('opponent-resource-text'), maxResourceSpan: document.getElementById('opponent-max-resource'),
status: document.getElementById('opponent-status'),
effectsContainer: document.getElementById('opponent-effects'),
buffsList: document.getElementById('opponent-effects')?.querySelector('.opponent-buffs'),
debuffsList: document.getElementById('opponent-effects')?.querySelector('.opponent-debuffs')
},
controls: {
turnIndicator: document.getElementById('turn-indicator'),
buttonAttack: document.getElementById('button-attack'),
buttonBlock: document.getElementById('button-block'), // Все еще здесь, хоть и отключен
abilitiesGrid: document.getElementById('abilities-grid'),
},
log: {
list: document.getElementById('log-list'),
},
gameOver: {
screen: document.getElementById('game-over-screen'),
message: document.getElementById('result-message'),
restartButton: document.getElementById('restart-game-button'),
modalContent: document.getElementById('game-over-screen')?.querySelector('.modal-content')
},
// Дополнительные элементы для PvP информации, если нужно
gameHeaderTitle: document.querySelector('.game-header h1') // Заголовок "Елена vs Балард"
};
/** Добавляет сообщение в лог. */
function addToLog(message, type = 'info') { // Используем строковый тип по умолчанию
const logListElement = uiElements.log.list;
if (!logListElement) {
console.warn("addToLog: Элемент списка логов не найден.");
return;
}
const li = document.createElement('li');
li.textContent = message;
// Используем типы логов из GAME_CONFIG, если он определен
// Иначе, используем переданный строковый тип как есть
const logTypeClass = window.GAME_CONFIG && window.GAME_CONFIG[`LOG_TYPE_${type.toUpperCase()}`]
? `log-${window.GAME_CONFIG[`LOG_TYPE_${type.toUpperCase()}`]}`
: `log-${type}`;
li.className = logTypeClass;
logListElement.appendChild(li);
// Плавная прокрутка к последнему сообщению
requestAnimationFrame(() => {
logListElement.scrollTop = logListElement.scrollHeight;
});
}
/** Обновляет бары, статус, имя и аватар бойца. */
function updateFighterUI(fighterRole, // 'player' (Елена) или 'opponent' (Балард) - технические роли
fighterActualId, // 'player' или 'opponent' - кто из gameState соответствует этой панели
fighterState, // gameState[fighterActualId]
fighterBaseStatsRef, // gameData[fighterActualId+'BaseStats']
isControlledByThisClient) { // true, если эта панель соответствует игроку за этим клиентом
const elements = uiElements[fighterRole]; // uiElements.player или uiElements.opponent
if (!elements || !elements.hpFill || !elements.hpText || !elements.resourceFill || !elements.resourceText || !elements.status || !fighterState || !fighterBaseStatsRef) {
console.warn(`updateFighterUI: Отсутствуют элементы/состояние/базовые статы для роли ${fighterRole} (id: ${fighterActualId}).`);
return;
}
// Обновление имени и иконки
if (elements.name) {
let nameHtml = `<i class="fas ${fighterBaseStatsRef.id === 'player' ? 'fa-hat-wizard icon-player' : 'fa-khanda icon-opponent'}"></i> ${fighterBaseStatsRef.name}`;
if (isControlledByThisClient) {
nameHtml += " (Вы)";
}
elements.name.innerHTML = nameHtml;
elements.name.style.color = fighterBaseStatsRef.id === 'player' ? 'var(--accent-player)' : 'var(--accent-opponent)';
}
// Обновление аватара (если путь к аватару есть в baseStats или приходит от сервера)
// if (elements.avatar && fighterBaseStatsRef.avatarPath) {
// elements.avatar.src = fighterBaseStatsRef.avatarPath;
// }
const maxHp = Math.max(1, fighterBaseStatsRef.maxHp);
const maxRes = Math.max(1, fighterBaseStatsRef.maxResource);
const currentHp = Math.max(0, fighterState.currentHp);
const currentRes = Math.max(0, fighterState.currentResource);
const hpPercent = (currentHp / maxHp) * 100;
const resourcePercent = (currentRes / maxRes) * 100;
elements.hpFill.style.width = `${hpPercent}%`;
elements.hpText.textContent = `${Math.round(currentHp)} / ${fighterBaseStatsRef.maxHp}`;
elements.resourceFill.style.width = `${resourcePercent}%`;
elements.resourceText.textContent = `${Math.round(currentRes)} / ${fighterBaseStatsRef.maxResource}`;
// Динамическое изменение цвета и иконки для ресурса Баларда (если он не мана)
const resourceBarContainer = elements.resourceFill.closest('.stat-bar-container');
if (resourceBarContainer) {
const iconElement = resourceBarContainer.querySelector('.bar-icon i');
if (fighterActualId === (window.GAME_CONFIG?.OPPONENT_ID || 'opponent') && fighterBaseStatsRef.resourceName !== "Мана") {
resourceBarContainer.classList.remove('mana');
resourceBarContainer.classList.add('stamina'); // Предполагаем, что это выносливость/ярость
if(iconElement) iconElement.className = 'fas fa-fire-alt'; // Иконка ярости
} else { // Для Елены или если у Баларда мана
resourceBarContainer.classList.remove('stamina');
resourceBarContainer.classList.add('mana');
if(iconElement) iconElement.className = 'fas fa-flask'; // Иконка маны
}
}
const statusText = fighterState.isBlocking ? (window.GAME_CONFIG?.STATUS_BLOCKING || 'Защищается') : (window.GAME_CONFIG?.STATUS_READY || 'Готов');
elements.status.textContent = statusText;
elements.status.classList.toggle(window.GAME_CONFIG?.CSS_CLASS_BLOCKING || 'blocking', fighterState.isBlocking);
// Обновление glow-эффекта панели
if (elements.panel) {
const accentColor = fighterActualId === (window.GAME_CONFIG?.PLAYER_ID || 'player')
? 'var(--accent-player)'
: 'var(--accent-opponent)';
const glowColor = fighterActualId === (window.GAME_CONFIG?.PLAYER_ID || 'player')
? 'var(--panel-glow-player)'
: 'var(--panel-glow-opponent)';
elements.panel.style.borderColor = accentColor;
elements.panel.style.boxShadow = `0 0 15px ${glowColor}, inset 0 0 10px rgba(0, 0, 0, 0.3)`;
}
}
function classifyAndGenerateEffectsHTMLArrays(effectsArray, fighterRole) {
const buffsHTMLStrings = [];
const debuffsHTMLStrings = [];
const config = window.GAME_CONFIG || {}; // Фоллбэк на пустой объект, если GAME_CONFIG не загружен
if (!effectsArray || effectsArray.length === 0) {
return { buffsHTMLStrings: ['Нет'], debuffsHTMLStrings: ['Нет'] };
}
effectsArray.forEach(eff => {
let isDebuff = false;
let effectClasses = config.CSS_CLASS_EFFECT || 'effect'; // Базовый класс
const title = `${eff.name}${eff.description ? ` - ${eff.description}` : ''} (Осталось: ${eff.turnsLeft} х.)`;
const displayText = `${eff.name} (${eff.turnsLeft} х.)`;
if (eff.type === config.ACTION_TYPE_DISABLE ||
eff.type === config.ACTION_TYPE_DEBUFF ||
eff.id === 'fullSilenceByElena' || // Специфичный эффект Елены
eff.id.startsWith('playerSilencedOn_') // Эффект безмолвия от Баларда на конкретную абилку
) {
isDebuff = true;
}
if (isDebuff) {
if (eff.id === 'fullSilenceByElena' || eff.id.startsWith('playerSilencedOn_') || eff.type === config.ACTION_TYPE_DISABLE) {
effectClasses += ' effect-stun'; // Или ваш класс для дизейблов
} else {
effectClasses += ' effect-debuff';
}
debuffsHTMLStrings.push(`<span class="${effectClasses}" title="${title}">${displayText}</span>`);
} else {
if (eff.grantsBlock) {
effectClasses += ' effect-block';
} else { // Все остальное считаем баффом
effectClasses += ' effect-buff';
}
buffsHTMLStrings.push(`<span class="${effectClasses}" title="${title}">${displayText}</span>`);
}
});
return {
buffsHTMLStrings: buffsHTMLStrings.length > 0 ? buffsHTMLStrings : ['Нет'],
debuffsHTMLStrings: debuffsHTMLStrings.length > 0 ? debuffsHTMLStrings : ['Нет']
};
}
function updateEffectsUI(currentGameState, myPlayerId) {
if (!currentGameState || !window.gameData) return; // gameData тоже должен быть доступен
const playerRoleForUI = myPlayerId === currentGameState.player.id ? 'player' : 'opponent';
const opponentRoleForUI = myPlayerId === currentGameState.player.id ? 'opponent' : 'player';
// Обновление для панели, соответствующей myPlayerId
const myPanelElements = uiElements[playerRoleForUI]; // Это будет uiElements.player или uiElements.opponent
const myState = currentGameState[myPlayerId]; // Это будет gameState.player или gameState.opponent
if (myPanelElements && myState) {
const myClassifiedEffects = classifyAndGenerateEffectsHTMLArrays(myState.activeEffects, playerRoleForUI);
if (myPanelElements.buffsList) myPanelElements.buffsList.innerHTML = myClassifiedEffects.buffsHTMLStrings.join(' ');
if (myPanelElements.debuffsList) myPanelElements.debuffsList.innerHTML = myClassifiedEffects.debuffsHTMLStrings.join(' ');
}
// Обновление для панели противника
const opponentActualId = myPlayerId === currentGameState.player.id ? currentGameState.opponent.id : currentGameState.player.id;
const opponentPanelElements = uiElements[opponentRoleForUI];
const opponentState = currentGameState[opponentActualId];
if (opponentPanelElements && opponentState) {
const opponentClassifiedEffects = classifyAndGenerateEffectsHTMLArrays(opponentState.activeEffects, opponentRoleForUI);
if (opponentPanelElements.buffsList) opponentPanelElements.buffsList.innerHTML = opponentClassifiedEffects.buffsHTMLStrings.join(' ');
if (opponentPanelElements.debuffsList) opponentPanelElements.debuffsList.innerHTML = opponentClassifiedEffects.debuffsHTMLStrings.join(' ');
}
}
/** Главная функция обновления всего UI. */
function updateUI() {
// Используем глобальные gameState, gameData, GAME_CONFIG, установленные из client.js
const currentGameState = window.gameState;
const gameDataGlobal = window.gameData;
const configGlobal = window.GAME_CONFIG;
const myPlayerId = window.myPlayerId; // myPlayerId также должен быть установлен глобально или передан
if (!currentGameState || !gameDataGlobal || !configGlobal || !myPlayerId) {
console.warn("updateUI: Отсутствуют глобальные gameState, gameData, GAME_CONFIG или myPlayerId.");
return;
}
if (!uiElements.player.panel || !uiElements.opponent.panel || !uiElements.controls.turnIndicator || !uiElements.controls.abilitiesGrid || !uiElements.log.list) {
console.warn("updateUI: Некоторые базовые uiElements не найдены.");
return;
}
// Определяем, какая панель (player/opponent в uiElements) соответствует какому игроку (player/opponent в gameState)
// и кто из них управляется этим клиентом.
const playerPanelRepresents = currentGameState.player.id; // Кого отображает панель "player"
const opponentPanelRepresents = currentGameState.opponent.id; // Кого отображает панель "opponent"
updateFighterUI('player', playerPanelRepresents, currentGameState[playerPanelRepresents], gameDataGlobal[playerPanelRepresents === 'player' ? 'playerBaseStats' : 'opponentBaseStats'], playerPanelRepresents === myPlayerId);
updateFighterUI('opponent', opponentPanelRepresents, currentGameState[opponentPanelRepresents], gameDataGlobal[opponentPanelRepresents === 'player' ? 'playerBaseStats' : 'opponentBaseStats'], opponentPanelRepresents === myPlayerId);
updateEffectsUI(currentGameState, myPlayerId);
// Обновление заголовка игры (Елена vs Балард)
if (uiElements.gameHeaderTitle && gameDataGlobal.playerBaseStats && gameDataGlobal.opponentBaseStats) {
uiElements.gameHeaderTitle.innerHTML = `<span class="title-enchantress">${gameDataGlobal.playerBaseStats.name}</span> <span class="separator"><i class="fas fa-fist-raised"></i></span> <span class="title-knight">${gameDataGlobal.opponentBaseStats.name}</span>`;
}
if (uiElements.controls.turnIndicator) {
const currentTurnActorId = currentGameState.isPlayerTurn ? currentGameState.player.id : currentGameState.opponent.id;
const currentTurnName = currentGameState[currentTurnActorId].name;
uiElements.controls.turnIndicator.textContent = `Ход: ${currentTurnName}`;
uiElements.controls.turnIndicator.style.color = currentTurnActorId === configGlobal.PLAYER_ID ? 'var(--accent-player)' : 'var(--accent-opponent)';
}
const canThisClientAct = (currentGameState.isPlayerTurn && myPlayerId === currentGameState.player.id) ||
(!currentGameState.isPlayerTurn && myPlayerId === currentGameState.opponent.id);
const isGameActive = !currentGameState.isGameOver;
// Обновление кнопки атаки
if (uiElements.controls.buttonAttack) {
uiElements.controls.buttonAttack.disabled = !(canThisClientAct && isGameActive);
// Подсветка атаки от "Силы Природы" (только если myPlayerId - это Елена)
if (myPlayerId === configGlobal.PLAYER_ID) {
const isNatureBuffReady = currentGameState.player.activeEffects.some(eff => eff.id === configGlobal.ABILITY_ID_NATURE_STRENGTH && !eff.justCast);
uiElements.controls.buttonAttack.classList.toggle(configGlobal.CSS_CLASS_ATTACK_BUFFED || 'attack-buffed', isNatureBuffReady && canThisClientAct && isGameActive);
} else {
uiElements.controls.buttonAttack.classList.remove(configGlobal.CSS_CLASS_ATTACK_BUFFED || 'attack-buffed');
}
}
// Кнопка блока всегда отключена
if (uiElements.controls.buttonBlock) {
uiElements.controls.buttonBlock.disabled = true;
}
// Обновление кнопок способностей для текущего игрока (myPlayerId)
const actingPlayerState = currentGameState[myPlayerId];
const actingPlayerAbilities = myPlayerId === configGlobal.PLAYER_ID ? gameDataGlobal.playerAbilities : gameDataGlobal.opponentAbilities;
uiElements.controls.abilitiesGrid?.querySelectorAll(`.${configGlobal.CSS_CLASS_ABILITY_BUTTON || 'ability-button'}`).forEach(button => {
if (!(button instanceof HTMLButtonElement) || !actingPlayerState || !actingPlayerAbilities) {
if(button instanceof HTMLButtonElement) button.disabled = true;
return;
}
const abilityId = button.dataset.abilityId;
const ability = actingPlayerAbilities.find(ab => ab.id === abilityId);
if (!ability) { button.disabled = true; return; }
const hasEnoughResource = actingPlayerState.currentResource >= ability.cost;
const isBuffAlreadyActive = ability.type === configGlobal.ACTION_TYPE_BUFF && actingPlayerState.activeEffects.some(eff => eff.id === abilityId);
const silencedInfo = actingPlayerState.disabledAbilities?.find(dis => dis.abilityId === abilityId);
const isSilencedByOpponent = !!silencedInfo;
const cooldownTurnsLeft = actingPlayerState.abilityCooldowns?.[ability.id] || 0;
const isOnCooldown = cooldownTurnsLeft > 0;
let isDisabledByDebuffOnTarget = false; // Для специфичных дебаффов типа Печати Слабости
if (ability.id === configGlobal.ABILITY_ID_SEAL_OF_WEAKNESS && myPlayerId === configGlobal.PLAYER_ID) { // Только Елена может накладывать
const opponentActualId = myPlayerId === currentGameState.player.id ? currentGameState.opponent.id : currentGameState.player.id;
const effectIdForDebuff = 'effect_' + ability.id;
isDisabledByDebuffOnTarget = currentGameState[opponentActualId].activeEffects.some(e => e.id === effectIdForDebuff);
}
let finalDisabledState = !(canThisClientAct && isGameActive) || !hasEnoughResource || isBuffAlreadyActive || isSilencedByOpponent || isOnCooldown || isDisabledByDebuffOnTarget;
button.disabled = finalDisabledState;
// Стилизация кнопки
button.classList.remove(
configGlobal.CSS_CLASS_NOT_ENOUGH_RESOURCE || 'not-enough-resource',
configGlobal.CSS_CLASS_BUFF_IS_ACTIVE || 'buff-is-active',
configGlobal.CSS_CLASS_ABILITY_SILENCED || 'is-silenced',
configGlobal.CSS_CLASS_ABILITY_ON_COOLDOWN || 'is-on-cooldown'
);
const cooldownDisplay = button.querySelector('.ability-cooldown-display');
if (isOnCooldown) {
button.classList.add(configGlobal.CSS_CLASS_ABILITY_ON_COOLDOWN || 'is-on-cooldown');
if (cooldownDisplay) {
cooldownDisplay.textContent = `КД: ${cooldownTurnsLeft}`;
cooldownDisplay.style.display = 'block';
}
} else {
if (cooldownDisplay) cooldownDisplay.style.display = 'none';
// Применяем другие классы только если не на КД
button.classList.toggle(configGlobal.CSS_CLASS_NOT_ENOUGH_RESOURCE || 'not-enough-resource', canThisClientAct && isGameActive && !hasEnoughResource && !isBuffAlreadyActive && !isSilencedByOpponent && !isDisabledByDebuffOnTarget);
button.classList.toggle(configGlobal.CSS_CLASS_BUFF_IS_ACTIVE || 'buff-is-active', canThisClientAct && isGameActive && isBuffAlreadyActive && !isSilencedByOpponent); // Для баффов на себя
button.classList.toggle(configGlobal.CSS_CLASS_ABILITY_SILENCED || 'is-silenced', isSilencedByOpponent);
// Если Печать Слабости уже на цели, можно добавить особый класс или просто дизейблить
if (isDisabledByDebuffOnTarget) {
// button.classList.add('debuff-already-active'); // Пример
}
}
// Обновление Tooltip
const resourceName = actingPlayerState.resourceName;
let titleText = `${ability.name} (${ability.cost} ${resourceName}) - ${ability.description}`;
if (typeof ability.descriptionFunction === 'function') {
const configForDesc = window.clientSideConfig?.GAME_CONFIG_FOR_ABILITIES || configGlobal;
const targetStatsForDesc = (myPlayerId === configGlobal.PLAYER_ID)
? (window.gameData?.opponentBaseStats)
: (window.gameData?.playerBaseStats);
titleText = `${ability.name} (${ability.cost} ${resourceName}) - ${ability.descriptionFunction(configForDesc, targetStatsForDesc)}`;
}
let abilityBaseCooldown = ability.cooldown;
if (myPlayerId === configGlobal.OPPONENT_ID) { // Если Балард-игрок
if (ability.internalCooldownFromConfig && configGlobal) {
abilityBaseCooldown = configGlobal[ability.internalCooldownFromConfig];
} else if (ability.internalCooldownValue) {
abilityBaseCooldown = ability.internalCooldownValue;
}
}
if (abilityBaseCooldown) titleText += ` (КД: ${abilityBaseCooldown} х.)`;
if (isOnCooldown) {
titleText = `${ability.name} - На перезарядке! Осталось: ${cooldownTurnsLeft} х.`;
} else if (isSilencedByOpponent) {
titleText = `Заглушено! Осталось: ${silencedInfo.turnsLeft} х.`;
} else if (isBuffAlreadyActive) {
const activeEffect = actingPlayerState.activeEffects.find(eff => eff.id === abilityId);
titleText = `Эффект "${ability.name}" уже активен${activeEffect ? ` (${activeEffect.turnsLeft} х.)` : ''}`;
} else if (isDisabledByDebuffOnTarget) {
const opponentActualId = myPlayerId === currentGameState.player.id ? currentGameState.opponent.id : currentGameState.player.id;
const activeDebuff = currentGameState[opponentActualId].activeEffects.find(e => e.id === 'effect_' + ability.id);
titleText = `Эффект "${ability.name}" уже наложен на ${currentGameState[opponentActualId].name}${activeDebuff ? ` (${activeDebuff.turnsLeft} х.)` : ''}`;
}
button.setAttribute('title', titleText);
});
}
/** Показывает экран конца игры с задержкой. */
function showGameOver(playerWon, reason = "") {
const config = window.GAME_CONFIG || {};
const gameDataGlobal = window.gameData || {};
const gameOverScreenElement = uiElements.gameOver.screen;
if (!gameOverScreenElement) return;
const resultMsgElement = uiElements.gameOver.message;
const opponentPanelElement = uiElements.opponent.panel; // Панель Баларда в UI
if (resultMsgElement) {
let winText = `Победа! ${gameDataGlobal.playerBaseStats ? gameDataGlobal.playerBaseStats.name : 'Игрок'} празднует!`;
let loseText = `Поражение! ${gameDataGlobal.opponentBaseStats ? gameDataGlobal.opponentBaseStats.name : 'Противник'} оказался сильнее!`;
if (reason === 'opponent_disconnected') {
if (playerWon) { // Если "мы" победили из-за дисконнекта оппонента
winText = `Противник покинул игру. Победа присуждается вам!`;
} else { // Если "мы" были оппонентом, который остался
loseText = `Игрок покинул игру. Техническая победа.`;
}
}
if (playerWon) {
resultMsgElement.textContent = winText;
resultMsgElement.style.color = 'var(--heal-color)';
} else {
resultMsgElement.textContent = loseText;
resultMsgElement.style.color = 'var(--damage-color)';
}
}
// Анимация растворения панели Баларда, если победила Елена (и это не дисконнект)
// И если панель оппонента действительно отображает Баларда
const opponentPanelRepresents = window.gameState?.opponent.id; // Кто сейчас на панели оппонента
if (playerWon && reason !== 'opponent_disconnected' &&
opponentPanelElement && opponentPanelRepresents === config.OPPONENT_ID) {
// addToLog вызывается из client.js при получении gameOver от сервера
opponentPanelElement.classList.add('dissolving');
} else if (opponentPanelElement) {
// Убираем класс, если он был, на случай рестарта
opponentPanelElement.classList.remove('dissolving');
}
setTimeout(() => {
gameOverScreenElement.classList.remove(config.CSS_CLASS_HIDDEN || 'hidden');
requestAnimationFrame(() => { // Для плавности анимации появления
gameOverScreenElement.style.opacity = '0'; // Сначала делаем невидимым
setTimeout(() => { // Даем браузеру время на рендер невидимого состояния
gameOverScreenElement.style.opacity = '1'; // Анимируем появление
if (uiElements.gameOver.modalContent) {
uiElements.gameOver.modalContent.style.transform = 'scale(1) translateY(0)';
uiElements.gameOver.modalContent.style.opacity = '1';
}
}, config.MODAL_TRANSITION_DELAY || 10);
});
}, config.DELAY_BEFORE_VICTORY_MODAL || 1500);
}
// --- Экспорт ---
// Функции теперь доступны через window.gameUI
window.gameUI = {
uiElements,
addToLog,
updateUI,
showGameOver
// classifyAndGenerateEffectsHTMLArrays, // Можно сделать доступным, если нужно извне
// updateFighterUI,
// updateEffectsUI
};
})();
// console.log("ui.js loaded and initialized.", window.gameUI);