JavaScript must be enabled to play.
Browser lacks capabilities required to play.
Upgrade or switch to another browser.
Loading…
<<if $page_id == 0>>\ <<set $pageTitle to "pass_info">>\ <div class="page">\ <<include "page-header">>\ <div class="page-main">\ <<include "page-main-background">>\ <div class="page-main-disclaimer">\ <div class="page-main-disclaimer-text-two-title"><<= setup.getLocalizedText("start_disclaimer_title")>></div>\ <div class="page-main-disclaimer-text-one"><<= setup.getLocalizedText("start_disclaimer_text")>></div>\ </div>\ </div>\ <div class="page-futer">\ <<L "disclaimer_accept" "Start" "$page_id = 1">>\ <a href="https://www.google.com/" style="border:1px solid #1E90FF;color:#1E90FF;border-radius: 7px;padding: 2px 5px"><<= setup.getLocalizedText("disclaimer_refuse")>></a>\ </div>\ </div>\ <<elseif $page_id == 1>>\ <<set $pageTitle to "pass_annotation">>\ <div class="page">\ <<include "page-header">>\ <div class="page-main">\ <<include "page-main-background">>\ <div class="page-main-disclaimer">\ <div class="page-main-disclaimer-text-two">\ <div class="page-main-disclaimer-text-two-title"><<= setup.getLocalizedText("start_annotation_title")>></div>\ <div class="page-main-disclaimer-box">\ <div class="page-main-disclaimer-box-text-main"><<= setup.getLocalizedText("start_annotation_desc")>></div>\ </div>\ <div class="page-main-disclaimer-box">\ <div class="page-main-disclaimer-box-title"><<= setup.getLocalizedText("start_features_title")>></div>\ <div class="page-main-disclaimer-box-text"><<= setup.getLocalizedText("start_features_text")>></div>\ </div>\ <div class="page-main-disclaimer-box">\ <div class="page-main-disclaimer-box-title"><<= setup.getLocalizedText("start_tech_title")>></div>\ <div class="page-main-disclaimer-box-text"><<= setup.getLocalizedText("start_tech_text")>></div>\ </div>\ <div class="page-main-disclaimer-box">\ <div class="page-main-disclaimer-box-title"><<= setup.getLocalizedText("start_thanks_title")>></div>\ <div class="page-main-disclaimer-box-text"><<= setup.getLocalizedText("start_thanks_text")>></div>\ </div>\ <div class="page-main-disclaimer-box">\ <div class="page-main-disclaimer-box-title"><<= setup.getLocalizedText("start_license_title")>></div>\ <div class="page-main-disclaimer-box-text"><<= setup.getLocalizedText("start_license_text")>></div>\ </div>\ </div>\ </div>\ </div>\ <div class="page-futer">\ <<L "btn_start" "Start" "$page_id = 2">>\ </div>\ </div>\ <<elseif $page_id == 2>>\ <<set $pageTitle to "pass_start_config">>\ <<set _gameSettings to setup.game_parameters?.game_settings>>\ <<set _isDual to _gameSettings?.dual_protagonist !== false>>\ <<set _availableHeroes to _gameSettings?.available_protagonists || ["gg_m", "gg_f"]>>\ <<set _hasMale to _availableHeroes.includes("gg_m")>>\ <<set _hasFemale to _availableHeroes.includes("gg_f")>>\ <div class="page">\ <<include "page-header">>\ <div class="page-main">\ <<include "page-main-background">>\ <div class="page-main-disclaimer">\ <div class="page-main-disclaimer-text-two-2">\ <div class="page-main-disclaimer-text-two-title-2"><<= setup.getLocalizedText("start_config_title")>></div>\ <div class="page-main-disclaimer-box-2">\ <div class="page-main-disclaimer-box-text-main-2"><<= setup.getLocalizedText("start_config_desc")>></div>\ </div>\ <div class="page-main-disclaimer-box-3">\ <<if _isDual && _hasMale && _hasFemale>>\ <!-- Режим двух героев - показываем обоих с переключением -->\ <div class="page-main-disclaimer-box-2">\ <div class="page-main-disclaimer-box-title-2">\ <<if $gg == 0>><<= setup.getLocalizedText("start_hero_male")>>\ <<elseif $gg == 1>><<link `setup.getLocalizedText("start_select_male")` "Start">><<set $gg = 0>><<set $gg_quest_owner to 0>><</link>>\\ <</if>>\ </div>\ <div class="page-main-disclaimer-box-text-2">\ <div class="page-main-disclaimer-box-text-add">\ <<set _hasCases to ($lang eq 'ru' || $lang eq 'uk' || $lang eq 'pl')>>\ <<if _hasCases>>\ <<= setup.getLocalizedText("start_name_hint_who")>> <<textbox "$gn_m" "Максим">> <<= setup.getLocalizedText("start_name_hint_whom_no")>> <<textbox "$gn_m_net" "Максима">> <<= setup.getLocalizedText("start_name_hint_whom_give")>> <<textbox "$gn_m_dam" "Максиму">> <<= setup.getLocalizedText("start_name_hint_whom_see")>> <<textbox "$gn_m_vizhu" "Максима">> <<= setup.getLocalizedText("start_name_hint_whom_proud")>> <<textbox "$gn_m_kem" "Максимом">> <<= setup.getLocalizedText("start_name_hint_whom_about")>> <<textbox "$gn_m_okom" "Максиме">> <<= setup.getLocalizedText("start_name_hint_short")>> <<textbox "$gn_m_short" "Макс">> <</if>>\ <<if !_hasCases>>\ <<= setup.getLocalizedText("start_name_hint_name") || "Name">> <<textbox "$gn_m" "Max">> <<= setup.getLocalizedText("start_name_hint_short") || "Nickname">> <<textbox "$gn_m_short" "Max">> <</if>>\ </div>\ </div>\ </div>\ <div class="page-main-disclaimer-box-2">\ <div class="page-main-disclaimer-box-title-2">\ <<if $gg == 0>><<link `setup.getLocalizedText("start_select_female")` "Start">><<set $gg = 1>><<set $gg_quest_owner to 1>><</link>>\ <<elseif $gg == 1>><<= setup.getLocalizedText("start_hero_female")>>\ <</if>>\ </div>\ <div class="page-main-disclaimer-box-text-2">\ <div class="page-main-disclaimer-box-text-add">\ <<set _hasCases to ($lang eq 'ru' || $lang eq 'uk' || $lang eq 'pl')>>\ <<if _hasCases>>\ <<= setup.getLocalizedText("start_name_hint_who")>> <<textbox "$gn_f" "Карина">> <<= setup.getLocalizedText("start_name_hint_whom_no")>> <<textbox "$gn_f_net" "Карины">> <<= setup.getLocalizedText("start_name_hint_whom_give")>> <<textbox "$gn_f_dam" "Карине">> <<= setup.getLocalizedText("start_name_hint_whom_see")>> <<textbox "$gn_f_vizhu" "Карину">> <<= setup.getLocalizedText("start_name_hint_whom_proud")>> <<textbox "$gn_f_kem" "Кариной">> <<= setup.getLocalizedText("start_name_hint_whom_about")>> <<textbox "$gn_f_okom" "Карине">> <<= setup.getLocalizedText("start_name_hint_short")>> <<textbox "$gn_f_short" "Карин">>\ <</if>>\ <<if !_hasCases>>\ <<= setup.getLocalizedText("start_name_hint_name") || "Name">> <<textbox "$gn_f" "Karina">> <<= setup.getLocalizedText("start_name_hint_short") || "Nickname">> <<textbox "$gn_f_short" "Karina">> <</if>>\ </div>\ </div>\ </div>\ <<elseif _hasMale>>\ <!-- Режим только мужского героя -->\ <div class="page-main-disclaimer-box-2">\ <div class="page-main-disclaimer-box-title-2">\ <<= setup.getLocalizedText("start_hero_male")>>\ </div>\ <div class="page-main-disclaimer-box-text-2">\ <div class="page-main-disclaimer-box-text-add">\ <<set _hasCases to ($lang eq 'ru' || $lang eq 'uk' || $lang eq 'pl')>>\ <<if _hasCases>>\ <<= setup.getLocalizedText("start_name_hint_who")>> <<textbox "$gn_m" "Лиам">> <<= setup.getLocalizedText("start_name_hint_whom_no")>> <<textbox "$gn_m_net" "Лиама">> <<= setup.getLocalizedText("start_name_hint_whom_give")>> <<textbox "$gn_m_dam" "Лиаму">> <<= setup.getLocalizedText("start_name_hint_whom_see")>> <<textbox "$gn_m_vizhu" "Лиама">> <<= setup.getLocalizedText("start_name_hint_whom_proud")>> <<textbox "$gn_m_kem" "Лиамом">> <<= setup.getLocalizedText("start_name_hint_whom_about")>> <<textbox "$gn_m_okom" "Лиаме">> <<= setup.getLocalizedText("start_name_hint_short")>> <<textbox "$gn_m_short" "Ли">> <</if>>\ <<if !_hasCases>>\ <<= setup.getLocalizedText("start_name_hint_name") || "Name">> <<textbox "$gn_m" "Liam">> <<= setup.getLocalizedText("start_name_hint_short") || "Nickname">> <<textbox "$gn_m_short" "Liam">> <</if>>\ </div>\ </div>\ </div>\ <<elseif _hasFemale>>\ <!-- Режим только женского героя -->\ <div class="page-main-disclaimer-box-2">\ <div class="page-main-disclaimer-box-title-2">\ <<= setup.getLocalizedText("start_hero_female")>>\ </div>\ <div class="page-main-disclaimer-box-text-2">\ <div class="page-main-disclaimer-box-text-add">\ <<set _hasCases to ($lang eq 'ru' || $lang eq 'uk' || $lang eq 'pl')>>\ <<if _hasCases>>\ <<= setup.getLocalizedText("start_name_hint_who")>> <<textbox "$gn_f" "Карина">> <<= setup.getLocalizedText("start_name_hint_whom_no")>> <<textbox "$gn_f_net" "Карины">> <<= setup.getLocalizedText("start_name_hint_whom_give")>> <<textbox "$gn_f_dam" "Карине">> <<= setup.getLocalizedText("start_name_hint_whom_see")>> <<textbox "$gn_f_vizhu" "Карину">> <<= setup.getLocalizedText("start_name_hint_whom_proud")>> <<textbox "$gn_f_kem" "Кариной">> <<= setup.getLocalizedText("start_name_hint_whom_about")>> <<textbox "$gn_f_okom" "Карине">> <<= setup.getLocalizedText("start_name_hint_short")>> <<textbox "$gn_f_short" "Карин">>\ <</if>>\ <<if !_hasCases>>\ <<= setup.getLocalizedText("start_name_hint_name") || "Name">> <<textbox "$gn_f" "Karina">> <<= setup.getLocalizedText("start_name_hint_short") || "Nickname">> <<textbox "$gn_f_short" "Karina">> <</if>>\ </div>\ </div>\ </div>\ <</if>>\ </div>\ </div>\ </div>\ </div>\ <div class="page-futer">\ <<L "btn_introduction" "Start" "$page_id = 3">>\ </div>\ </div>\ <<elseif $page_id == 3>>\ <<set $page_id = 0>>\ <<set _hasCases to ($lang eq 'ru' || $lang eq 'uk' || $lang eq 'pl')>>\ <<if !_hasCases>>\ <<set $gn_m_net to $gn_m>>\ <<set $gn_m_dam to $gn_m>>\ <<set $gn_m_vizhu to $gn_m>>\ <<set $gn_m_kem to $gn_m>>\ <<set $gn_m_okom to $gn_m>>\ <<set $gn_f_net to $gn_f>>\ <<set $gn_f_dam to $gn_f>>\ <<set $gn_f_vizhu to $gn_f>>\ <<set $gn_f_kem to $gn_f>>\ <<set $gn_f_okom to $gn_f>>\ <</if>>\ <<set _gameSettings to setup.game_parameters?.game_settings>>\ <<set _availableHeroes to _gameSettings?.available_protagonists || ["gg_m", "gg_f"]>>\ <<if !_availableHeroes.includes("gg_m") && _availableHeroes.includes("gg_f")>>\ <<set $gg = 1>>\ <<elseif !_availableHeroes.includes("gg_f") && _availableHeroes.includes("gg_m")>>\ <<set $gg = 0>>\ <</if>>\ <<startQuest "home/prolog">>\ <</if>>\
<<nobr>> <<if not def $gameInitialized>> /* =================================================== * ИНИЦИАЛИЗАЦИЯ ИГРОВЫХ ПЕРЕМЕННЫХ * Engine: SugarCube 2.36.1 * =================================================== */ /* === СИСТЕМА КВЕСТОВ === */ <<set $quests_state = {}>> <<set $activeQuests = []>> <<set $completedQuests = []>> <<set $questProgress = {}>> <<set $currentQuestData = null>> <<set $currentQuestId = "">> <<set $currentQuestPage = 1>> <<set $questChoice = 0>> <<set $questRoutes to {}>> <<set $show_tooltips to false>> <<set $show_hints to false>> <<set $show_scroll_buttons to true>> /* === НАВИГАЦИЯ === */ <<set $current_location to "home">> <<set $current_character to "">> <<set $current_category to "location">> <<set $nexus_mode to "quest">> <<set $selected_quest to "">> <<set $show_completed to false>> <<set $completed_filter to "all">> <<set $map_filter to "all">> <<set $image_position to { x: 50, y: 50, scale: 1 }>> /* === ПЕРЕМЕННЫЕ ВЫБОРА (choice1-choice10) === */ <<for _i to 1; _i <= 10; _i++>> <<set State.variables["choice" + _i] to 0>> <</for>> /* === СТРАНИЦЫ === */ <<set $page_id = 0>> /* === ВЫБОР ПЕРСОНАЖА === */ <<if setup.game_parameters?.game_settings?.dual_protagonist === false>> <<if setup.game_parameters.game_settings.default_protagonist === "gg_f">> <<set $gg = 1>> <<else>> <<set $gg = 0>> <</if>> <<else>> <<set $gg = 0>> <</if>> <<set $gg_quest_owner to $gg>> /* === ИМЕНА ПЕРСОНАЖЕЙ === */ <<set $gn = "">> <<set $gn_net = "">> <<set $gn_dam = "">> <<set $gn_vizhu = "">> <<set $gn_kem = "">> <<set $gn_okom = "">> <<set $gn_short = "">> <<set $gn_m = "Лиам">> <<set $gn_m_net = "Лиама">> <<set $gn_m_dam = "Лиаму">> <<set $gn_m_vizhu = "Лиама">> <<set $gn_m_kem = "Лиамом">> <<set $gn_m_okom = "Лиаме">> <<set $gn_m_short = "Ли">> <<set $gn_f = "Карина">> <<set $gn_f_net = "Карины">> <<set $gn_f_dam = "Карине">> <<set $gn_f_vizhu = "Карину">> <<set $gn_f_kem = "Кариной">> <<set $gn_f_okom = "Карине">> <<set $gn_f_short = "Карин">> /* === ВИЗУАЛЬНЫЕ ЭЛЕМЕНТЫ === */ <<set $currentBackground = "">> <<set $currentSideImage = "">> /* === МОДИФИКАТОРЫ (баффы/дебаффы) === */ <<set $modifiers = { global: [], gg_m: [], gg_f: [] }>> /* === ХАРАКТЕРИСТИКИ === */ <<set $stats = {}>> <<set $reputation = {}>> <<set $relations = {}>> /* === ИНВЕНТАРЬ И ЭКИПИРОВКА === */ <<set $inv_main_storage = {}>> <<set $characters = {}>> <<set $inventory_filter = "main_storage">> /* === НАСТРОЙКИ ЗВУКА === */ <<set $settings = { masterVolume: 1.0, musicVolume: 1.0, sfxVolume: 1.0, ambientVolume: 1.0, videoVolume: 1.0, isMuted: false }>> /* === UI === */ <<set $active_gg_tab to "character">> /* === ЛОКАЛИЗАЦИЯ === */ <<set $lang = "ru">> /* === ФЛАГ ИНИЦИАЛИЗАЦИИ === */ <<set $gameInitialized = true>> <</if>> <</nobr>>
<div class="page-header">\ <div class="page-header-tittle">\ <div class="page-header-tittle-boxone">\ <<pageTitle>>\ </div>\ </div>\ <div class="page-header-menu">\ <<if passage() is "scene">>\ <img @class="'btn-toggle-gg-panel' + ($gg_panel_hidden ? ' active' : '')" src="img/ico/ico-leftpanel.webp" data-action="toggle-gg-panel" @data-tooltip="setup.getLocalizedText('tooltip_toggle_gg_panel') || 'Показать/Скрыть панель'">\ <</if>>\ <<mediaControls>>\ <<set _langConfig to setup.DataLoader?.config?.languages>>\ <<set _availableLangs to _langConfig?.available || ['ru', 'en']>>\ <<set _langNames to _langConfig?.names || {ru: 'Русский', en: 'English'}>>\ <<set _currentIndex to _availableLangs.indexOf($lang)>>\ <<set _nextLang to _availableLangs[(_currentIndex + 1) % _availableLangs.length]>>\ <span @data-tooltip="_langNames[_nextLang] || _nextLang"><<link [img['img/ico/flag_' + _nextLang + '.webp']]>><<run setup.switchLanguage(_nextLang)>><</link>></span>\ /* История (мелкие шаги) */ <span @data-tooltip="setup.getLocalizedText('tooltip_step_back') || 'Шаг назад'"><<link [img[img/ico/ico-backward.webp]]>><<run Engine.backward()>><</link>></span>\ <span @data-tooltip="setup.getLocalizedText('tooltip_step_forward') || 'Шаг вперёд'"><<link [img[img/ico/ico-forward.webp]]>><<run Engine.forward()>><</link>></span>\ /* Чекпоинты (крупные точки) */ <span @data-tooltip="setup.getLocalizedText('tooltip_checkpoint_prev') || 'Предыдущий чекпоинт'"><<link [img[img/ico/ico-step-back.webp]]>><<run setup.CheckpointSystem.restorePrevious()>><</link>></span>\ <span @data-tooltip="setup.getLocalizedText('tooltip_checkpoint_next') || 'Следующий чекпоинт'"><<link [img[img/ico/ico-step-forward.webp]]>><<run setup.CheckpointSystem.restoreNext()>><</link>></span>\ <span @data-tooltip="setup.getLocalizedText('tooltip_settings') || 'Настройки'"><<link [img[img/ico/ico-config.webp]]>><<run setup.SettingsModal.open('game')>><</link>></span>\ <span @data-tooltip="setup.getLocalizedText('tooltip_saves') || 'Сохранения'"><<link [img[img/ico/ico-save.webp]]>><<run setup.SettingsModal.open('saves')>><</link>></span>\ </div>\ </div>\
<div class="page-main-background">\ <<nobr>> <<set _passageName to passage()>> <<set _bgImage to "">> <!-- НОВАЯ СИСТЕМА: Читаем из BackgroundSystem --> <<if _passageName === "scene">> <!-- ✅ ИСПРАВЛЕНО: Для scene приоритет $currentBackground (сохраняется между страницами) --> <<set _bgImage to $currentBackground || BackgroundSystem.state.current>> <!-- Синхронизируем BackgroundSystem.state.current если он пустой --> <<if _bgImage && !BackgroundSystem.state.current>> <<run BackgroundSystem.state.current = _bgImage>> <</if>> <<elseif _passageName === "nexus">> <!-- Фон nexus всегда вычисляем от ТЕКУЩЕЙ локации (само-исцеляется при смене режима/выходе из карты). ВАЖНО: getLayers возвращает [] (truthy!) если слоёв нет — поэтому проверяем .length. --> <<set _locLayers to setup.LayerSystem ? (setup.LayerSystem.getLayers('@' + ($current_location || 'home'), {type: 'background'}) || []) : []>> <<if _locLayers && _locLayers.length>> <<set _bgImage to _locLayers>> <<else>> <<set _bgImage to BackgroundSystem.state.current || $currentBackground>> <</if>> <<else>> <<set _bgImage to BackgroundSystem.getPassageBackground(_passageName) || "">> <</if>> <!-- Рендерим фон с поддержкой слоёв --> <<if _bgImage>> <<if Array.isArray(_bgImage)>> <!-- Многослойный фон --> <div class="background-layers-grid"> <<for _i to 0; _i lt _bgImage.length; _i++>> <img class="background-layer img-fon" @src="_bgImage[_i]" alt=""> <</for>> </div> <<else>> <!-- Одиночный фон --> <img class="img-fon" @src="_bgImage" alt=""> <</if>> <<else>> <!-- Дефолтный фон если ничего не установлено --> <img class="img-fon" src="img/placeholder.webp" alt=""> <</if>> <</nobr>>\ </div>\
<div @class="'page-gg-box' + ($gg_panel_hidden ? ' hidden' : '')">\ <div class="page-gg-box-info">\ <<timeDisplay>>\ <div class="page-gg-box-info-money">\ <<displayMoney>>\ </div>\ </div>\ <<GGBoxSkin>>\ <div class="page-gg-box-bottom">\ <div class="page-gg-box-effects">\ <<displayEffects>>\ </div>\ <div class="page-gg-box-bar">\ <<displayGGBars>>\ </div>\ </div>\ </div>\
<div class="page">\ <<nobr>> <!-- Проверка загрузки данных после F5 --> <<if not setup.locations or not setup.characters or not State.variables.dataLoaded>> <<script>> if (!State.temporary.waitingForData) { State.temporary.waitingForData = true; var checkInterval = setInterval(function() { if (State.variables.dataLoaded && setup.locations && setup.characters) { clearInterval(checkInterval); State.temporary.waitingForData = false; Engine.play('nexus'); } }, 100); } <</script>> <div style="text-align: center; padding: 50px;"> <h2><<L "loading_data">></h2> <p><<L "loading_please_wait">></p> </div> <<else>> <!-- Инициализация постоянных переменных --> <<if not $current_location>> <<set $current_location to "home">> <</if>> <<if not $current_category_id>> <<set $current_category_id to "home">> <</if>> <<if not $current_category_type>> <<set $current_category_type to "location">> <</if>> <!-- Простая инициализация переменных по умолчанию --> <<if typeof $nexus_mode === "undefined">> <<set $nexus_mode to "quest">> <</if>> <!-- Инициализация фильтров инвентаря --> <<if typeof $inventory_filter === "undefined">> <<set $inventory_filter to "main_storage">> <</if>> <<if typeof $item_type_filter === "undefined">> <<set $item_type_filter to "all">> <</if>> <<if typeof $current_shop_id === "undefined">> <<set $current_shop_id to "">> <</if>> <!-- НЕ изменяем переменные после восстановления - они уже правильно установлены --> <</if>> <</nobr>>\ <<include "page-header">>\ <div class="page-main">\ <<nexusBody>>\ </div>\ </div>\
/* * Copyright 2026 LoomCraft (https://loomcraft.net) * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ <<nobr>> <!-- Виджет --> <!-- Виджет Загрузки квеста. Внимание! Оасно!--> <<widget "startQuest">> <<nobr>> <<set _questId to _args[0]>> <<if !_questId>> <div class="error">Ошибка: не указан ID квеста для запуска</div> <<else>> <<script>> (async function() { const questId = State.temporary.questId; const loading = document.createElement('div'); loading.textContent = 'Загрузка квеста...'; loading.style.position = 'fixed'; loading.style.top = '50%'; loading.style.left = '50%'; loading.style.transform = 'translate(-50%, -50%)'; loading.style.background = 'rgba(0,0,0,0.8)'; loading.style.color = 'white'; loading.style.padding = '20px'; loading.style.borderRadius = '10px'; loading.style.zIndex = '10000'; document.body.appendChild(loading); try { if (setup.MusicManager) { setup.MusicManager.stopAllLocationMusic(500); } const questData = await setup.QuestLoader.loadQuest(questId); setup.resetQuestChoices(); State.variables.currentQuestId = questId; State.variables.activeMedia = {}; if (setup.MediaStateStorage) setup.MediaStateStorage.clear(); State.variables.currentQuestPage = 1; State.variables.currentSideImage = ''; State.variables.gg_quest_owner = State.variables.gg; if (questData) { State.variables.currentQuestData = questData; State.variables.currentQuestId = questId; State.variables.currentQuestPage = questData.startPage || 1; if (!State.variables.questProgress) { State.variables.questProgress = {}; } if (!State.variables.questProgress[questId]) { State.variables.questProgress[questId] = { currentPage: questData.startPage || 1, startTime: Date.now() }; } if (loading && loading.parentNode) { loading.remove(); } if (questData.pages && questData.pages[1]) { for (let element of questData.pages[1].content || []) { if (element.type === 'bg') { const bgResult = BackgroundSystem.processBackgroundElement(element); if (bgResult) { State.variables.currentBackground = bgResult; BackgroundSystem.state.current = bgResult; } break; } } } setTimeout(() => { Engine.play('scene'); }, 10); } else { if (loading && loading.parentNode) { loading.remove(); } alert('Ошибка загрузки квеста: данные не найдены'); } } catch (error) { console.error('Ошибка при запуске квеста:', error); if (loading && loading.parentNode) { loading.remove(); } alert('Произошла ошибка при загрузке квеста: ' + error.message); } })(); <</script>> <</if>> <</nobr>> <</widget>> <!-- Виджет Парсера txt файла квеста. Внимание! Оасно!--> <<widget "displayQuestContent">> <<nobr>> <<set _questId to _args[0]>> <<if !setup.questDefinitions>> <div class="loading-text"><<print setup.getLocalizedText("loading_game_data")>></div> <<elseif _questId and $currentQuestPage>> <div id="quest-content-container"> <div class="loading-text"><<print setup.getLocalizedText("loading_page")>></div> </div> <<script>> (async function() { const questId = State.temporary.questId || State.variables.currentQuestId; const pageNum = State.variables.currentQuestPage; const previousBackground = State.variables.currentBackground; const pageData = await setup.QuestLoader.loadQuestPageDirect(questId, pageNum); if (pageData) { const container = document.getElementById('quest-content-container'); State.temporary.page = pageData; State.variables.pageData = pageData; container.innerHTML = ''; new Wikifier(container, '<<displayQuestPageContent $pageData>>'); if (State.variables.questProgress && State.variables.questProgress[questId]) { State.variables.questProgress[questId].currentPage = pageNum; } if (State.variables.activeMedia && Object.keys(State.variables.activeMedia).length > 0) { setTimeout(() => { for (const [type, mediaData] of Object.entries(State.variables.activeMedia)) { if (type === 'ambient') continue; const isPlaying = setup.MediaSystem.active[type].has(mediaData.id || mediaData.path); if (!isPlaying) { const markers = document.querySelectorAll('.media-change-marker'); markers.forEach(marker => { if (marker.dataset.mediaType === type && (marker.dataset.mediaId === mediaData.id || marker.dataset.mediaId === mediaData.path)) { marker.dataset.activated = 'true'; marker.remove(); } }); const element = setup.MediaPlayer.play(type, mediaData.id || mediaData.path, { volume: mediaData.volume, loop: mediaData.loop, variant: mediaData.variant, autoplay: true }); if (element) { } } } }, 100); } if (window.BackgroundSystem && State.variables.currentBackground && !BackgroundSystem.state.current) { BackgroundSystem.state.current = State.variables.currentBackground; } if (window.BackgroundSystem && window.BackgroundSystem.observeMarkers) { setTimeout(function() { window.BackgroundSystem.observeMarkers(); }, 200); } } else { const container = document.getElementById('quest-content-container'); const errorText = setup.getLocalizedText('error_quest_page_load') || 'Ошибка загрузки страницы квеста'; const btnBack = setup.getLocalizedText('btn_error_go_back') || 'Вернуться назад'; const btnHome = setup.getLocalizedText('btn_error_go_home') || 'На базу'; container.innerHTML = ` <div class="error"> <p>${errorText}</p> <div class="error-buttons"> <button class="btn-error-back">${btnBack}</button> <button class="btn-error-home">${btnHome}</button> </div> </div> `; container.querySelector('.btn-error-back').addEventListener('click', function() { if (setup.CheckpointSystem && setup.CheckpointSystem.canRestorePrevious()) { setup.CheckpointSystem.restorePrevious(); } else { State.variables.current_location = State.variables.current_location || 'home'; State.variables.current_category = 'location'; Engine.play('nexus'); } }); container.querySelector('.btn-error-home').addEventListener('click', function() { State.variables.current_location = 'home'; State.variables.current_category = 'location'; Engine.play('nexus'); }); } })(); <</script>> <<else>> <div class="error"> Ошибка: не указан ID квеста или номер страницы <br><br> <<L "Return_Nexus" "nexus">> </div> <</if>> <</nobr>> <</widget>> <!-- Виджет Парсера txt файла квеста. Внимание! Оасно!--> <<widget "displayQuestPageContent">> <<nobr>> <<set _page to State.temporary.page>> <<if _page>> <<set $narrator to _page.narrator || null>> <<run setup.refreshGGBox()>> <<run setup.updatePageTitle()>> <<set _lastBackground to null>> <<set _containers to []>> <<set _currentContainer to { content: [], sideImageGroups: [], background: null }>> <<set _needBackgroundUpdate to false>> <<set _firstBgApplied to false>> <<set _hasHadContainerBreak to false>> <<for _elementIndex to 0; _elementIndex lt _page.content.length; _elementIndex++>> <<set _element to _page.content[_elementIndex]>> <<switch _element.type>> <<case "container_break">> <<if _currentContainer.content.length > 0 || _currentContainer.sideImageGroups.length > 0>> <<set _currentContainer.background to _lastBackground>> <<set _containers.push(_currentContainer)>> <</if>> <<set _currentContainer to { content: [], sideImageGroups: [], background: null }>> <<set _needBackgroundUpdate to true>> <<set _hasHadContainerBreak to true>> <<case "bg">> <<set _bgData to _element.data>> <<set _bgResult to null>> <<if _bgData.startsWith('@')>> <<set _bgResult to BackgroundSystem.get(_bgData, 'default')>> <<elseif _bgData.includes('.') && !_bgData.includes('/')>> <<set _parts to _bgData.split('.')>> <<set _bgResult to BackgroundSystem.get(_parts[0], _parts[1])>> <<else>> <<set _bgResult to BackgroundSystem.get(_bgData, 'default') || _bgData>> <</if>> <<if _bgResult>> <<set _lastBackground to _bgResult>> <!-- ✅ ИСПРАВЛЕНО: Применяем глобально при ПЕРВОМ [bg] на странице --> <<if !_firstBgApplied && !_hasHadContainerBreak>> <<set _firstBgApplied to true>> <<set $currentBackground to _bgResult>> <<run BackgroundSystem.state.current = _bgResult>> <<run BackgroundSystem.updateDOM(_bgResult)>> <</if>> <</if>> <<case "side">> <<set _sideData to _element.data>> <<set _imageGroup to []>> <<if _sideData.includes('/') || _sideData.endsWith('.png') || _sideData.endsWith('.jpg') || _sideData.endsWith('.webp')>> <!-- Прямая ссылка - одна картинка --> <<set _imageGroup to [_sideData]>> <<else>> <!-- Используем LayerSystem для получения слоев --> <<set _sideLayers to setup.LayerSystem.getLayers(_sideData, {type: 'skin', withMeta: true})>> <<if _sideLayers && _sideLayers.length > 0>> <<set _imageGroup to _sideLayers>> <</if>> <</if>> <!-- Добавляем группу как отдельный элемент --> <<if _imageGroup.length > 0>> <<set _currentContainer.sideImageGroups.push(_imageGroup)>> <</if>> <<case "music" "sfx" "ambient" "video">> <!-- ДОБАВЛЯЕМ МЕДИА ЭЛЕМЕНТЫ В КОНТЕЙНЕР --> <<set _currentContainer.content.push(_element)>> <<default>> <!-- Все остальные элементы добавляем в контент --> <<set _currentContainer.content.push(_element)>> <</switch>> <</for>> <!-- Добавляем последний контейнер --> <<if _currentContainer.content.length > 0 || _currentContainer.sideImageGroups.length > 0>> <<set _currentContainer.background to _lastBackground>> <<set _containers.push(_currentContainer)>> <</if>> <!-- Рендерим контейнеры --> <<for _containerIndex to 0; _containerIndex lt _containers.length; _containerIndex++>> <<set _container to _containers[_containerIndex]>> <!-- Устанавливаем фон для первого контейнера --> <<if _containerIndex === 0 && !_container.background && $currentBackground>> <!-- Если у первого контейнера нет фона - используем текущий --> <<set _container.background to $currentBackground>> <</if>> <<set _isLastContainer to (_containerIndex === _containers.length - 1)>> <!-- Маркер для смены фона в обоих направлениях --> <<if _container.background && (_containerIndex === 0 || _containers[_containerIndex-1].background !== _container.background)>> <<set _previousBg to _containerIndex > 0 ? _containers[_containerIndex-1].background : ($currentBackground || '')>> <div class="bg-change-marker" @data-bg="_container.background" @data-previous-bg="_previousBg" data-direction="both" style="height: 1px; margin: -1px; opacity: 0; pointer-events: none;"></div> <</if>> <div class="div-story-container"> <!-- Боковые изображения - ЕДИНЫЙ ПУТЬ --> <<if _container.sideImageGroups && _container.sideImageGroups.length > 0>> <div class="div-story-box-img"> <<for _groupIndex to 0; _groupIndex lt _container.sideImageGroups.length; _groupIndex++>> <<set _group to _container.sideImageGroups[_groupIndex]>> <div class="side-image-group"> <<for _imgIndex to 0; _imgIndex lt _group.length; _imgIndex++>> <<set _sit to _group[_imgIndex]>> <<set _sitSrc to (typeof _sit === 'string') ? _sit : _sit.image>> <<set _sitAnim to (typeof _sit === 'object' && _sit && _sit.anim) ? _sit.anim : ''>> <img @src="_sitSrc" @class="'img-div-story-box-img' + (_sitAnim ? ' anim-' + _sitAnim : '')" alt=""> <</for>> </div> <</for>> </div> <</if>> <!-- Блок с текстом --> <div class="div-story-box-text"> <div class="narrator-story-text"> <!-- Элементы контента --> <<if _container.content && _container.content.length > 0>> <<for _contentIndex to 0; _contentIndex lt _container.content.length; _contentIndex++>> <<set _element to _container.content[_contentIndex]>> <!-- Проверяем, это медиа элемент --> <<if (_element.type === "music" || _element.type === "sfx" || _element.type === "ambient" || _element.type === "video")>> <!-- Создаем маркер для медиа БЕЗ воспроизведения --> <<run _mediaParams = JSON.stringify(_element)>> <div class="media-change-marker" @data-media-type="_element.type" @data-media-id="_element.id || _element.path || ''" @data-media-params="_mediaParams" data-autoplay="false" data-direction="both" style="height: 1px; margin: -1px; opacity: 0; pointer-events: none;"></div> <<else>> <!-- Обычный рендеринг для других элементов --> <<print setup.renderContentElement(_element)>> <</if>> <</for>> <</if>> <!-- Выборы только в последнем контейнере ВНУТРИ narrator-story-text --> <<if _isLastContainer && _page.choices && _page.choices.length > 0>> <!-- Проверяем required pick --> <<set _pickBlocked to (setup.PickSystem && !setup.PickSystem.checkRequired())>> <div @class="'choice-story-menu' + (_pickBlocked ? ' pick-blocked' : '') + ($show_hints ? ' hints-on' : ' hints-off')"> <<for _choiceIndex to 0; _choiceIndex lt _page.choices.length; _choiceIndex++>> <<capture _choiceIndex>> <<set _choice to _page.choices[_choiceIndex]>> <div class="choice-story-box"> <!-- Описание выбора если есть --> <<if _choice.description>> <div class="desc-choice-story-box"> <div class="desc-choice-story-box-text"><<= _choice.description>></div> <!-- Эффекты выбора --> <<if _choice.effects && Object.keys(_choice.effects).length > 0>> <<for _effect, _value range _choice.effects>> <div class="choice-story-stat-box"> <<= _effect>>: <<= _value>> </div> <</for>> <</if>> </div> <</if>> <!-- Сама кнопка выбора --> <span class="choice-story-text"> <<if _pickBlocked>> <!-- Заблокировано - не все required pick выбраны --> <span class="choice-disabled"><<= setup.renderChoiceLabel(_choice)>></span> <<elseif _choice.action === "next">> <<link setup.renderChoiceLabel(_choice)>> <<run setup.makeQuestChoice(_choiceIndex, _page.choices[_choiceIndex].effects)>> <<set $currentQuestPage to _page.choices[_choiceIndex].value>> <<run Engine.play('scene')>> <</link>> <<elseif _choice.action === "passage">> <<link setup.renderChoiceLabel(_choice) _choice.target>> <<run setup.makeQuestChoice(_choiceIndex, _page.choices[_choiceIndex].effects)>> <</link>> <<elseif _choice.action === "complete">> <<link setup.renderChoiceLabel(_choice)>> <<run setup.makeQuestChoice(_choiceIndex, _page.choices[_choiceIndex].effects)>> <<set _wasInteraction to $isInteraction>> <<set _returnNpc to $current_character>> <<if $isInteraction>> <<if $interactionReward>> <<run setup.applyInteractionReward($interactionReward)>> <</if>> <<set $isInteraction to false>> <<set $interactionReward to null>> <<else>> <<run setup.completeQuest($currentQuestId)>> <</if>> <<run setup.ForcedEvents?.handleExit()>> <<if _wasInteraction && _returnNpc>> <<set $current_character to _returnNpc>> <<set $current_category to 'character'>> <</if>> <<set $selected_quest to ''>> <<run Engine.play('nexus')>> <</link>> <</if>> </span> </div> <</capture>> <</for>> </div> <</if>> </div> <!-- закрывается narrator-story-text --> </div> </div> <</for>> <<else>> <div class="error"> <p>Ошибка: данные страницы не загружены</p> <<link "Вернуться назад">> <<if setup.CheckpointSystem && setup.CheckpointSystem.canRestore()>> <<run setup.CheckpointSystem.restore()>> <<else>> <<set $current_location to $current_location || "home">> <<set $current_category to "location">> <<goto "nexus">> <</if>> <</link>> <<link "На базу (home)">> <<set $current_location to "home">> <<set $current_category to "location">> <<goto "nexus">> <</link>> </div> <</if>> <<script>> setTimeout(function() { var storyDiv = document.querySelector('.div-story'); var containers = document.querySelectorAll('.div-story-container'); if (storyDiv && containers.length > 0) { var lastContainer = containers[containers.length - 1]; var containerRect = lastContainer.getBoundingClientRect(); var storyRect = storyDiv.getBoundingClientRect(); if (containerRect.bottom < storyRect.bottom) { var additionalHeight = storyRect.bottom - containerRect.bottom; lastContainer.style.minHeight = 'calc(100% + ' + additionalHeight + 'px)'; } } }, 100); <</script>> <</nobr>> <</widget>> <<widget "say">> <<nobr>> <<set _rawSpeaker to _args[0] || "narrator">> <<set _text to _args[1] || "">> <<set _isThought to _args[2] || false>> <!-- Парсим параметры через | --> <<set _parts to _rawSpeaker.split('|')>> <<set _speaker to _parts[0]>> <<set _colorOverride to _parts[1] || "">> <<set _customImage to _parts[2] || "">> <<if _speaker == "narrator">> <<= _text>> <<else>> <!-- Обрабатываем спикера для получения правильного имени --> <<set _displayName to _speaker>> <<set _baseSpeaker to _speaker>> <!-- Обработка составных имён --> <<if _speaker.includes(' ')>> <<set _displayName to _speaker>> <<set _baseSpeaker to "nps">> <<set _textClass to "nps-story-text">> <!-- Добавить --> <<set _nameClass to "nps-story-name">> <!-- Добавить --> <<else>> <!-- Используем processCompoundName чтобы получить отображаемое имя --> <<set _displayName to setup.processCompoundName(_speaker)>> <!-- Определяем базового спикера для аватарки --> <<if _speaker == "$gg">> <<set _baseSpeaker to State.variables.gg === 0 ? "gg_m" : "gg_f">> <<elseif _speaker.startsWith('$')>> <<set _baseSpeaker to _speaker.substring(1)>> <<elseif _speaker.startsWith('#')>> <<set _baseSpeaker to _speaker.substring(1)>> <<else>> <!-- Обычное имя без префикса - используем nps --> <<set _baseSpeaker to "nps">> <<set _textClass to "nps-story-text">> <<set _nameClass to "nps-story-name">> <</if>> <</if>> <!-- Определяем классы стилей --> <<set _textClass to "nps-story-text">> <<set _nameClass to "nps-story-name">> <<if _baseSpeaker == "gg_m">> <<set _textClass to "gg-m-story-text">> <<set _nameClass to "gg-m-story-name">> <<elseif _baseSpeaker == "gg_f">> <<set _textClass to "gg-f-story-text">> <<set _nameClass to "gg-f-story-name">> <</if>> <!-- Получаем аватарку и цвет --> <<set _avatarData to setup.getAvatar(_baseSpeaker, _customImage)>> <<set _borderColor to _colorOverride || _avatarData.color || "#f0e68c">> <!-- Рендерим диалог --> <div class="pers-story-box"> <<if _displayName and not _isThought>> <div @class="_nameClass" @style="'border-color: ' + _borderColor + '; color: ' + _borderColor"> <<= _displayName>> </div> <</if>> <div @class="_textClass" @style="'border-color: ' + _borderColor"> <!-- Многослойная аватарка --> <<set _layers to _avatarData.layers || (_avatarData.image ? [_avatarData.image] : [])>> <<if _layers.length > 0>> <div class="ava-container"> <<for _layer range _layers>> <img class="ava-text" @src="_layer" @style="'border-color: ' + _borderColor + '; grid-area: stack; margin: 0;'" alt=""> <</for>> </div> <</if>> <div class="dialog-text-content"> <<if _isThought>> <span class="thought-text"><<= _text>></span> <<else>> — <<= _text>> <</if>> </div> </div> </div> <</if>> <</nobr>> <</widget>> <<widget "renderSceneButtons">> <<nobr>> <<set _questData to $currentQuestData>> <<set _currentPage to $currentQuestPage || 1>> <<if _questData and _questData.pages and _questData.pages[_currentPage]>> <<set _page to _questData.pages[_currentPage]>> <<if _page.sceneButtons and _page.sceneButtons.length > 0>> <<for _btn range _page.sceneButtons>> <<if _btn.isScenePage>> <!-- Переход на страницу квеста --> <<set _btnText to setup.L10n[_btn.l10n] || _btn.l10n>> <<capture _btn>> <<link _btnText>> <<run setup.commitPendingSets()>> <<if _btn.effects && Object.keys(_btn.effects).length gt 0>> <<run setup.makeQuestChoice(-1, _btn.effects)>> <</if>> <<if _btn.isComplete>> <<run setup.completeQuest($currentQuestId)>> <</if>> <<set $currentQuestPage to _btn.pageNum>> <<goto "scene">> <</link>> <</capture>> <<else>> <!-- Обычный переход на пассаж --> <<capture _btn>> <<set _btnText to setup.L10n[_btn.l10n] || _btn.l10n>> <<link _btnText>> <<run setup.commitPendingSets()>> <<if _btn.effects && Object.keys(_btn.effects).length gt 0>> <<run setup.makeQuestChoice(-1, _btn.effects)>> <</if>> <<if _btn.isComplete>> <<run setup.completeQuest($currentQuestId)>> <</if>> <<if $isInteraction && $interactionReward && (_btn.passage === "nexus" || _btn.passage === "location")>> <<run setup.applyInteractionReward($interactionReward)>> <<set $isInteraction to false>> <<set $interactionReward to null>> <</if>> <<goto _btn.passage>> <</link>> <</capture>> <</if>> <</for>> <</if>> <</if>> <</nobr>> <</widget>> <!-- Виджет для проверки условий квеста --> <!-- Вспомогательный виджет для обработки эффектов --> <<widget "completeQuest">> <<nobr>> <<set _questId to _args[0] || $currentQuestId>> <<if _questId>> <<run setup.completeQuest(_questId)>> <</if>> <</nobr>> <</widget>> <!-- Виджет --> <<widget "displayQuestList">> <<nobr>> <<set _catId to _args[0] || $current_character || $current_location || "home">> <<set _showCompleted to _args[1] || false>> <!-- Получаем квесты для категории --> <<set _categoryQuests to []>> <<if setup.questDefinitions>> <<set _currentHeroId to ($gg === 0) ? "gg_m" : "gg_f">> <<for _questId, _questData range setup.questDefinitions>> <<if _questData.category_id === _catId>> <!-- Пропускаем forced квесты --> <<if _questData.data && _questData.data.type === "forced">> <<continue>> <</if>> <!-- Проверяем гендер квеста --> <<set _questGender to _questData.data.gender || "any">> <<if _questGender === "any" || _questGender === _currentHeroId>> <!-- Проверяем requirements (access) --> <<set _meetsRequirements to setup.checkRequirements(_questId)>> <<if (_showCompleted && $completedQuests.includes(_questId)) || (!_showCompleted && !$completedQuests.includes(_questId) && _meetsRequirements && ($activeQuests.includes(_questId) || _questData.status === "active" || _questData.status === "dev" || _questData.status === "preview"))>> <<set _categoryQuests.push({ id: _questId, data: _questData })>> <</if>> <</if>> <</if>> <</for>> <</if>> <!-- Отображаем квесты --> <<if _categoryQuests.length > 0>> <<for _quest range _categoryQuests>> <<set _questTitle to setup.L10n[_quest.data.title] || _quest.data.title>> <<set _questType to _quest.data.data.type || "side">> <<set _questGender to _quest.data.data.gender || "any">> <div class="div-quest-list-quest"> <div @class="'quest-card-link quest-type-' + _questType + ' quest-gender-' + _questGender" @style="'border-color: ' + setup.getQuestTypeColor(_questType) + '; --quest-glow-color: ' + setup.getQuestTypeColor(_questType)" @data-quest-id="_quest.id"> <a class="link-internal"> <span class="quest-title-text" @style="'color: ' + setup.getGenderColor(_questGender)"><<print _questTitle>></span> </a> </div> </div> <</for>> <<else>> <div class="no-quests"> <<if _showCompleted>> <<print setup.getLocalizedText('no_completed_quests') || 'Нет завершённых квестов'>> <<else>> <<print setup.getLocalizedText('no_available_quests') || 'Нет доступных квестов'>> <</if>> </div> <</if>> <</nobr>> <</widget>> <<widget "displayAllCategories">> <<nobr>> <div class="map-grid-container"> <<set _allCategories to []>> <!-- Собираем ВСЕ категории --> <<set _filteredLocs to setup.getFilteredLocations ? setup.getFilteredLocations() : {}>> <<if setup.locations && _filteredLocs>> <<for _locId, _locData range _filteredLocs>> <<set _questStats to setup.getCachedQuestStats(_locId)>> <<set _questCount to {total: _questStats.total, active: _questStats.active, completed: _questStats.completed}>> <<set _questType to _questStats.highestType>> <<set _allCategories.push({ id: _locId, data: _locData, type: 'location', questCount: _questCount, questType: _questType, priorityNum: setup.questTypePriority[_questType] || 999 })>> <</for>> <</if>> <<if setup.characters>> <<for _charId, _charData range setup.getCharactersForCards()>> <<if (_charData.shortcuts && _charData.shortcuts.type && _charData.shortcuts.type.includes("character"))>> <<set _questStats to setup.getCachedQuestStats(_charId)>> <<set _questCount to {total: _questStats.total, active: _questStats.active, completed: _questStats.completed}>> <<set _questType to _questStats.highestType>> <<set _allCategories.push({ id: _charId, data: _charData, type: 'character', questCount: _questCount, questType: _questType, priorityNum: setup.questTypePriority[_questType] || 999 })>> <</if>> <</for>> <</if>> <!-- Сортируем по приоритету типа квеста, затем по количеству активных --> <<set _allCategories.sort(function(a, b) { if (a.priorityNum !== b.priorityNum) { return a.priorityNum - b.priorityNum; } return (b.questCount && b.questCount.active || 0) - (a.questCount && a.questCount.active || 0); })>> <!-- Отображаем все категории --> <<for _cat range _allCategories>> <<set _catName to setup.getCategoryName(_cat.id)>> <<set _catLayers to (_cat.type === 'character' ? ((_cat.id === 'gg_m' || _cat.id === 'gg_f') ? setup.LayerSystem.getLayers('$' + _cat.id, {type: 'skin', withMeta: true}) : setup.LayerSystem.getLayers('#' + _cat.id, {type: 'skin', withMeta: true})) : [{ image: setup.getLocationCardImage(_cat.id), anim: null }]) || []>> <<set _borderColor to setup.getQuestTypeColor(_cat.questType)>> <<set _hasActiveQuests to _cat.questCount && _cat.questCount.active > 0>> <<set _priorityClass to _hasActiveQuests ? 'priority-active' : (_cat.questCount && _cat.questCount.completed > 0 ? 'priority-completed' : 'priority-empty')>> <<set _catBars to setup.getCategoryBars(_cat.id, "compact")>> <<set _hasExtraBars to _catBars && _catBars.length > 1>> <!-- Для персонажа: если его текущая (по расписанию) локация сейчас недоступна игровой механикой (закрыта по времени/дню, или ещё не готова) — затемняем карточку и глушим клик, как у закрытых карточек-переходов между локациями (.location-closed). --> <<set _charLocId to (_cat.type === 'character') ? ((setup.NPCSimulation ? setup.NPCSimulation.getCachedLocation(_cat.id) : null) || _cat.data.location) : null>> <<set _charLocked to (_charLocId && !setup.isLocationAccessible(_charLocId))>> <div @class="'map-card ' + _priorityClass + ' quest-type-' + _cat.questType + (_charLocked ? ' category-locked' : '')" @style="'border-color: ' + _borderColor + '; --quest-glow-color: ' + _borderColor" @data-category="_charLocked ? '' : _cat.id" @data-type="_cat.type"> <<if _charLocked>> <div class="category-locked-badge" @data-tooltip="setup.getLocalizedText('npc_unavailable_now') || 'Сейчас недоступно'">🔒</div> <</if>> <div class="map-card-image-container"> <div class="category-layers-grid"> <<if _catLayers && _catLayers.length > 0>> <<for _layer range _catLayers>> <img @class="'category-layer' + (_layer.anim ? ' anim-' + _layer.anim : '')" @src="_layer.image"> <</for>> <<else>> <img class="category-layer" src="img/placeholder.webp"> <</if>> </div> <!-- Иконка info для доп. баров --> <<if _hasExtraBars>> <<set _infoBars to []>> <<for _bar range _catBars>> <<if _bar.type isnot "quest">> <<set _value to _bar.data.value || 0>> <<set _percent to Math.round((_value / _bar.data.max) * 100)>> <<set _infoBars.push({ icon: _bar.icon || "📊", title: setup.getLocalizedText(_bar.title) || _bar.title, value: _percent + "%" })>> <</if>> <</for>> <div class="map-card-info-btn" data-info-toggle @data-info-content="JSON.stringify(_infoBars)"> <span>ℹ</span> </div> <</if>> <!-- Название карточки поверх изображения --> <div class="map-card-overlay"> <h3 class="map-card-title"><<print _catName>></h3> </div> <<set _mapBars to setup.getCategoryBars(_cat.id, "compact")>> <<if _mapBars && _mapBars.length gt 0>> <div class="map-card-progress-overlay"> <<universalProgressBars _cat.id "compact" 1>> </div> <</if>> </div> </div> <</for>> </div> <</nobr>> <</widget>> <<widget "displayLocationInfo">> <<nobr>> <<set _locationId to $current_location || 'home'>> <<set _locationData to setup.locations[_locationId]>> <<set _isMap to ($nexus_mode === 'map')>> <div @class="'div-nexus-location-img' + (_isMap ? ' map-mode' : '')"> <!-- Фон локации (рисуем всегда, в т.ч. в карте; дополнительно он же уходит в page-main-background) --> <<set _bgLayers to setup.LayerSystem?.getLayers?.('@' + _locationId, {type: 'background'}) || []>> <!-- Сохранённый зум/позиция применяются СРАЗУ (инлайн), чтобы первый кадр был уже правильным --> <!-- иначе картинка рендерится в дефолте и потом прыгает к сохранённому (setTimeout в initImageDrag). --> <<set _imgPos to ($image_position && $image_position[_locationId]) || {x: 50, y: 50, scale: 1}>> <<set _imgStyle to '--img-x:' + _imgPos.x + '%; --img-y:' + _imgPos.y + '%; --img-scale:' + (_imgPos.scale || 1)>> <<if _bgLayers && _bgLayers.length gt 0>> <<for _layer range _bgLayers>> <img class="img-div-nexus-location" @src="_layer" @style="_imgStyle"> <</for>> <</if>> <!-- Режим персонажа (НЕ в карте): НПС рисуется в этом же большом блоке --> <<set _isChar to ($current_category === 'character' && $current_character && !_isMap)>> <!-- Контент поверх картинок --> <div class="div-nexus-location-img-box"> <<if _isMap>> <!-- Режим КАРТЫ: тонкая полоса 10vh — имя локации + бары (со скроллом) --> <div class="nx-layer nx-tc nx-name"> <div class="div-nexus-location-img-name"><<print setup.getCategoryName(_locationId)>></div> </div> <<set _mapBars to setup.getCategoryBars($current_location, "full")>> <<if _mapBars && _mapBars.length > 0>> <div class="nx-layer nx-tl nx-bars"> <div class="div-nexus-location-img-bar"><<universalProgressBars $current_location "full">></div> </div> <</if>> <<elseif _isChar>> <<set _charId to $current_character>> <<set _charData to setup.characters[_charId]>> <!-- Слой 1: имя ЛОКАЦИИ (центр сверху, дальний) --> <div class="nx-layer nx-tc nx-name"> <div class="div-nexus-location-img-name"><<print setup.getCategoryName(_locationId)>></div> </div> <!-- Слой 2: имя НПС + бары — верхний левый угол --> <div class="nx-layer nx-tl nx-charinfo"> <div class="div-nexus-location-img-bar char-bottom-right"> <div class="div-quest-img-name"><<print setup.getCategoryName(_charId)>></div> <<set _charBars to setup.getCategoryBars(_charId, "full")>> <<if _charBars && _charBars.length > 0>> <<universalProgressBars _charId "full">> <</if>> </div> </div> <!-- Слой 3: портрет НПС --> <div class="nx-layer nx-portrait"> <<set _cLayers to (($current_character === 'gg_m' || $current_character === 'gg_f') ? setup.LayerSystem.getLayers('$' + $current_character, {type: 'skin', withMeta: true}) : setup.LayerSystem.getLayers('#' + $current_character, {type: 'skin', withMeta: true})) || []>> <div class="div-quest-img character-interaction-container"> <<if Array.isArray(_cLayers) && _cLayers.length > 0>> <<for _cl range _cLayers>> <img @class="'img-div-quest-img quest-layer' + (_cl.anim ? ' anim-' + _cl.anim : '')" @src="_cl.image" alt="<<print setup.getCategoryName($current_character)>>"> <</for>> <<else>> <<set _cFallback to BackgroundSystem.getLocationLayers ? BackgroundSystem.getLocationLayers($current_character) : null>> <<set _cImg to (_cFallback && _cFallback[0]) || "img/placeholder.webp">> <img class="img-div-quest-img" @src="_cImg" alt="<<print setup.getCategoryName($current_character)>>"> <</if>> </div> </div> <!-- Слой 4a: кнопка «назад» — верхний правый угол, поверх портрета --> <div class="nx-layer nx-tr nx-back"> <<if $current_location>> <button class="btn-back-character" data-action="back-to-location">←</button> <</if>> </div> <!-- Слой 4b: кнопки взаимодействия — нижний левый угол, горизонтально --> <div class="nx-layer nx-bl nx-npc-actions"> <div class="div-nexus-location-img-button"> <<if _charData && _charData.interaction>> <<set _cKeys to Object.keys(_charData.interaction)>> <<for _ci to 0; _ci lt _cKeys.length; _ci++>> <<set _cActionId to _cKeys[_ci]>> <<if _cActionId === "forced">><<continue>><</if>> <<set _cItr to _charData.interaction[_cActionId]>> <<set _cAccess to true>> <<if _cItr.access && Object.keys(_cItr.access).length > 0>> <<set _cAccess to setup.checkInteractionAccess(_cItr.access)>> <</if>> <<capture _cActionId, _cAccess, _cItr>> <<set _cTitle to (_cAccess ? (setup.L10n[_cItr.title_interaction] || _cItr.title_interaction || _cActionId) : (setup.L10n[_cItr.title_access] || _cItr.title_access || "Недоступно"))>> <button @class="'character-interaction-btn' + (_cAccess ? '' : ' disabled')" @data-char-id="$current_character" @data-interaction-key="_cActionId" @data-tooltip="_cTitle"> <<if _cItr.ico>> <img class="interaction-ico" @src="_cItr.ico" alt=""> <<else>> <span class="interaction-text"><<print setup.L10n[_cItr.title_interaction] || _cItr.title_interaction || _cActionId>></span> <</if>> </button> <</capture>> <</for>> <</if>> </div> </div> <<else>> <div class="div-nexus-location-img-top"> <!-- Прогресс-бар слева (только если есть бары) --> <<set _locationBars to setup.getCategoryBars($current_location, "full")>> <<if _locationBars && _locationBars.length > 0>> <div class="div-nexus-location-img-bar"> <<universalProgressBars $current_location "full">> </div> <</if>> <!-- Название локации --> <div class="div-nexus-location-img-name"> <<print setup.getCategoryName(_locationId)>> </div> <!-- Аватарки персонажей — теперь в верхнем правом слоте (грид-колонка 3) --> <div class="div-nexus-location-char"> <<set _charsHere to setup.getCharactersAtLocation(_locationId)>> <<for _i to 0; _i lt _charsHere.length; _i++>> <<set _charId to _charsHere[_i]>> <<set _charData to setup.characters[_charId]>> <<if _charData>> <<set _charDisplayData to setup.getCategoryDisplayData(_charId)>> <<set _charName to setup.getCategoryName(_charId)>> <div @class="'char-avatar' + (_showCharacter === _charId ? ' current-char' : '')" @data-char="_charId" @data-cat-id="_charId" @style="'border-color: ' + _charDisplayData.borderColor" @data-tooltip="_charName"> <<set _charAvatar to setup.getAvatar(_charId)>> <<if _charAvatar.layers && _charAvatar.layers.length > 0>> <<for _layer range _charAvatar.layers>> <img class="char-avatar-layer" @src="_layer" @alt="_charName"> <</for>> <<else>> <img @src="_charAvatar.image" @alt="_charName"> <</if>> </div> <</if>> <</for>> </div> </div> <div class="div-nexus-location-img-bottom"> <!-- Кнопки взаимодействия с локацией — теперь в нижнем левом слоте --> <div class="div-nexus-location-img-button"> <<if _locationData && _locationData.interaction>> <<set _interactions to Object.keys(_locationData.interaction)>> <<for _i to 0; _i lt _interactions.length; _i++>> <<set _actionId to _interactions[_i]>> <<set _interaction to _locationData.interaction[_actionId]>> <<set _canAccess to true>> <<if _interaction.access && Object.keys(_interaction.access).length > 0>> <<set _canAccess to setup.checkInteractionAccess(_interaction.access)>> <</if>> <!-- title_dynamic_var: имя обычной переменной, в которую JS заранее кладёт L10n-ключ подписи (напр. "interact_meal_breakfast"/"..._lunch"/...). Так кнопка меняет подпись в зависимости от условий (время суток и т.д.) БЕЗ новых функций в движке — просто ссылка на переменную из JSON. --> <<set _dynKey to (_interaction.title_dynamic_var ? State.variables[_interaction.title_dynamic_var] : null)>> <<set _btnTitle to (_dynKey && setup.L10n[_dynKey]) || setup.L10n[_interaction.title_interaction] || _interaction.title_interaction || _actionId>> <<set _btnTooltip to !_canAccess ? (setup.L10n[_interaction.title_access] || _interaction.title_access || _btnTitle) : _btnTitle>> <button @class="'interaction-btn' + (!_canAccess ? ' disabled' : '')" @data-action="_actionId" @data-shop-id="_interaction.shopId || ''" @data-tooltip="_btnTooltip"> <<if _interaction.ico>> <img class="interaction-ico" @src="_interaction.ico" alt=""> <<else>> <<print _btnTitle>> <</if>> </button> <</for>> <</if>> </div> <!-- Панель переходов между локациями --> <<if _locationData && _locationData.transitions && (_locationData.transitions.entry || _locationData.transitions.exit)>> <div class="div-nexus-container-transitions"> <!-- Входящие переходы (слева) --> <div class="transitions-entry"> <<if _locationData.transitions.entry && _locationData.transitions.entry.length > 0>> <<for _i to 0; _i lt _locationData.transitions.entry.length; _i++>> <<set _transitionId to _locationData.transitions.entry[_i]>> <<set _transitionData to setup.locations[_transitionId]>> <<if _transitionData>> <<set _transitionLayers to null>> <<set _transitionImage to setup.getLocationCardImage(_transitionId, _locationId)>> <<set _transitionName to setup.L10n[_transitionData.short_title] || setup.L10n[_transitionData.title] || _transitionData.title || _transitionId>> <<set _catDisplayData to setup.getCategoryDisplayData(_transitionId)>> <<set _locAccessible to setup.isLocationAccessible(_transitionId)>> <div @class="'transition-card' + (_locAccessible ? '' : ' location-closed')" @style="'border-color: ' + (_locAccessible ? _catDisplayData.borderColor : '#555')" @data-cat-id="_locAccessible ? _transitionId : ''" @data-tooltip="_transitionName + (_locAccessible ? '' : ' 🔒')"> <<set _transLayers to null>> <<if Array.isArray(_transLayers) && _transLayers.length > 0>> <<for _tLayer range _transLayers>> <img class="transition-card-image" @src="_tLayer" @alt="_transitionName"> <</for>> <<else>> <img class="transition-card-image" @src="_transitionImage" @alt="_transitionName"> <</if>> <div class="transition-card-name" @style="'color: ' + (_catDisplayData.hasQuests ? _catDisplayData.textColor : '#888888')"> <<print _transitionName>> </div> </div> <</if>> <</for>> <</if>> </div> <!-- Исходящие переходы (справа) --> <div class="transitions-exit"> <<if _locationData.transitions.exit && _locationData.transitions.exit.length > 0>> <<for _i to 0; _i lt _locationData.transitions.exit.length; _i++>> <<set _transitionId to _locationData.transitions.exit[_i]>> <<set _transitionData to setup.locations[_transitionId]>> <<if _transitionData>> <<set _transitionLayers to null>> <<set _transitionImage to setup.getLocationCardImage(_transitionId, _locationId)>> <<set _transitionName to setup.L10n[_transitionData.short_title] || setup.L10n[_transitionData.title] || _transitionData.title || _transitionId>> <<set _catDisplayData to setup.getCategoryDisplayData(_transitionId)>> <<set _locAccessible to setup.isLocationAccessible(_transitionId)>> <div @class="'transition-card' + (_locAccessible ? '' : ' location-closed')" @style="'border-color: ' + (_locAccessible ? _catDisplayData.borderColor : '#555')" @data-cat-id="_locAccessible ? _transitionId : ''" @data-tooltip="_transitionName + (_locAccessible ? '' : ' 🔒')"> <<set _transLayers to null>> <<if Array.isArray(_transLayers) && _transLayers.length > 0>> <<for _tLayer range _transLayers>> <img class="transition-card-image" @src="_tLayer" @alt="_transitionName"> <</for>> <<else>> <img class="transition-card-image" @src="_transitionImage" @alt="_transitionName"> <</if>> <div class="transition-card-name" @style="'color: ' + (_catDisplayData.hasQuests ? _catDisplayData.textColor : '#888888')"> <<print _transitionName>> </div> </div> <</if>> <</for>> <</if>> </div> </div> <</if>> </div> <</if>> </div> </div> <</nobr>> <</widget>> <<widget "displayCategoriesPanel">> <<nobr>> <<set _allCategories to []>> <!-- Персонажи (со всех локаций для быстрого перемещения) --> <<if setup.characters>> <<set _filteredChars to setup.getCharactersForCards()>> <<set _charIds to Object.keys(_filteredChars)>> <<for _i to 0; _i lt _charIds.length; _i++>> <<set _id to _charIds[_i]>> <<set _questStats to setup.getCachedQuestStats(_id)>> <<set _questCount to {total: _questStats.total, active: _questStats.active, completed: _questStats.completed}>> <<if _questCount && _questCount.active > 0>> <<set _questType to _questStats.highestType>> <<set _gender to setup.getCategoryGender(_id)>> <<set _allCategories.push({ id: _id, type: 'character', name: setup.getCategoryName(_id), image: ((_id === 'gg_m' || _id === 'gg_f') ? setup.LayerSystem.getLayers('$' + _id, {type: 'skin', withMeta: true}) : setup.LayerSystem.getLayers('#' + _id, {type: 'skin', withMeta: true})) || [], questCount: _questCount, questType: _questType, gender: _gender, priorityNum: setup.questTypePriority[_questType] || 999 })>> <</if>> <</for>> <</if>> <!-- Локации --> <<set _filteredLocs2 to setup.getFilteredLocations ? setup.getFilteredLocations() : {}>> <<if setup.locations && _filteredLocs2>> <<for _id, _data range _filteredLocs2>> <<set _questStats to setup.getCachedQuestStats(_id)>> <<set _questCount to {total: _questStats.total, active: _questStats.active, completed: _questStats.completed}>> <<if _questCount && _questCount.active > 0>> <<set _questType to _questStats.highestType>> <<set _gender to setup.getCategoryGender(_id)>> <<set _allCategories.push({ id: _id, type: 'location', name: setup.getCategoryName(_id), image: [{ image: setup.getLocationCardImage(_id), anim: null }], questCount: _questCount, questType: _questType, gender: _gender, priorityNum: setup.questTypePriority[_questType] || 999 })>> <</if>> <</for>> <</if>> <!-- Сортируем по приоритету --> <<set _allCategories.sort(function(a, b) { if (a.priorityNum !== b.priorityNum) { return a.priorityNum - b.priorityNum; } return b.questCount.active - a.questCount.active; })>> <!-- Отображаем карточки --> <<if _allCategories.length > 0>> <<for _i to 0; _i lt _allCategories.length; _i++>> <<set _cat to _allCategories[_i]>> <<set _borderColor to setup.getQuestTypeColor(_cat.questType)>> <!-- Затемнение недоступного NPC (см. пояснение в displayAllCategories) --> <<set _charLocId2 to (_cat.type === 'character') ? ((setup.NPCSimulation ? setup.NPCSimulation.getCachedLocation(_cat.id) : null) || (setup.getCategoryData(_cat.id) || {}).location) : null>> <<set _charLocked2 to (_charLocId2 && !setup.isLocationAccessible(_charLocId2))>> <div @class="'quest-card-link quest-type-' + _cat.questType + ' quest-gender-' + _cat.gender + (_charLocked2 ? ' category-locked' : '')" @style="'border-color: ' + setup.getQuestTypeColor(_cat.questType) + '; --quest-glow-color: ' + setup.getQuestTypeColor(_cat.questType)" @data-category="_charLocked2 ? '' : _cat.id" @data-type="_cat.type" @data-cat-id="_charLocked2 ? '' : _cat.id"> <<if _charLocked2>> <div class="category-locked-badge" @data-tooltip="setup.getLocalizedText('npc_unavailable_now') || 'Сейчас недоступно'">🔒</div> <</if>> <!-- Контейнер с картинкой --> <div class="quest-card-image-container"> <!-- Используем grid для слоёв --> <<if _cat.image && _cat.image.length > 0>> <<for _layer range _cat.image>> <img @class="'quest-card-layer' + (_layer.anim ? ' anim-' + _layer.anim : '')" @src="_layer.image"> <</for>> <<else>> <img class="quest-card-layer" src="img/placeholder.webp"> <</if>> </div> <!-- Контент поверх картинки --> <div class="quest-card-content"> <div class="quest-card-text"> <span class="quest-card-name"><<print _cat.name>></span> </div> </div> </div> <</for>> <<else>> <div class="no-categories"><<print setup.getLocalizedText('no_available_quest_categories') || 'Нет доступных категорий с квестами'>></div> <</if>> <</nobr>> <</widget>> <!-- Удалить в будущем --> <<widget "includeSection">> <<nobr>> <<if _args.length > 0 and typeof _args[0] === 'string' and _args[0].includes('::')>> <<set _path to _args[0].split('::')>> <<set _passageName to _path[0]>> <<set _sectionName to _path[1]>> <<if Story.has(_passageName)>> <<set _passageContent to Story.get(_passageName).text>> <<set _sectionRegex to new RegExp('^::' + _sectionName + '\\n([\\s\\S]*?)(?=\\n::|$)', 'm')>> <<set _match to _passageContent.match(_sectionRegex)>> <<if _match and _match[1]>> <<print _match[1].trim()>> <<else>> <div class="error">Ошибка: Секция '<<print _sectionName>>' не найдена в пассаже '<<print _passageName>>'.</div> <</if>> <<else>> <div class="error">Ошибка: Пассаж-контейнер '<<print _passageName>>' не найден.</div> <</if>> <<else>> <div class="error">Ошибка <<includeSection>>: неверный путь к секции. Ожидается формат "ИмяПассажа::ИмяСекции".</div> <</if>> <</nobr>> <</widget>> <!-- Виджет --> <<widget "universalProgressBars">> <<nobr>> <<set _categoryId to _args[0]>> <<set _mode to _args[1] || "full">> <<set _maxBars to _args[2] || 999>> <<if _categoryId>> <<set _allBars to setup.getCategoryBars(_categoryId, _mode)>> <<set _displayBars to _allBars.slice(0, _maxBars)>> <<if _mode is "compact">> <div class="reputation-progress-container map-compact"> <<for _bar range _displayBars>> <<if _bar.type is "quest">> <!-- Квесты с поддержкой цвета --> <div class="quest-progress-section" @style="'--quest-percentage: ' + _bar.data.percentage + '%; --quest-color: ' + (setup.convertColorToRGBA(_bar.color || 'blue', 0.3)) + ';'"> <<if _bar.icon>> <span class="quest-icon"><<print _bar.icon>></span> <</if>> <span class="quest-progress-text"><<print setup.getLocalizedText("title_stats_quest") || "Квесты">></span> <span class="quest-progress-count"><<print _bar.data.completed>>/<<print _bar.data.total>></span> </div> <<else>> <!-- Статы с поддержкой цвета --> <<set _value to _bar.data.value || 0>> <<set _percent to Math.round((_value / _bar.data.max) * 100)>> <<set _levelName to 'lvl_1'>> <<if _bar.data.level>> <<set _levelName to _bar.data.level.name>> <</if>> <div @class="'reputation-progress-section reputation-level-' + _levelName" @style="'--reputation-percentage: ' + _percent + '%; --reputation-color: ' + (setup.convertColorToRGBA(_bar.color || 'gold', 0.3)) + ';'"> <<if _bar.icon>> <span class="stat-icon"><<print _bar.icon>></span> <</if>> <span class="reputation-progress-text"> <<print setup.getLocalizedText(_bar.title) || _bar.title || _bar.type>> </span> <span class="reputation-progress-percent"><<print _percent>>%</span> </div> <</if>> <</for>> </div> <<else>> <!-- Полная версия --> <div class="reputation-progress-container"> <<for _bar range _displayBars>> <<if _bar.type is "quest">> <div class="quest-progress-section" @style="'--quest-percentage: ' + _bar.data.percentage + '%; --quest-color: ' + (setup.convertColorToRGBA(_bar.color || 'blue', 0.3)) + ';'"> <span class="quest-progress-text"> <<if _bar.icon>> <span class="quest-icon"><<print _bar.icon>></span> <</if>> <span><<print setup.getLocalizedText(_bar.title) || _bar.title || "Квесты">>:</span> <<print _bar.data.completed>>/<<print _bar.data.total>> </span> <span class="quest-progress-percentage"><<print _bar.data.percentage>>%</span> </div> <<else>> <!-- Статы - универсальная обработка --> <<set _value to _bar.data.value || 0>> <<set _percent to Math.round((_value / _bar.data.max) * 100)>> <<set _levelName to 'lvl_1'>> <<if _bar.data.level>> <<set _levelName to _bar.data.level.name>> <</if>> <div @class="'reputation-progress-section reputation-level-' + _levelName" @style="'--reputation-percentage: ' + _percent + '%; --reputation-color: ' + (setup.convertColorToRGBA(_bar.color || 'gold', 0.3)) + ';'"> <span class="reputation-progress-text"> <<if _bar.icon>> <span class="stat-icon"><<print _bar.icon>></span> <</if>> <span><<print setup.getLocalizedText(_bar.title) || _bar.title || _bar.type>>:</span> <<print _value>> <<if !_bar.data.isMaxLevel>> (<<print _percent>>%) <</if>> </span> <<if _bar.data.level && _bar.data.level.localizedName>> <span class="reputation-level-name"><<print setup.getLocalizedText(_bar.data.level.localizedName) || _bar.data.level.name>></span> <</if>> </div> <</if>> <</for>> </div> <</if>> <</if>> <</nobr>> <</widget>> <!-- Виджет отображения времени --> <<widget "timeDisplay">> <<nobr>> <<if setup.TimeSystem && setup.TimeSystem.initialized && $gameTime>> <div class="page-gg-box-info-time"> <!-- Месяц --> <div class="box-info-month"> <div class="box-info-month-img"> <span class="time-icon"><<print setup.TimeSystem.getMonthIcon()>></span> </div> <div class="box-info-month-text"> <<print setup.TimeSystem.getMonthName()>>, <<print $gameTime.day>> </div> </div> <!-- День недели --> <<set _dayId to setup.TimeSystem.getDayOfWeekId()>> <<set _dayFullName to setup.TimeSystem.getDayOfWeekName()>> <<set _dayShortName to setup.getLocalizedText('short_' + _dayId) || _dayFullName>> <div class="box-info-dayofweek" @data-tooltip="_dayFullName"> <div class="box-info-dayofweek-text"> <<print _dayShortName>> </div> </div> <!-- Время дня --> <<set _timeTooltip to setup.TimeSystem.getFormattedTime()>> <div class="box-info-timeofday" @data-tooltip="_timeTooltip"> <div class="box-info-timeofday-img" > <span class="time-icon"><<print setup.TimeSystem.getPeriodIcon()>></span> </div> <div class="box-info-timeofday-text"> <<print setup.TimeSystem.getPeriodName()>> </div> </div> </div> <<else>> <div class="page-gg-box-info-time"> <div class="time-loading">Время загружается...</div> </div> <</if>> <</nobr>> <</widget>> <!-- Виджет для добавления времени (для использования в квестах) --> <!-- Виджет для установки времени --> <<widget "displayGGBars">> <<nobr>> <!-- Определяем текущего главного героя --> <<set _currentGG to ($gg === 0) ? "gg_m" : "gg_f">> <<if setup.game_parameters && setup.player && setup.player[_currentGG]>> <<set _playerData to setup.player[_currentGG]>> <<set _gameParams to setup.game_parameters>> <!-- Ищем все статы с display: "gg_bar" --> <<set _ggBars to []>> <<for _statName, _statConfig range _gameParams.stats>> <<if _statConfig.display === "gg_bar">> <!-- Проверяем глобальный enabled в game_parameters --> <<if _statConfig.enabled === false>> <<continue>> <</if>> <!-- Проверяем, есть ли этот стат у текущего ГГ --> <<if _playerData.stats && _playerData.stats[_statName]>> <<set _statData to _playerData.stats[_statName]>> <!-- Проверяем персональный enabled в player.json --> <<if _statData.enabled === false>> <<continue>> <</if>> <!-- ИСПРАВЛЕНИЕ: Правильно получаем значения из переменных State --> <<set _varName to _currentGG + "_" + _statName>> <<set _currentValue to State.variables[_varName]>> <!-- Если переменной нет, используем значение из конфига --> <<if _currentValue === undefined>> <<if _statData.current === "max">> <!-- Вычисляем максимальное значение через JS --> <<set _currentValue to setup.StatsSystem.calculateStatMax(_statName, _currentGG)>> <<elseif typeof _statData.current === "number">> <<set _currentValue to _statData.current>> <<else>> <<set _currentValue to 0>> <</if>> <!-- Инициализируем переменную для будущего использования --> <<set State.variables[_varName] to _currentValue>> <</if>> <!-- Убеждаемся что значение не отрицательное для отображения --> <<set _displayValue to Math.max(0, _currentValue)>> <!-- Вычисляем максимум через JS функцию --> <<set _maxValue to setup.StatsSystem.calculateStatMax(_statName, _currentGG)>> <<set _percentage to Math.round((_displayValue / _maxValue) * 100)>> <!-- Получаем иконку: сначала из конфига, потом дефолтную --> <<set _statIcon to _statConfig.icon>> <<if !_statIcon || _statIcon === "">> <<switch _statName>> <<case "energy">> <<set _statIcon to "⚡">> <<case "health">> <<set _statIcon to "❤️">> <<case "mana">> <<set _statIcon to "💙">> <<case "reputation">> <<set _statIcon to "🏆">> <<case "relationships">> <<set _statIcon to "💕">> <<default>> <<set _statIcon to "📊">> <</switch>> <</if>> <<set _statTitle to setup.getLocalizedText(_statConfig.title) || _statConfig.title || _statName>> <!-- Добавляем в массив для сортировки --> <<set _ggBars.push({ name: _statName, title: _statTitle, icon: _statIcon, current: _displayValue, max: _maxValue, percentage: _percentage, priority: _statConfig.display_priority || 1, color: _statConfig.color || "gold" })>> <</if>> <</if>> <</for>> <!-- Сортируем по приоритету --> <<set _ggBars.sort(function(a, b) { return (a.priority || 1) - (b.priority || 1); })>> <!-- Отображаем бары в стиле category_bar --> <<if _ggBars.length > 0>> <<for _bar range _ggBars>> <div class="gg-stat-container" data-stat="<<print _bar.name>>"> <!-- Используем тот же подход что и в reputation-progress-section --> <div class="gg-stat-background gg-stat-level-default" @style="'--gg-bar-percentage: ' + _bar.percentage + '%; --gg-bar-color: ' + (setup.convertColorToRGBA(_bar.color || 'gold', 0.3)) + ';'"> <div class="gg-stat-content"> <!-- Левая часть: иконка, название, показатели --> <div class="gg-stat-left"> <span class="gg-stat-icon"><<print _bar.icon>></span> <span class="gg-stat-title"><<print _bar.title>></span> <span class="gg-stat-values"><<print _bar.current>>/<<print _bar.max>></span> </div> <!-- Правая часть: проценты --> <div class="gg-stat-right"> <span class="gg-stat-percentage"><<print _bar.percentage>>%</span> </div> </div> </div> </div> <</for>> <<else>> <!-- Fallback если нет баров для отображения --> <div class="gg-no-bars"> <span style="opacity: 0.6; font-size: 0.8em;"><<print setup.getLocalizedText('no_available_stats') || 'Нет доступных характеристик'>></span> </div> <</if>> <<else>> <!-- Fallback если нет данных --> <div class="gg-bars-loading"> <span style="opacity: 0.6; font-size: 0.8em;">Загрузка...</span> </div> <</if>> <</nobr>> <</widget>> <<widget "displayEffects">> <<nobr>> <<set _hero to ($gg === 0) ? "gg_m" : "gg_f">> <<set _effects to setup.EffectsSystem.getActiveEffects(_hero)>> <<if _effects and _effects.length > 0>> <div class="effects-box"> <<for _effect range _effects>> <<set _effectValueText to (_effect.type === 'buff' ? ('+' + _effect.value) : ('-' + _effect.value + '%'))>> <<set _iconHtml to (_effect.icon && (_effect.icon.includes('.webp') || _effect.icon.includes('.png') || _effect.icon.includes('.jpg'))) ? ('<img class="effect-img" src="' + _effect.icon + '" alt="">') : ('<div class="effect-emoji">' + _effect.icon + '</div>')>> <<set _tipAttrs to ($show_debuff_icons != false) ? (' data-tooltip="' + _effect.title + ' (' + _effectValueText + ')" data-tooltip-required="true"') : ''>> <<print '<div class="effect-card" data-type="' + _effect.type + '"' + _tipAttrs + '>' + _iconHtml + '</div>'>> <</for>> </div> <</if>> <</nobr>> <</widget>> <<widget "GGBoxSkin">> <<nobr>> <<set _hero to ($gg === 0) ? "gg_m" : "gg_f">> <<set _isNarrator to (typeof $narrator !== "undefined" && $narrator !== null && $narrator !== "")>> <div @class="'page-gg-box-skin' + (_isNarrator ? ' narrator-mode' : '')"> <div class="skin-tabs"> <<for _tab range setup.GGBoxInterface.getTabs()>> <<capture _tab>> <button @class="'skin-tab' + (_tab.id === $active_gg_tab ? ' active' : '')" @data-tab="_tab.id" @data-tooltip="_tab.title"> <<print setup.renderIcon(_tab.icon, 'tab-icon')>> </button> <</capture>> <</for>> </div> <div class="skin-content"> <div class="character-background"> <div class="character-layers"> <<if _isNarrator>> <<set _narratorLayers to setup.LayerSystem.getLayers($narrator, {type: 'skin', withMeta: true})>> <<if !_narratorLayers || _narratorLayers.length === 0>> <<set _narratorLayers to setup.LayerSystem.getLayers($narrator, {type: 'card', withMeta: true})>> <</if>> <<if _narratorLayers && _narratorLayers.length > 0>> <<for _layer range _narratorLayers>> <img @class="'character-layer' + (_layer.anim ? ' anim-' + _layer.anim : '')" @src="_layer.image" @data-layer="_layer.layerId" alt=""> <</for>> <<else>> <img @class="'character-layer' + (typeof _layer !== 'undefined' && _layer && _layer.anim ? ' anim-' + _layer.anim : '')" src="img/char/default.png" alt=""> <</if>> <<else>> <<set _equipped to {}>> <<if $characters && $characters[_hero] && $characters[_hero].equipped>> <<set _equipped to $characters[_hero].equipped>> <</if>> <<set _heroLayers to setup.LayerSystem.getLayersWithIds('$' + _hero, {type: 'skin', equipped: _equipped})>> <<if _heroLayers && _heroLayers.length > 0>> <<for _layer range _heroLayers>> <img @class="'character-layer' + (_layer.anim ? ' anim-' + _layer.anim : '')" @src="_layer.image" @data-layer="_layer.layerId" alt=""> <</for>> <<else>> <img @class="'character-layer' + (typeof _layer !== 'undefined' && _layer && _layer.anim ? ' anim-' + _layer.anim : '')" src="img/char/default.png" alt=""> <</if>> <</if>> </div> </div> <div @class="'tab-content' + ($active_gg_tab === 'character' ? ' active' : '')" id="character"></div> <div @class="'tab-content' + ($active_gg_tab === 'equipment' ? ' active' : '')" id="equipment"><<GGEquipmentDisplay>></div> <div @class="'tab-content' + ($active_gg_tab === 'stats' ? ' active' : '')" id="stats"><<GGStatsDisplay>></div> <div @class="'tab-content' + ($active_gg_tab === 'inventory' ? ' active' : '')" id="inventory"><<GGInventoryDisplay>></div> </div> <div class="skin-controls"> <<GGLayerControls>> <<GGCharacterSwitcher>> </div> </div> <</nobr>> <</widget>> <<widget "GGEquipmentDisplay">> <<nobr>> <<set _hero to ($gg === 0) ? "gg_m" : "gg_f">> <!-- Собираем данные об экипировке из ПРАВИЛЬНОГО места --> <<set _equipped to {}>> <<if $characters && $characters[_hero] && $characters[_hero].equipped>> <<set _equipped to $characters[_hero].equipped>> <</if>> <div class="equipment-grid"> <<for _slot range setup.GGBoxInterface.getEquipmentSlots()>> <<capture _slot>> <!-- Получаем ПРАВИЛЬНЫЙ itemId из новой структуры --> <<set _equippedItemId to _equipped[_slot.id] || null>> <div @class="'equip-slot ' + _slot.cssClass" @data-slot="_slot.id" @data-tooltip="(() => { if (_equippedItemId) { const item = ItemSystem.getItemData(_equippedItemId); if (item) { return setup.getLocalizedText(item.title) || item.title; } else { return '❌ Предмет не загружен'; } } else { return _slot.title; } })()" @style="setup.GGBoxInterface.getSlotStyles(_slot.position)"> <<if _equippedItemId>> <!-- Слот ЗАНЯТ --> <<set _item to ItemSystem.getItemData(_equippedItemId)>> <<if _item>> <!-- ПРАВИЛЬНОЕ получение картинки --> <<set _imgSrc to "">> <<if _item.image>> <<if typeof _item.image === "string">> <<set _imgSrc to _item.image>> <<elseif typeof _item.image === "object">> <<set _imgSrc to _item.image.pristine || _item.image.default || Object.values(_item.image)[0] || "">> <</if>> <</if>> <<if _imgSrc>> <img @src="_imgSrc" alt="" class="item-image"> <<else>> <div class="empty-slot-icon fallback-icon" data-tooltip="⚠️ Изображение недоступно"> <<print setup.GGBoxInterface.getSlotIcon(_slot.id)>> </div> <</if>> <<else>> <div class="empty-slot-icon missing-item"> <<print setup.GGBoxInterface.getSlotIcon(_slot.id)>> </div> <</if>> <<else>> <div class="empty-slot"></div> <</if>> </div> <</capture>> <</for>> </div> <</nobr>> <</widget>> <<widget "GGStatsDisplay">> <<nobr>> <!-- Отображаем profession_bar (профессии) --> <<displayProfessionBars>> <</nobr>> <</widget>> <<widget "displayProfessionBars">> <<nobr>> <<set _currentGG to ($gg === 0) ? "gg_m" : "gg_f">> <<if setup.game_parameters && setup.player && setup.player[_currentGG]>> <<set _playerData to setup.player[_currentGG]>> <<set _gameParams to setup.game_parameters>> <!-- Загружаем определения групп --> <<set _groupDefs to _gameParams.stat_groups || {}>> <!-- Создаём структуру групп --> <<set _groups to {}>> <<for _groupId, _groupDef range _groupDefs>> <<if _groupDef.enabled !== false>> <<set _groups[_groupId] to { id: _groupId, title: _groupDef.title || "title_group_" + _groupId, icon: _groupDef.icon || "", priority: _groupDef.priority || 999, bars: [] }>> <</if>> <</for>> <!-- Распределяем бары по группам --> <<for _statName, _statConfig range _gameParams.stats>> <<if _statConfig.display === "profession_bar">> <<if _playerData.profession && _playerData.profession[_statName]>> <<set _profData to _playerData.profession[_statName]>> <<if _profData.enabled>> <<set _accessOk to true>> <<if _profData.access && Array.isArray(_profData.access) && _profData.access.length gt 0>> <<set _accessOk to false>> <<for _accRule range _profData.access>> <<if _accRule.quest_complete && State.variables.completedQuests && State.variables.completedQuests.includes(_accRule.quest_complete)>> <<set _accessOk to true>> <</if>> <</for>> <</if>> <<if _accessOk>> <<set _expVarName to _currentGG + "_" + _statName + "_exp">> <<set _currentExp to State.variables[_expVarName]>> <<if _currentExp === undefined>> <<set _currentExp to _profData[_statName + "_exp"] || 0>> <</if>> <<set _maxExp to 100>> <<set _percentage to Math.round((_currentExp / _maxExp) * 100)>> <!-- Определяем группу --> <<set _groupId to _statConfig.group || "default">> <!-- Если группа не существует, создаём default --> <<if !_groups[_groupId]>> <<if !_groups["default"]>> <<set _groups["default"] to { id: "default", title: "", icon: "", priority: 999, bars: [] }>> <</if>> <<set _groupId to "default">> <</if>> <!-- Получаем приоритет бара --> <<set _barPriority to _statConfig.display_priority>> <<if typeof _barPriority === "object">> <<set _barPriority to _barPriority.default || 999>> <</if>> <!-- Добавляем бар в группу --> <<set _groups[_groupId].bars.push({ name: _statName, title: setup.getLocalizedText(_statConfig.title) || _statConfig.title || _statName, icon: _statConfig.icon || "", current: _currentExp, max: _maxExp, percentage: _percentage, priority: _barPriority || 999, color: _statConfig.color || "gold" })>> <</if>> <</if>> <</if>> <</if>> <</for>> <!-- Фильтруем пустые группы и сортируем --> <<set _groupsArray to Object.values(_groups).filter(function(g) { return g.bars.length > 0; })>> <<set _groupsArray.sort(function(a, b) { return (a.priority || 999) - (b.priority || 999); })>> <!-- Сортируем бары внутри каждой группы --> <<for _group range _groupsArray>> <<set _group.bars.sort(function(a, b) { return (a.priority || 999) - (b.priority || 999); })>> <</for>> <!-- Отображаем --> <<if _groupsArray.length > 0>> <div class="gg-bars-container"> <<for _group range _groupsArray>> <!-- Заголовок группы (если есть title и больше одной группы или title не пустой) --> <<if _group.title && (_groupsArray.length > 1 || _group.icon)>> <div class="gg-bars-group-header"> <<if _group.icon>> <<set _isIconImage to _group.icon.includes('/') || _group.icon.includes('.')>> <<if _isIconImage>> <img class="gg-bars-group-icon" @src="_group.icon" alt=""> <<else>> <span class="gg-bars-group-icon"><<print _group.icon>></span> <</if>> <</if>> <span class="gg-bars-group-title"><<print setup.getLocalizedText(_group.title) || _group.title>></span> </div> <</if>> <!-- Бары группы --> <<for _bar range _group.bars>> <div class="gg-stat-container" data-stat="<<print _bar.name>>"> <div class="gg-stat-background gg-stat-level-default" @style="'--gg-bar-percentage: ' + _bar.percentage + '%; --gg-bar-color: ' + (setup.convertColorToRGBA(_bar.color || 'gold', 0.3)) + ';'"> <div class="gg-stat-content"> <div class="gg-stat-left"> <<if _bar.icon>> <<set _isBarIconImage to _bar.icon.includes('/') || _bar.icon.includes('.')>> <<if _isBarIconImage>> <img class="gg-stat-icon-img" @src="_bar.icon" alt=""> <<else>> <span class="gg-stat-icon"><<print _bar.icon>></span> <</if>> <</if>> <span class="gg-stat-title"><<print _bar.title>></span> <span class="gg-stat-values"><<print _bar.current>>/<<print _bar.max>></span> </div> <div class="gg-stat-right"> <span class="gg-stat-percentage"><<print _bar.percentage>>%</span> </div> </div> </div> </div> <</for>> <</for>> </div> <<else>> <div class="gg-no-bars"> <span style="opacity: 0.6; font-size: 0.8em;"><<print setup.getLocalizedText('no_available_stats') || 'Нет доступных характеристик'>></span> </div> <</if>> <<else>> <div class="gg-no-bars"> <span style="opacity: 0.6; font-size: 0.8em;"><<print setup.getLocalizedText('no_available_stats') || 'Нет доступных характеристик'>></span> </div> <</if>> <</nobr>> <</widget>> <<widget "GGInventoryDisplay">> <<nobr>> <<set _hero to ($gg === 0) ? "gg_m" : "gg_f">> <<set _inventoryId to "main_storage">> /* Получаем предметы через ItemSystem */ <<set _items to ItemSystem.getInventoryItems?.(_inventoryId) || []>> /* Настройки фильтра */ <<set _currentFilterType to "filter_storage_mini">> <<set _currentActiveFilter to "1">> <<set _filterConfig to ItemSystem.getActiveFilters(_currentFilterType)>> <<set _activeFilter to _filterConfig.find(f => f.id == _currentActiveFilter)>> <div class="inventory-compact"> <<if _items && _items.length > 0>> <div class="inventory-grid-compact"> <<for _item range _items>> <<capture _item>> /* Проверяем фильтр */ <<set _shouldShow to false>> <<if !_activeFilter || !_activeFilter.filter || _activeFilter.filter.length === 0>> <<set _shouldShow to true>> <<else>> <<set _filterBy to _activeFilter.filter_by || "subtype">> <<if _filterBy === "subtype">> <<set _shouldShow to _activeFilter.filter.includes(_item.subtype)>> <<elseif _filterBy === "type">> <<set _shouldShow to _activeFilter.filter.includes(_item.type)>> <<elseif _filterBy === "category">> <<if Array.isArray(_item.category)>> <<set _shouldShow to _item.category.some(cat => _activeFilter.filter.includes(cat))>> <<else>> <<set _shouldShow to _activeFilter.filter.includes(_item.category)>> <</if>> <</if>> <</if>> <<if _shouldShow>> <div @class="'inv-item inv-item-' + _item.rarity + (_item.usable ? ' inv-item-usable' : '')" @data-item-id="_item.id" @data-usable="_item.usable" @data-count="_item.count"> <<if _item.icon>> <<if _item.icon.includes('.webp') || _item.icon.includes('.png') || _item.icon.includes('.jpg')>> <img class="inv-item-icon" @src="_item.icon" alt=""> <<else>> <span class="inv-item-emoji"><<print _item.icon>></span> <</if>> <<else>> <span class="inv-item-emoji">📦</span> <</if>> <<if _item.count > 1>> <span class="inv-item-count"><<print _item.count>></span> <</if>> <div class="inv-item-tooltip"> <div class="inv-tooltip-header inv-tooltip-<<print _item.rarity>>"> <<print _item.name>> </div> <<if _item.count > 1>> <div class="inv-tooltip-count">Количество: <<print _item.count>></div> <</if>> <<if _item.usable>> <div class="inv-tooltip-hint">Клик → Использовать</div> <</if>> </div> <<if _item.usable>> <div class="inv-use-prompt" style="display: none;"> <button class="inv-use-btn">Использовать?</button> </div> <</if>> </div> <</if>> <</capture>> <</for>> </div> <<else>> <div class="inventory-empty"> <span class="inventory-empty-icon">📦</span> <span class="inventory-empty-text">Инвентарь пуст</span> </div> <</if>> </div> <</nobr>> <</widget>> <<widget "GGLayerControls">> <<nobr>> <div class="layer-toggles"> <<set _ggLayers to setup.GGBoxInterface?.getLayers?.() || []>> <<for _layer range _ggLayers>> <<capture _layer>> <button @class="'layer-btn' + (_layer.visible ? ' active' : '')" @data-layer="_layer.id" @data-tooltip="_layer.title"> <<print setup.renderIcon(_layer.icon, 'layer-icon')>> </button> <</capture>> <</for>> </div> <</nobr>> <</widget>> <<widget "GGCharacterSwitcher">> <<nobr>> <<set _gameSettings to setup.game_parameters?.game_settings>> <<set _isDual to _gameSettings?.dual_protagonist !== false>> <<set _availableHeroes to _gameSettings?.available_protagonists || ["gg_m", "gg_f"]>> <<set _hasMale to _availableHeroes.includes("gg_m")>> <<set _hasFemale to _availableHeroes.includes("gg_f")>> <<set _enableSwitch to _gameSettings?.enable_character_switch !== false>> <<set _currentHero to ($gg === 0) ? "male" : "female">> <<set _heroName to ($gg === 0) ? ($gn_m || "Максим") : ($gn_f || "Карина")>> <!-- Проверяем, можно ли переключать героя --> <<set _canSwitch to _isDual && _hasMale && _hasFemale && _enableSwitch>> <<set _questGender to "any">> <!-- На scene с активным квестом --> <<if passage() === "scene" && $currentQuestId>> <<set _questDef to setup.questDefinitions[$currentQuestId]>> <<set _questGender to _questDef?.data?.gender || "any">> <<set _lockSwitch to _questDef?.data?.lockSwitch === true>> <<if _questGender !== "any" || _lockSwitch>> <<set _canSwitch to false>> <</if>> <</if>> <!-- На nexus с выбранным квестом --> <<if passage() === "nexus" && $selected_quest>> <<set _questDef to setup.questDefinitions[$selected_quest]>> <<set _questGender to _questDef?.data?.gender || "any">> <<set _lockSwitch to _questDef?.data?.lockSwitch === true>> <<if _questGender !== "any" || _lockSwitch>> <<set _canSwitch to false>> <</if>> <</if>> <<if _canSwitch>> <!-- Режим двух персонажей - показываем кнопки переключения --> <div class="character-switcher"> <button class="switch-arrow left" data-target="male" @data-tooltip="$gn_m || setup.getLocalizedText('switch_to_hero')">◄</button> <div class="switch-track"> <div @class="'switch-indicator ' + _currentHero"></div> <span class="character-name"><<print _heroName>></span> </div> <button class="switch-arrow right" data-target="female" @data-tooltip="$gn_f || setup.getLocalizedText('switch_to_heroine')">►</button> </div> <<else>> <!-- Режим одного персонажа или заблокировано квестом --> <<if _isDual && _hasMale && _hasFemale && _enableSwitch>> <div class="character-name-display single-character"> <span class="character-name"><<print _heroName>></span> </div> <</if>> <!-- Если квест заблокирован по гендеру - ничего не показываем --> <</if>> <</nobr>> <</widget>> <!-- Виджет управления громкостью в page-header --> <<widget "mediaControls">> <<nobr>> <div class="media-controls"> <!-- Кнопка отключения звука --> <button id="mute-toggle" class="media-control-btn" @data-tooltip="setup.getLocalizedText('tooltip_mute_toggle') || 'Вкл/Выкл звук'"> <span class="mute-icon">🔊</span> </button> <!-- Ползунок общей громкости --> <input type="range" id="master-volume" class="volume-slider" min="0" max="100" value="100" @data-tooltip="setup.getLocalizedText('tooltip_master_volume') || 'Общая громкость'"> <!-- Кнопка настроек звука --> <button id="audio-settings" class="media-control-btn" @data-tooltip="setup.getLocalizedText('tooltip_audio_settings') || 'Настройки звука'"> ⚙️ </button> </div> <<script>> $(document).ready(function() { const $muteBtn = $('#mute-toggle'); const $volumeSlider = $('#master-volume'); const $settingsBtn = $('#audio-settings'); const settings = State.variables.settings || {}; const masterVolume = settings.masterVolume !== undefined ? settings.masterVolume * 100 : 100; const isMuted = settings.isMuted || false; $volumeSlider.val(masterVolume); updateMuteButton(isMuted); $muteBtn.on('click', function() { const wasMuted = State.variables.settings.isMuted || false; const newMuted = !wasMuted; State.variables.settings.isMuted = newMuted; if (newMuted) { State.variables.settings.savedVolume = setup.MediaSystem.globalVolume.master; setup.MediaVolume.setMasterVolume(0); } else { const savedVolume = State.variables.settings.savedVolume || 1; setup.MediaVolume.setMasterVolume(savedVolume); $volumeSlider.val(savedVolume * 100); } updateMuteButton(newMuted); }); $volumeSlider.on('input', function() { const value = parseFloat(this.value) / 100; setup.MediaVolume.setMasterVolume(value); if (State.variables.settings.isMuted && value > 0) { State.variables.settings.isMuted = false; updateMuteButton(false); } }); function updateMuteButton(isMuted) { const $icon = $muteBtn.find('.mute-icon'); $icon.text(isMuted ? '🔇' : '🔊'); $muteBtn.toggleClass('muted', isMuted); } }); <</script>> <</nobr>> <</widget>> <<widget "displayDialog">> <<nobr>> <<say _args[0] _args[1] _args[2]>> <</nobr>> <</widget>> <<widget "displayInventoryCards">> <<nobr>> <<set _inventoryId to $inventory_filter || 'main_storage'>> <!-- Инициализация фильтра если не установлен --> <<if !$item_type_filter>> <<set $item_type_filter to "all">> <</if>> <<set _varName to 'inv_' + _inventoryId>> <<set _inventory to State.variables[_varName] || {}>> <!-- Группируем предметы --> <<set _groupedItems to {}>> <!-- НОВОЕ: Поддержка как массивов, так и объектов --> <<if Array.isArray(_inventory)>> <!-- Если массив - группируем как раньше --> <<for _itemId range _inventory>> <<if !_groupedItems[_itemId]>> <<set _groupedItems[_itemId] to {id: _itemId, count: 0}>> <</if>> <<set _groupedItems[_itemId].count++>> <</for>> <<else>> <!-- Если объект - просто копируем --> <<for _itemId, _count range _inventory>> <<set _groupedItems[_itemId] to {id: _itemId, count: _count}>> <</for>> <</if>> <!-- Фильтр --> <<set _filteredItems to []>> <!-- Универсальная фильтрация через новую систему --> <<set _currentFilterType to null>> <<set _currentActiveFilter to $active_filter || "1">> <!-- Определяем какой тип фильтра использовать --> <<if $nexus_mode === "shop_wardrobe" && $current_shop_filter>> <<set _currentFilterType to $current_shop_filter>> <<elseif $nexus_mode === "shop_body" && $current_shop_filter>> <<set _currentFilterType to $current_shop_filter>> <<elseif $nexus_mode === "shop" && $current_shop_filter>> <<set _currentFilterType to $current_shop_filter>> <<elseif $nexus_mode === "storage">> <<set _currentFilterType to "filter_storage">> <<elseif $nexus_mode === "wardrobe">> <<set _currentFilterType to "filter_wardrobe">> <<elseif $nexus_mode === "collection">> <<set _currentFilterType to "filter_collection">> <</if>> <<if _currentFilterType>> <!-- Используем новую систему фильтров --> <<set _filterConfig to ItemSystem.getActiveFilters(_currentFilterType)>> <<set _activeFilter to _filterConfig.find(f => f.id == _currentActiveFilter)>> <<for _itemId, _itemGroup range _groupedItems>> <<set _itemData to ItemSystem.getItemData(_itemId)>> <<if _itemData>> <<set _shouldShow to false>> <<if !_activeFilter || !_activeFilter.filter || _activeFilter.filter.length === 0>> <!-- Фильтр "Всё" - показываем все --> <<set _shouldShow to true>> <<else>> <!-- Проверяем по какому полю фильтровать --> <<set _filterBy to _activeFilter.filter_by || "subtype">> <<if _filterBy === "subtype">> <<set _shouldShow to _activeFilter.filter.includes(_itemData.subtype)>> <<elseif _filterBy === "type">> <<set _shouldShow to _activeFilter.filter.includes(_itemData.type)>> <<elseif _filterBy === "category">> <!-- Категория может быть массивом --> <<if Array.isArray(_itemData.category)>> <<set _shouldShow to _itemData.category.some(cat => _activeFilter.filter.includes(cat))>> <<else>> <<set _shouldShow to _activeFilter.filter.includes(_itemData.category)>> <</if>> <<elseif _filterBy === "rarity">> <<set _shouldShow to _activeFilter.filter.includes(_itemData.rarity)>> <<elseif _filterBy === "slot">> <<set _shouldShow to _activeFilter.filter.includes(_itemData.slot)>> <<elseif _filterBy === "layer">> <<set _shouldShow to _activeFilter.filter.includes(_itemData.layer)>> <<elseif _filterBy === "consumable">> <!-- Для consumable проверяем boolean --> <<set _shouldShow to (_itemData.consumable === true)>> <<elseif _filterBy === "stackable">> <!-- Для stackable проверяем boolean --> <<set _shouldShow to (_itemData.stackable === true)>> <<elseif _filterBy === "usable">> <!-- Для usable проверяем boolean --> <<set _shouldShow to (_itemData.usable === true)>> <<elseif _filterBy === "custom">> <!-- Для кастомных фильтров - проверяем функцию --> <<if _activeFilter.customFilter && typeof _activeFilter.customFilter === 'function'>> <<set _shouldShow to _activeFilter.customFilter(_itemData)>> <</if>> <</if>> <</if>> <<if _shouldShow>> <<run _filteredItems.push({ id: _itemId, data: _itemData, count: _itemGroup.count })>> <</if>> <</if>> <</for>> <<else>> <!-- Без фильтров - показываем всё --> <<for _itemId, _itemGroup range _groupedItems>> <<set _itemData to ItemSystem.getItemData(_itemId)>> <<if _itemData>> <<run _filteredItems.push({ id: _itemId, data: _itemData, count: _itemGroup.count })>> <</if>> <</for>> <</if>> <!-- Фильтруем валюты с inventary_show: false --> <<set _finalItems to []>> <<for _item range _filteredItems>> <<set _itemData to _item.data>> <<set _shouldShow to true>> <!-- Если это валюта, проверяем нужно ли показывать --> <<if _itemData.type === 'currency'>> <<set _shouldShow to ItemSystem.shouldShowCurrencyInInventory(_item.id)>> <</if>> <<if _shouldShow>> <<run _finalItems.push(_item)>> <</if>> <</for>> <!-- Далее работаем с _finalItems вместо _filteredItems --> <<if _finalItems.length === 0>> <div class="inventory-empty"> ... </div> <<else>> <!-- Сетка карточек --> <div class="inventory-grid"> <<for _item range _finalItems>> <!-- ← Изменено с _filteredItems на _finalItems --> <<set _itemId to _item.id>> <<set _itemData to _item.data>> <<set _itemCount to _item.count>> <!-- ЛОКАЛИЗАЦИЯ --> <<set _title to setup.getLocalizedText(_itemData.title) || _itemData.title || _itemId>> <<set _desc to setup.getLocalizedText(_itemData.description) || _itemData.description || ''>> <<set _lore to setup.getLocalizedText(_itemData.lore) || _itemData.lore || ''>> <!-- Редкость --> <<set _rarityData to ItemSystem.getRarityData(_itemData.rarity || 'common')>> <<set _rarity to _rarityData.id>> <<set _rarityName to _rarityData.name>> <<set _rarityColor to _rarityData.color>> <!-- Иконка --> <<set _icon to (_itemData.image?.pristine || _itemData.image || _itemData.icon || "📦")>> <<set _isEmoji to _icon.length <= 4 && !_icon.includes('/')>> <<set _isCollectionActive to false>> <<if $nexus_mode === "collection">> <<if _itemData.type === "collection" && _itemData.subtype === "artifact">> <<run _isCollectionActive = window.isArtifactEquipped(_itemId)>> <<elseif _itemData.category && (_itemData.category.includes("skin_char") || _itemData.category.includes("skin_loc"))>> <<run _isCollectionActive = window.isCollectionSkinActive(_itemId)>> <</if>> <</if>> <div @class="'inv-card' + (_isCollectionActive ? ' collection-active' : '')" @data-rarity="_rarity" @data-item-id="_itemId" @style="'--rarity-color: ' + _rarityColor"> <!-- Секция картинки с GRID --> <div class="inv-card-img-section"> <!-- Картинка --> <div class="inv-img-wrapper"> <<if _isEmoji>> <div class="inv-emoji"><<print _icon>></div> <<else>> <img @src="_icon" @alt="_title" class="inv-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';"> <div class="inv-emoji" style="display:none;">📦</div> <</if>> </div> <!-- Оверлей GRID --> <div class="inv-overlay"> <!-- Редкость верхний левый --> <div class="inv-rarity"><<print _rarity.substring(0,1).toUpperCase()>></div> <!-- Количество нижний левый --> <<if _itemData.stackable && _itemCount > 1>> <div class="inv-count">×<<print _itemCount>></div> <</if>> <!-- Кнопки правый нижний --> <div class="inv-btns"> <<if $shop_mode === "buy">> <<set _shopId to $current_shop_id>> <<set _buyCurrencies to setup.getItemBuyCurrencies(_itemData, _shopId)>> <<set _canBuy to (_buyCurrencies.length > 0)>> <<set _hasAffordable to false>> <<for _curr range _buyCurrencies>> <<if _curr.canAfford>> <<set _hasAffordable to true>> <<break>> <</if>> <</for>> <<set _tooltip to "">> <<if !_canBuy>> <<set _tooltip to setup.L10n.shop_cannot_buy || "Нельзя купить">> <<elseif !_hasAffordable>> <<set _tooltip to setup.L10n.shop_not_enough_money || "Недостаточно денег">> <</if>> <<set _buyTitle to setup.L10n.btn_buy || "Купить">> <<if _canBuy && _hasAffordable>> <button class="inv-btn shop-buy-btn" @data-item-id="_itemId" data-action="buy" @title="_buyTitle"> <img src="img/ico/ico-buy.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">💰</span> </button> <<else>> <button class="inv-btn shop-buy-btn disabled" @data-item-id="_itemId" @data-tooltip="_tooltip" disabled> <img src="img/ico/ico-buy.webp" alt="" class="btn-img" style="opacity:0.3;" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;opacity:0.3;">💰</span> </button> <</if>> <<elseif $shop_mode === "sell">> <<set _shopId to $current_shop_id>> <<set _sellCurrencies to setup.getItemSellCurrencies(_itemData, _shopId)>> <<set _canSell to (_sellCurrencies.length > 0)>> <<set _tooltip to "">> <<if !_canSell>> <<set _tooltip to setup.L10n.shop_cannot_sell || "Магазин не принимает этот товар">> <</if>> <<set _sellTitle to setup.L10n.btn_sell || "Продать">> <<if _canSell>> <button class="inv-btn shop-sell-btn" @data-item-id="_itemId" data-action="sell" @title="_sellTitle"> <img src="img/ico/ico-sell.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">💸</span> </button> <<else>> <button class="inv-btn shop-sell-btn disabled" @data-item-id="_itemId" @data-tooltip="_tooltip" disabled> <img src="img/ico/ico-sell.webp" alt="" class="btn-img" style="opacity:0.3;" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;opacity:0.3;">💸</span> </button> <</if>> <<elseif $nexus_mode === "collection">> <!-- КНОПКИ ДЛЯ КОЛЛЕКЦИЙ --> <<set _showCollectionBtn to false>> <<set _collectionBtnType to "">> <<set _collectionBtnText to "">> <<set _isActive to false>> <!-- Определяем тип кнопки по типу предмета --> <<if _itemData.type === "collection" && _itemData.subtype === "artifact">> <!-- Артефакты - кнопка "Установить" или "Снять" --> <<set _showCollectionBtn to true>> <<run _isActive = window.isArtifactEquipped(_itemId)>> <<if _isActive>> <<set _collectionBtnType to "unequip-artifact">> <<set _collectionBtnText to setup.L10n?.collection_btn_unequip || "Снять">> <<else>> <<set _collectionBtnType to "equip-artifact">> <<set _collectionBtnText to setup.L10n?.collection_btn_equip || "Установить">> <</if>> <<elseif _itemData.category && (_itemData.category.includes("skin_char") || _itemData.category.includes("skin_loc"))>> <!-- Скины персонажей/локаций - кнопка "Активировать" или "Снять" --> <<set _showCollectionBtn to true>> <<run _isActive = window.isCollectionSkinActive(_itemId)>> <<if _isActive>> <<set _collectionBtnType to "deactivate-skin">> <<set _collectionBtnText to setup.L10n?.collection_btn_deactivate || "Снять">> <<else>> <<set _collectionBtnType to "activate-skin">> <<set _collectionBtnText to setup.L10n?.collection_btn_activate || "Активировать">> <</if>> <</if>> <<if _showCollectionBtn>> <button @class="'inv-btn collection-action-btn ' + (_isActive ? 'active-item' : '')" @data-item-id="_itemId" @data-action="_collectionBtnType" @title="_collectionBtnText"> <img src="img/ico/ico-equip.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;"><<if _isActive>>✅<<else>>🎯<</if>></span> </button> <</if>> <<elseif $shop_wardrobe_mode>> <!-- РЕЖИМ ГАРДЕРОБА-МАГАЗИНА --> <<set _isClothing to (_itemData.category && _itemData.category.includes('skin_gg'))>> <<set _canTryOn to _isClothing>> <<set _canBuy to false>> <<set _tooltip to "">> <!-- Проверка гендера для примерки --> <<if _isClothing && _itemData.attributes && _itemData.attributes.gender>> <<set _hero to ($gg === 0) ? "gg_m" : "gg_f">> <<set _charGender to (_hero.includes('_f')) ? 'female' : 'male'>> <<set _allowedGenders to (Array.isArray(_itemData.attributes.gender)) ? _itemData.attributes.gender : [_itemData.attributes.gender]>> <<if !_allowedGenders.includes(_charGender)>> <<set _canTryOn to false>> <<set _tooltip to setup.L10n.wardrobe_gender_mismatch || "Не подходит по полу">> <</if>> <</if>> <!-- Проверка возможности покупки --> <<set _shopId to $current_shop_id>> <<set _buyCurrencies to setup.getItemBuyCurrencies(_itemData, _shopId)>> <<if _buyCurrencies.length > 0>> <<set _hasAffordable to false>> <<for _curr range _buyCurrencies>> <<if _curr.canAfford>> <<set _hasAffordable to true>> <<set _canBuy to true>> <<break>> <</if>> <</for>> <</if>> <!-- Кнопка "Примерить" --> <<set _tryOnTitle to setup.L10n.btn_try_on || "Примерить">> <<if _canTryOn>> <button class="inv-btn inv-try-on-btn" @data-item-id="_itemId" data-action="try-on" @title="_tryOnTitle"> <img src="img/ico/ico-equip.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">👁️</span> </button> <<else>> <button class="inv-btn inv-try-on-btn disabled" @data-item-id="_itemId" @data-tooltip="_tooltip" disabled> <img src="img/ico/ico-equip.webp" alt="" class="btn-img" style="opacity:0.3;" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;opacity:0.3;">⚠️</span> </button> <</if>> <!-- Кнопка "Купить" --> <<set _buyTitle to setup.L10n.btn_buy || "Купить">> <<if _canBuy>> <button class="inv-btn shop-buy-btn" @data-item-id="_itemId" @title="_buyTitle"> <img src="img/ico/ico-buy.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">💰</span> </button> <<else>> <<set _noMoneyTooltip to setup.L10n.shop_not_enough_money || "Недостаточно денег">> <button class="inv-btn shop-buy-btn disabled" @data-item-id="_itemId" @data-tooltip="_noMoneyTooltip" disabled> <img src="img/ico/ico-buy.webp" alt="" class="btn-img" style="opacity:0.3;" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;opacity:0.3;">💰</span> </button> <</if>> <<elseif $body_shop_mode>> <!-- РЕЖИМ МАГАЗИНА ВНЕШНОСТИ --> <<set _isBodyItem to (_itemData.category && _itemData.category.includes('skin_gg') && !_itemData.slot)>> <<set _canTryOn to _isBodyItem>> <<set _tooltip to "">> <!-- Проверка гендера --> <<if _isBodyItem && _itemData.attributes?.gender>> <<set _hero to ($gg === 0) ? "gg_m" : "gg_f">> <<set _charGender to (_hero.includes('_f')) ? 'female' : 'male'>> <<set _allowedGenders to (Array.isArray(_itemData.attributes.gender)) ? _itemData.attributes.gender : [_itemData.attributes.gender]>> <<if !_allowedGenders.includes(_charGender)>> <<set _canTryOn to false>> <<set _tooltip to setup.L10n.wardrobe_gender_mismatch || "Не подходит по полу">> <</if>> <</if>> <!-- Проверяем активирован ли предмет (state === 2) --> <<set _isActive to false>> <<set _isPreview to false>> <<if _itemData.layer && _itemData.layer.length > 0>> <<set _layerId to _itemData.layer[0]>> <!-- Проверяем примерку --> <<if $body_shop_preview && $body_shop_preview[_layerId] === _itemId>> <<set _isPreview to true>> <</if>> <!-- Проверяем реальный state --> <<set _layerData to setup.player_image?.[_hero]?.[_layerId]?.[_itemId]>> <<if _layerData && _layerData.state === 2>> <<set _isActive to true>> <</if>> <</if>> <!-- Кнопки --> <<if _itemData.is_reset_item>> <!-- Кнопка сброса слоя --> <button class="inv-btn body-reset-btn" @data-item-id="_itemId" @data-layer="_itemData.layer[0]" title="Сбросить"> <img src="img/ico/ico-reset.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">🔄</span> </button> <<elseif _isPreview>> <!-- Кнопка снять примерку --> <button class="inv-btn body-cancel-preview-btn" @data-item-id="_itemId" @data-layer="_layerId" title="Снять примерку"> <img src="img/ico/ico-remove.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">👁️</span> </button> <<elseif _isActive && _itemData.subtype === "hairstyle">> <!-- Причёску снять нельзя (нет состояния "лысый") - просто показываем что уже надета --> <button class="inv-btn body-active-indicator disabled" disabled @data-tooltip="setup.L10n.body_already_active || 'Уже надето'"> <img src="img/ico/ico-equip.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">✔️</span> </button> <<elseif _isActive>> <!-- Кнопка удалить (уже активирован) --> <button class="inv-btn body-remove-btn" @data-item-id="_itemId" title="Удалить"> <img src="img/ico/ico-remove.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">❌</span> </button> <<else>> <!-- Кнопка примерить --> <<set _tryOnTitle to setup.L10n.btn_try_on || "Примерить">> <<if _canTryOn>> <button class="inv-btn body-try-on-btn" @data-item-id="_itemId" @title="_tryOnTitle"> <img src="img/ico/ico-equip.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">👁️</span> </button> <</if>> <!-- Проверка возможности покупки (деньги) --> <<set _buyCurrencies to setup.getItemBuyCurrencies(_itemData, $current_shop_id)>> <<set _canBuy to false>> <<for _curr range _buyCurrencies>> <<if _curr.canAfford>> <<set _canBuy to true>> <<break>> <</if>> <</for>> <!-- Кнопка купить/установить --> <<set _buyTitle to setup.L10n.btn_buy || "Купить">> <<if _canBuy>> <button class="inv-btn body-buy-btn" @data-item-id="_itemId" @title="_buyTitle"> <img src="img/ico/ico-buy.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">💰</span> </button> <<else>> <<set _noMoneyTooltip to setup.L10n.shop_not_enough_money || "Недостаточно денег">> <button class="inv-btn body-buy-btn disabled" @data-item-id="_itemId" @data-tooltip="_noMoneyTooltip" disabled> <img src="img/ico/ico-buy.webp" alt="" class="btn-img" style="opacity:0.3;" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;opacity:0.3;">💰</span> </button> <</if>> <</if>> <<elseif $wardrobe_mode>> <!-- РЕЖИМ ГАРДЕРОБА --> <<set _isClothing to (_itemData.category && _itemData.category.includes('skin_gg'))>> <<set _isBodyItem to (_isClothing && !_itemData.slot && _itemData.layer)>> <<set _canEquip to _isClothing>> <<set _tooltip to "">> <!-- Проверка гендера --> <<if _isClothing && _itemData.attributes && _itemData.attributes.gender>> <<set _hero to ($gg === 0) ? "gg_m" : "gg_f">> <<set _charGender to (_hero.includes('_f')) ? 'female' : 'male'>> <<set _allowedGenders to (Array.isArray(_itemData.attributes.gender)) ? _itemData.attributes.gender : [_itemData.attributes.gender]>> <<if !_allowedGenders.includes(_charGender)>> <<set _canEquip to false>> <<set _tooltip to setup.L10n.wardrobe_gender_mismatch || "Не подходит по полу">> <</if>> <</if>> <<if _isBodyItem>> <!-- Кнопка для макияжа/тату (предметы без slot) --> <<set _applyTitle to setup.L10n.btn_apply || "Применить">> <<if _canEquip>> <button class="inv-btn inv-apply-body-btn" @data-item-id="_itemId" data-action="apply-body" @title="_applyTitle"> <img src="img/ico/ico-equip.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">💄</span> </button> <<else>> <button class="inv-btn inv-apply-body-btn disabled" @data-item-id="_itemId" @data-tooltip="_tooltip" disabled> <img src="img/ico/ico-equip.webp" alt="" class="btn-img" style="opacity:0.3;" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;opacity:0.3;">⚠️</span> </button> <</if>> <<else>> <!-- Кнопка для одежды (предметы со slot) --> <<set _equipTitle to setup.L10n.btn_equip || "Надеть">> <<if _canEquip>> <button class="inv-btn inv-equip-btn" @data-item-id="_itemId" data-action="equip" @title="_equipTitle"> <img src="img/ico/ico-equip.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">👔</span> </button> <<else>> <button class="inv-btn inv-equip-btn disabled" @data-item-id="_itemId" @data-tooltip="_tooltip" disabled> <img src="img/ico/ico-equip.webp" alt="" class="btn-img" style="opacity:0.3;" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;opacity:0.3;">⚠️</span> </button> <</if>> <</if>> <<else>> <<set _useTitle to setup.L10n.inv_btn_use || "Использовать">> <<if _itemData.usable>> <button class="inv-btn use" @data-item-id="_itemId" data-action="use" @title="_useTitle"> <img src="img/ico/ico-use.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">⚡</span> </button> <</if>> <<set _equipTitle to setup.L10n.inv_btn_equip || "Экипировать">> <<if _itemData.equippable>> <button class="inv-btn equip" @data-item-id="_itemId" data-action="equip" @title="_equipTitle"> <img src="img/ico/ico-equip.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">👕</span> </button> <</if>> <</if>> </div> </div> </div> <!-- Контент --> <div class="inv-content"> <!-- Название с иконкой истории --> <h3 class="inv-title"> <<if _lore>> <span class="lore-icon" @data-item-id="_itemId">📜</span> <</if>> <<print _title>> </h3> <!-- ОПИСАНИЕ - ВСЕГДА ПОЛНОСТЬЮ --> <<if _desc>> <div class="inv-desc"><<print _desc>></div> <</if>> <!-- Характеристики --> <<set _sellData to ItemSystem.getBestSellPrice(_itemId)>> <!-- Определяем режим: это инвентарь магазина или игрока? --> <<set _isShopInventory to false>> <<set _isPlayerInShop to false>> <!-- Обычный магазин --> <<if $shop_mode === "buy">> <<set _isShopInventory to true>> <<elseif $shop_mode === "sell">> <<set _isPlayerInShop to true>> <</if>> <!-- Магазин гардероба --> <<if $shop_wardrobe_mode && $inventory_filter !== "main_storage">> <<set _isShopInventory to true>> <<elseif $shop_wardrobe_mode && $inventory_filter === "main_storage">> <<set _isPlayerInShop to true>> <</if>> <!-- Магазин внешности --> <<if $body_shop_mode && $inventory_filter !== "main_storage">> <<set _isShopInventory to true>> <<elseif $body_shop_mode && $inventory_filter === "main_storage">> <<set _isPlayerInShop to true>> <</if>> <!-- Получаем цены в зависимости от режима --> <<set _priceList to []>> <<set _priceType to "sell">> <<if _isShopInventory && $current_shop_id>> <<set _priceList to setup.getItemBuyCurrencies(_itemData, $current_shop_id)>> <<set _priceType to "buy">> <<elseif _isPlayerInShop && $current_shop_id>> <<set _priceList to setup.getItemSellCurrencies(_itemData, $current_shop_id)>> <<set _priceType to "sell">> <<elseif _sellData && _sellData.allPrices>> <<set _priceList to _sellData.allPrices>> <<set _priceType to "sell">> <</if>> <<if _itemData.use_effect || _priceList.length > 0>> <div class="inv-stats"> <<if _itemData.use_effect>> <<if _itemData.use_effect.energy>> <span class="stat-badge">⚡+<<print _itemData.use_effect.energy>></span> <</if>> <<if _itemData.use_effect.health>> <span class="stat-badge">❤️+<<print _itemData.use_effect.health>></span> <</if>> <<if _itemData.use_effect.magic>> <span class="stat-badge">🔮+<<print _itemData.use_effect.magic>></span> <</if>> <</if>> <<if _priceList.length > 0>> <<for _priceItem range _priceList>> <<set _currIcon to ItemSystem.getCurrencyIcon(_priceItem.currency)>> <<set _currName to ItemSystem.getCurrencyName(_priceItem.currency)>> <<set _isIconImage to _currIcon.includes('/')>> <<set _priceValue to _priceType === "buy" ? _priceItem.price : (_priceItem.sell || _priceItem.price)>> <!-- Тултип --> <<set _priceTooltip to ''>> <<set _iconHtml to _isIconImage ? '<img src="' + _currIcon + '" style="width: 1em; height: 1em;vertical-align:middle;">' : _currIcon>> <<if _priceType === "sell" && _sellData && _sellData.allPrices>> <<for _p range _sellData.allPrices>> <<if _p.currency === _priceItem.currency>> <<set _shopData to ItemSystem.getShopData(_p.shopId)>> <<set _shopTitle to setup.getLocalizedText(_shopData?.title) || _p.shopId>> <<set _locationTitle to ''>> <<if _shopData?.location>> <<set _locData to setup.categoryDefinitions[_shopData.location]>> <<set _locationTitle to setup.getLocalizedText(_locData?.title) || ''>> <</if>> <<set _priceTooltip += '<div style="margin-bottom:0.5em">'>> <<set _priceTooltip += '<div style="font-weight:bold;color:#edde75">' + _shopTitle + '</div>'>> <<if _locationTitle>> <<set _priceTooltip += '<div style="font-size:0.9em;color:rgba(215,208,161,0.7)">' + _locationTitle + '</div>'>> <</if>> <<set _priceTooltip += '<div style="margin-top:0.2em">' + _iconHtml + ' ' + _currName + ': <span style="color:#edde75;font-weight:bold">' + _p.sell + '</span></div>'>> <<set _priceTooltip += '</div>'>> <</if>> <</for>> <<elseif _priceType === "buy">> <<set _currentShopData to ItemSystem.getShopData($current_shop_id)>> <<set _currentShopTitle to setup.getLocalizedText(_currentShopData?.title) || $current_shop_id>> <<set _priceTooltip += '<div style="margin-bottom:0.5em">'>> <<set _priceTooltip += '<div style="font-weight:bold;color:#edde75">' + _currentShopTitle + '</div>'>> <<set _priceTooltip += '<div style="margin-top:0.2em">' + _iconHtml + ' ' + _currName + ': <span style="color:#edde75;font-weight:bold">' + _priceItem.price + '</span></div>'>> <<set _priceTooltip += '</div>'>> <</if>> <span class="stat-badge price" @data-tooltip="_priceTooltip"> <<if _isIconImage>> <img @src="_currIcon" style="width: 1em;height: 1em;vertical-align:middle;"> <<else>> <<print _currIcon>> <</if>> <<print _priceValue>> </span> <</for>> <</if>> </div> <</if>> <!-- ЛОР (раскрывается кликом на иконку в заголовке) --> <<if _lore>> <div class="inv-lore" @id="'lore-' + _itemId" style="display: none;"> <div class="inv-lore-text"><<print _lore>></div> </div> <</if>> </div> </div> <</for>> </div> <</if>> <</nobr>> <</widget>> <<widget "displayShopInterface">> <<nobr>> <<set _shopId to $current_shop_id>> <<if !_shopId>> <div class="inventory-empty"> <div class="inventory-empty-icon">🏪</div> <div class="inventory-empty-text">Магазин не выбран</div> </div> <<else>> <<set _shopConfig to setup.game_parameters?.inventories?.[_shopId]>> <<set _isNpcShop to _shopId.startsWith('npc_')>> <<if !_shopConfig && !_isNpcShop>> <div class="inventory-empty"> <div class="inventory-empty-icon">⚠️</div> <div class="inventory-empty-text">Конфигурация магазина не найдена</div> </div> <<else>> <div class="shop-container"> <!-- ДИВ 1: МАГАЗИН --> <div class="shop-section shop-vendor"> <div class="shop-section-header"> <div class="shop-header-title"> <<if _shopId.startsWith('npc_')>> <<set _npcId to _shopId.replace('npc_', '')>> <<set _npcData to setup.characters?.[_npcId]>> <<print _npcData ? (setup.getLocalizedText(_npcData.title) || "Торговля") : "Торговля">> <<else>> <<print setup.getLocalizedText(_shopConfig.title) || "Торговая лавка">> <</if>> </div> <button class="shop-close-btn" data-action="close-shop" @data-tooltip="setup.getLocalizedText('btn_close_shop')">✕</button> </div> <div class="shop-inventory-wrapper"> <<set $inventory_filter to _shopId>> <<set $shop_mode to "buy">> <<displayInventoryCards>> </div> </div> <!-- ДИВ 2: ИНВЕНТАРЬ ГГ --> <div class="shop-section shop-player"> <!-- Карточки --> <div class="shop-inventory-wrapper shop-player-scroll"> <<set $inventory_filter to "main_storage">> <<set $shop_mode to "sell">> <<displayInventoryCards>> </div> <!-- Футер внизу --> <div class="shop-section-footer"> <div class="shop-footer-title"> <<print setup.getLocalizedText('title_inventory_main') || "Инвентарь">> </div> </div> </div> </div> <<unset $shop_mode>> <</if>> <</if>> <</nobr>> <</widget>> <<widget "displayWardrobeInterface">> <<nobr>> <!-- Защита от залипших флагов магазина: если сюда попали не через "Закрыть магазин" (напр. переход по вкладке), $shop_wardrobe_mode/$body_shop_mode/$shop_mode могли остаться true с прошлого визита в магазин - тогда displayInventoryCards ошибочно рендерит режим магазина (с проверкой денег и значком 💰) вместо простого "Надеть" в личном гардеробе. --> <<set $shop_wardrobe_mode to false>> <<set $body_shop_mode to false>> <<set $shop_mode to false>> <<set _hero to ($gg === 0) ? "gg_m" : "gg_f">> <div class="wardrobe-container"> <!-- ШАПКА СО СЛОТАМИ (горизонтальный скролл) --> <div class="wardrobe-header"> <div class="wardrobe-slots-scroll"> <!-- Собираем данные об экипировке --> <<set _equipped to {}>> <<if $characters && $characters[_hero] && $characters[_hero].equipped>> <<set _equipped to $characters[_hero].equipped>> <</if>> <<for _slot range setup.GGBoxInterface.getEquipmentSlots()>> <<capture _slot>> <div @class="'equip-slot-card ' + (_slot.equipped ? 'has-item' : '')" @data-slot="_slot.id" @data-tooltip="(() => { if (_slot.equipped) { const item = ItemSystem.getItemData(_slot.equipped); if (item) { return setup.getLocalizedText(item.title) || item.title; } } return _slot.title; })()"> <<if _slot.equipped>> <<set _item to ItemSystem.getItemData(_slot.equipped)>> <<if _item>> <<set _imgSrc to (_item.image?.pristine || _item.image || _item.icon || "")>> <<set _isEmoji to _imgSrc.length <= 4 && !_imgSrc.includes('/')>> <<if _isEmoji>> <div class="slot-emoji"><<print _imgSrc>></div> <<else>> <img @src="_imgSrc" alt="" class="slot-img" onerror="this.style.display='none';"> <</if>> <button class="slot-unequip-btn" @data-slot="_slot.id" title="Снять">✕</button> <</if>> <<else>> <div class="slot-empty-label"><<print _slot.title>></div> <</if>> </div> <</capture>> <</for>> </div> </div> <!-- КОНТЕЙНЕР ДЛЯ ИНВЕНТАРЯ И ПЕРСОНАЖА --> <div class="wardrobe-body"> <!-- ИНВЕНТАРЬ --> <div class="wardrobe-inventory"> <div class="wardrobe-inventory-wrapper"> <<set $inventory_filter to "main_storage">> <<set $wardrobe_mode to true>> <<displayInventoryCards>> <<unset $wardrobe_mode>> </div> </div> <!-- ПЕРСОНАЖ --> <div class="wardrobe-character"> <div class="wardrobe-character-display"> <div class="character-layers"> <<set _heroLayers to setup.LayerSystem.getLayersWithIds('$' + _hero, {type: 'skin', equipped: _equipped})>> <<if _heroLayers && _heroLayers.length > 0>> <<for _layer range _heroLayers>> <img @class="'character-layer' + (typeof _layer !== 'undefined' && _layer && _layer.anim ? ' anim-' + _layer.anim : '')" @src="_layer.image" @data-layer="_layer.layerId" alt=""> <</for>> <<else>> <img @class="'character-layer' + (typeof _layer !== 'undefined' && _layer && _layer.anim ? ' anim-' + _layer.anim : '')" src="img/char/default.png" alt=""> <</if>> </div> </div> </div> </div> </div> <</nobr>> <</widget>> <<widget "displayShopWardrobeInterface">> <<nobr>> <<set _hero to ($gg === 0) ? "gg_m" : "gg_f">> <<set _shopId to $current_shop_id>> <<if !_shopId>> <div class="inventory-empty"> <div class="inventory-empty-icon">🏪</div> <div class="inventory-empty-text">Магазин не выбран</div> </div> <<else>> <<set _shopConfig to setup.game_parameters?.inventories?.[_shopId]>> <<if !_shopConfig || _shopConfig.type !== 'shop'>> <div class="inventory-empty"> <div class="inventory-empty-icon">⚠️</div> <div class="inventory-empty-text">Конфигурация магазина не найдена</div> </div> <<else>> <div class="wardrobe-container shop-wardrobe-container"> <!-- ШАПКА С НАЗВАНИЕМ МАГАЗИНА --> <div class="shop-wardrobe-header"> <div class="shop-wardrobe-title"> <<print setup.getLocalizedText(_shopConfig.title) || "Магазин одежды">> </div> <button class="shop-close-btn" data-action="close-shop" @data-tooltip="setup.getLocalizedText('btn_close_shop')"> ✕ </button> </div> <!-- ШАПКА СО СЛОТАМИ (горизонтальный скролл) --> <div class="wardrobe-header"> <div class="wardrobe-slots-scroll"> <!-- Собираем данные об экипировке --> <<set _equipped to {}>> <<if $characters && $characters[_hero] && $characters[_hero].equipped>> <<set _equipped to $characters[_hero].equipped>> <</if>> <!-- Получаем примерку если есть --> <<set _previewEquipped to $shop_wardrobe_preview || {}>> <!-- Объединяем реальную экипировку и примерку --> <<set _displayEquipped to {}>> <!-- Сначала копируем всю реальную экипировку --> <<for _slotId, _itemId range _equipped>> <<set _displayEquipped[_slotId] to _itemId>> <</for>> <!-- Применяем примерку (надеваем новое или снимаем) --> <<for _slotId, _itemId range _previewEquipped>> <<if _itemId === null>> <!-- Слот снят в примерке - УДАЛЯЕМ --> <<run delete _displayEquipped[_slotId]>> <<else>> <!-- Надет новый предмет в примерке --> <<set _displayEquipped[_slotId] to _itemId>> <</if>> <</for>> <<for _slot range setup.GGBoxInterface.getEquipmentSlots()>> <<capture _slot>> <<set _slotItemId to _displayEquipped[_slot.id] || null>> <<set _isPreview to _previewEquipped[_slot.id] !== undefined>> <div @class="'equip-slot-card ' + (_slotItemId ? 'has-item' : '') + (_isPreview ? ' preview-item' : '')" @data-slot="_slot.id" @data-tooltip="(() => { if (_slotItemId) { const item = ItemSystem.getItemData(_slotItemId); if (item) { return setup.getLocalizedText(item.title) || item.title; } } return _slot.title; })()"> <<if _slotItemId>> <<set _item to ItemSystem.getItemData(_slotItemId)>> <<if _item>> <<set _imgSrc to (_item.image?.pristine || _item.image || _item.icon || "")>> <<set _isEmoji to _imgSrc.length <= 4 && !_imgSrc.includes('/')>> <<if _isEmoji>> <div class="slot-emoji"><<print _imgSrc>></div> <<else>> <img @src="_imgSrc" alt="" class="slot-img" onerror="this.style.display='none';"> <</if>> <<if _isPreview>> <button class="slot-remove-preview-btn" @data-slot="_slot.id" title="Снять примерку">👁️</button> <<elseif _slotItemId>> <button class="slot-remove-preview-btn" @data-slot="_slot.id" title="Снять" data-action="remove-equipped">➖</button> <</if>> <</if>> <<else>> <!-- Слот пустой --> <<set _wasRemoved to ($shop_wardrobe_preview && $shop_wardrobe_preview[_slot.id] === null)>> <<if _wasRemoved>> <!-- Одежда была снята в примерке - показываем кнопку вернуть --> <div class="slot-empty-with-action"> <span class="slot-status-text"><<L "slot_removed">></span> <button class="equipment-slot-btn slot-restore-btn" @data-slot="_slot.id" @title="setup.L10n.btn_restore_item || 'Вернуть'"> <img src="img/ico/ico-restore.webp" alt="" class="btn-img" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"> <span class="btn-emoji" style="display:none;">↩️</span> </button> </div> <<else>> <!-- Слот действительно пустой --> <div class="slot-empty"><<L "slot_empty">></div> <</if>> <</if>> </div> <</capture>> <</for>> </div> </div> <!-- КОНТЕЙНЕР ДЛЯ ИНВЕНТАРЯ И ПЕРСОНАЖА --> <div class="wardrobe-body"> <!-- ИНВЕНТАРЬ МАГАЗИНА --> <div class="wardrobe-inventory"> <div class="wardrobe-inventory-wrapper"> <<set $inventory_filter to _shopId>> <<set $shop_wardrobe_mode to true>> <<displayInventoryCards>> <<unset $shop_wardrobe_mode>> </div> </div> <!-- ПЕРСОНАЖ --> <div class="wardrobe-character"> <div class="wardrobe-character-display"> <div class="character-layers"> <<set _heroLayers to setup.LayerSystem.getLayersWithIds('$' + _hero, {type: 'skin', equipped: _displayEquipped})>> <<if _heroLayers && _heroLayers.length > 0>> <<for _layer range _heroLayers>> <img @class="'character-layer' + (typeof _layer !== 'undefined' && _layer && _layer.anim ? ' anim-' + _layer.anim : '')" @src="_layer.image" @data-layer="_layer.layerId" alt=""> <</for>> <<else>> <img @class="'character-layer' + (typeof _layer !== 'undefined' && _layer && _layer.anim ? ' anim-' + _layer.anim : '')" src="img/char/default.png" alt=""> <</if>> </div> </div> </div> </div> </div> <</if>> <</if>> <</nobr>> <</widget>> <<widget "displayBodyShopInterface">> <<nobr>> <<set _hero to ($gg === 0) ? "gg_m" : "gg_f">> <<set _shopId to $current_shop_id>> <<if !_shopId>> <div class="inventory-empty"> <div class="inventory-empty-icon">🏪</div> <div class="inventory-empty-text">Магазин не выбран</div> </div> <<else>> <<set _shopConfig to setup.game_parameters?.inventories?.[_shopId]>> <<if !_shopConfig || _shopConfig.type !== 'shop'>> <div class="inventory-empty"> <div class="inventory-empty-icon">⚠️</div> <div class="inventory-empty-text">Конфигурация магазина не найдена</div> </div> <<else>> <div class="wardrobe-container body-shop-container"> <!-- ШАПКА С НАЗВАНИЕМ МАГАЗИНА --> <div class="shop-wardrobe-header"> <div class="shop-wardrobe-title"> <<print setup.getLocalizedText(_shopConfig.title) || "Магазин внешности">> </div> <button class="shop-close-btn" data-action="close-shop" @data-tooltip="setup.getLocalizedText('btn_close_shop')"> ✕ </button> </div> <!-- КОНТЕЙНЕР ДЛЯ ИНВЕНТАРЯ И ПЕРСОНАЖА --> <div class="wardrobe-body"> <!-- ИНВЕНТАРЬ МАГАЗИНА --> <div class="wardrobe-inventory"> <div class="wardrobe-inventory-wrapper"> <<set $inventory_filter to _shopId>> <<set $nexus_mode to "shop_body">> <<set $current_shop_filter to "filter_body">> <<set $body_shop_mode to true>> <<displayInventoryCards>> </div> </div> <!-- ПЕРСОНАЖ БЕЗ ОДЕЖДЫ (только skin слои) --> <div class="wardrobe-character"> <div class="wardrobe-character-display"> <div class="character-layers"> <<set _equipped to {}>> <<if $characters && $characters[_hero] && $characters[_hero].equipped>> <<set _equipped to $characters[_hero].equipped>> <</if>> <<set _excludedLayers to []>> <<for _slotId, _itemId range _equipped>> <<if _itemId>> <<set _itemData to window.ItemSystem.getItemData(_itemId)>> <<if _itemData && _itemData.layer>> <<set _itemLayers to (Array.isArray(_itemData.layer) ? _itemData.layer : [_itemData.layer])>> <<for _layerId range _itemLayers>> <<run _excludedLayers.push(String(_layerId))>> <</for>> <</if>> <</if>> <</for>> <<set _bodyPreview to $body_shop_preview || {}>> <<set _heroLayers to setup.LayerSystem.getLayersWithIds('$' + _hero, { type: 'skin', excludeLayers: _excludedLayers, previewLayers: _bodyPreview })>> <<if _heroLayers && _heroLayers.length > 0>> <<for _layer range _heroLayers>> <img @class="'character-layer' + (typeof _layer !== 'undefined' && _layer && _layer.anim ? ' anim-' + _layer.anim : '')" @src="_layer.image" @data-layer="_layer.layerId" alt=""> <</for>> <<else>> <img @class="'character-layer' + (typeof _layer !== 'undefined' && _layer && _layer.anim ? ' anim-' + _layer.anim : '')" src="img/char/default.png" alt=""> <</if>> </div> </div> </div> </div> </div> <</if>> <</if>> <</nobr>> <</widget>> <<widget "displayCollectionInterface">> <<nobr>> <<set _hero to ($gg === 0) ? "gg_m" : "gg_f">> <!-- Инициализация структуры коллекций если её нет --> <<if !setup.player?.gg?.slot_collection>> <<run console.warn("⚠️ setup.player.gg.slot_collection не найден в player.json")>> <</if>> <!-- Получаем данные из State.variables (загруженные из player.json) --> <<set _unlockedSlots to $slot_collection_unlocked || [1]>> <<set _equipped to $slot_collection_equipped || {}>> <<set _maxSlots to setup.game_parameters?.item?.slot_collection?.max || 7>> <<set _slotIds to setup.game_parameters?.item?.slot_collection?.id || []>> <div class="collection-container"> <!-- ШАПКА СО СЛОТАМИ (горизонтальный скролл) --> <div class="collection-header"> <div class="collection-slots-scroll"> <<for _i to 0; _i lt _maxSlots; _i++>> <<set _slotId to _slotIds[_i] || (_i + 1)>> <<set _isUnlocked to _unlockedSlots.includes(_slotId)>> <<set _itemId to _equipped[_slotId] || null>> <<set _slotTitle to !_isUnlocked ? (setup.L10n?.collection_slot_locked || 'Слот заблокирован') : (_itemId ? (ItemSystem.getItemData(_itemId)?.title || _itemId) : (setup.L10n?.collection_slot_empty || 'Пустой слот'))>> <div @class="'collection-slot-card ' + (_isUnlocked ? (_itemId ? 'equipped' : 'empty') : 'locked')" @data-slot="_slotId" @data-tooltip="_slotTitle"> <<if !_isUnlocked>> <!-- ЗАБЛОКИРОВАННЫЙ СЛОТ --> <div class="collection-slot-locked"> <span>🔒</span> </div> <<elseif _itemId>> <!-- СЛОТ С ПРЕДМЕТОМ --> <<set _itemData to ItemSystem.getItemData(_itemId)>> <<if _itemData>> <div class="collection-slot-image"> <img @src="_itemData.image?.pristine || 'img/items/default.webp'" @alt="_itemData.title"> </div> <button class="collection-unequip-btn" @data-action="'unequip-collection'" @data-slot="_slotId" @data-item="_itemId" @title="setup.L10n?.collection_btn_unequip || 'Снять'"> ✕ </button> <</if>> <<else>> <!-- ПУСТОЙ РАЗБЛОКИРОВАННЫЙ СЛОТ --> <div class="collection-slot-placeholder"> <span>⭘</span> </div> <</if>> </div> <</for>> </div> </div> <!-- КОНТЕЙНЕР ДЛЯ ИНВЕНТАРЯ И ПЕРСОНАЖА --> <div class="collection-body"> <!-- ИНВЕНТАРЬ КОЛЛЕКЦИЙ --> <div class="collection-inventory"> <div class="collection-inventory-wrapper"> <<set $inventory_filter to "main_storage">> <<set $nexus_mode to "collection">> <<displayInventoryCards>> </div> </div> <!-- ПЕРСОНАЖ ИЛИ ИНФО-ПАНЕЛЬ --> <div class="collection-info"> <div class="collection-info-display"> <div class="collection-stats"> <div class="collection-stats-title"> <<print setup.L10n?.collection_effects_title || "Эффекты коллекций">> </div> <!-- Здесь можно вывести активные бонусы от установленных артефактов --> <<if _equipped && Object.keys(_equipped).length > 0>> <div class="collection-active-effects"> <<print setup.L10n?.collection_equipped_count || "Установлено:">> <<print Object.keys(_equipped).length>> / <<print _unlockedSlots.length>> </div> <<else>> <div class="collection-empty-state"> <<print setup.L10n?.collection_empty_state || "Нет установленных коллекционных предметов">> </div> <</if>> </div> </div> </div> </div> </div> <</nobr>> <</widget>> <<widget "displayMoney">> <<nobr>> <div class="money-display"> <<for _currId, _amount range $money>> <<set _currIcon to ItemSystem.getCurrencyIcon(_currId)>> <<set _currName to ItemSystem.getCurrencyName(_currId)>> <<set _isImage to _currIcon.includes('/')>> <div class="money-item" @data-tooltip="_currName"> <<if _isImage>> <img class="money-icon-img" @src="_currIcon" alt=""> <<else>> <span class="money-icon"><<print _currIcon>></span> <</if>> <span class="money-amount"><<print _amount>></span> </div> <</for>> </div> <</nobr>> <</widget>> <<widget "pageTitle">> <<nobr>> <<set _title to "">> <<set _passageName to passage()>> <<set _title to _passageName>> <!-- Устанавливаем дефолтное значение --> <<if def $pageTitle>> <<set _titleKey to $pageTitle>> <<set _title to (setup.L10n && setup.L10n[_titleKey]) ? setup.L10n[_titleKey] : _titleKey>> <<unset $pageTitle>> <!-- Очищаем после использования --> <<elseif _passageName is "scene" and $currentQuestId>> <!-- Проверяем, является ли это interaction --> <<if $currentQuestId.startsWith('interaction_')>> <!-- Это interaction, получаем данные из locations --> <<set _parts to $currentQuestId.split('_')>> <<set _locationId to _parts[1]>> <<set _actionId to _parts[2]>> <<if setup.locations && setup.locations[_locationId] && setup.locations[_locationId].interaction && setup.locations[_locationId].interaction[_actionId]>> <<set _interaction to setup.locations[_locationId].interaction[_actionId]>> <<set _questName to setup.L10n[_interaction.title_interaction] || _interaction.title_interaction || "Действие">> <<else>> <<set _questName to "Действие">> <</if>> <<else>> <!-- Обычный квест --> <<set _questDef to setup.questDefinitions[$currentQuestId]>> <<if _questDef>> <<set _questName to setup.L10n[_questDef.title] || _questDef.title>> <<else>> <<set _questName to "Квест">> <</if>> <</if>> <<set _isNarrator to (typeof $narrator !== "undefined" && $narrator !== null && $narrator !== "")>> <<if _isNarrator>> <<set _narratorId to $narrator.replace(/^[@#$]/, '')>> <<set _heroName to setup.getCategoryName(_narratorId) || _narratorId>> <<else>> <<set _heroName to $gg === 0 ? $gn_m : $gn_f>> <</if>> <<set _title to _questName + ". " + _heroName>> <!-- Страница nexus с категорией --> <<elseif _passageName is "nexus" and $current_location>> <<if $current_character>> <!-- Персонаж выбран: "Локация. Персонаж" --> <<set _locationName to setup.getCategoryName($current_location)>> <<set _charName to setup.getCategoryName($current_character)>> <<set _title to _locationName + ". " + _charName>> <<else>> <!-- Только локация --> <<set _title to setup.getCategoryName($current_location)>> <</if>> <!-- Стандартные пассажи --> <<else>> <<set _passKey to "pass_" + _passageName>> <<if setup.L10n && setup.L10n[_passKey]>> <<set _title to setup.L10n[_passKey]>> <</if>> <</if>> <<print _title>> <</nobr>> <</widget>> <<widget "displayCategoryContent">> <<nobr>> <<set _categoryId to _args[0]>> <<set _categoryType to _args[1]>> <<set _cacheKey to _categoryId + "_" + _categoryType>> <!-- Приветственный текст персонажа/локации МОЖЕТ отличаться от захода к заходу (сейчас — случайно; позже — по условиям: одежда/время суток/локация). Поэтому НЕ кэшируем результат навсегда, как раньше (там {if} внутри контента вычислялся один раз и застывал на всю игру) — перечитываем и парсим контент заново при каждом реальном заходе на этот экран. "Реальный заход" = сменился _cacheKey с прошлого рендера (не просто перерисовка того же экрана из-за других причин) — иначе текст менял бы вариант прямо во время просмотра. --> <<if $lastCategoryContentKey !== _cacheKey>> <<set $lastCategoryContentKey to _cacheKey>> <<run State.variables.random_roll = Math.floor(Math.random() * 100)>> <</if>> <<set _parsed to setup.loadCategoryContent(_categoryId, _categoryType)>> <<set _page1 to _parsed?.pages?.[1]>> <<if _page1 && _page1.content>> <<for _element range _page1.content>> <<print setup.renderContentElement(_element)>> <</for>> <</if>> <</nobr>> <</widget>> <<widget "checkpoint">> <<run setup.CheckpointSystem?.create(_args[0] || 'manual')>> <</widget>> <<widget "L">> <<nobr>> <<set _key to _args[0]>> <<set _lang to $lang || 'ru'>> /* Формируем текст для отображения */ <<if def setup.L10n and def setup.L10n[_key]>> <<set _text to setup.L10n[_key]>> <<else>> <<set _text to "[" + _key + "]">> <</if>> /* Если только один аргумент - просто выводим текст */ <<if _args.length == 1>> <<print _text>> /* Если есть второй аргумент - создаем ссылку */ <<elseif _args.length >= 2>> <<set _passage to _args[1]>> /* Если есть третий аргумент - это setter */ <<if _args.length >= 3 and _args[2]>> <<set _setter to _args[2]>> <<capture _setter>> <<link _text _passage>> <<run Scripting.evalTwineScript(_setter)>> <</link>> <</capture>> <<else>> /* Простая ссылка без setter */ <<link _text _passage>><</link>> <</if>> <</if>> <</nobr>> <</widget>> <<widget "nexusBody">> <<nobr>> <<include "page-main-background">> <div class="page-main-body"> <<include "page-gg-box">> <div class="div-nexus"> <div class="div-nexus-container"> <div class="div-button-menu"> <img @class="'btn-toggle-gg-panel' + ($gg_panel_hidden ? ' active' : '')" src="img/ico/ico-leftpanel.webp" data-action="toggle-gg-panel" @data-tooltip="setup.getLocalizedText('btn_show_gg_panel')"> <div class="div-button-main"> <img @class="'img_test' + ($nexus_mode eq 'storage' ? ' active' : '')" src="img/ico/ico-storage.webp" data-passage="storage" data-page-id="1" @data-tooltip="setup.getLocalizedText('btn_storage')"> <img @class="'img_test' + ($nexus_mode eq 'collection' ? ' active' : '')" src="img/ico/ico-collection.webp" data-passage="collection" data-page-id="2" @data-tooltip="setup.getLocalizedText('btn_collection')"> <img @class="'img_test' + ($nexus_mode eq 'wardrobe' ? ' active' : '')" src="img/ico/ico-wardrobe.webp" data-passage="wardrobe" data-page-id="3" @data-tooltip="setup.getLocalizedText('btn_wardrobe')"> <<if $nexus_mode eq "map">> <img class="img_test active" src="img/ico/ico-hub.webp" data-page-id="6" @data-tooltip="setup.getLocalizedText('btn_back_to_quests')"> <<else>> <img class="img_test" src="img/ico/ico-map.webp" data-page-id="4" @data-tooltip="setup.getLocalizedText('btn_world_map')"> <</if>> <<if $nexus_mode neq "map">> <img class="img_test btn-wait-time" src="img/ico/ico-time.webp" data-action="open-wait" @data-tooltip="setup.getLocalizedText('btn_wait_time')"> <</if>> </div> <div class="div-button-utilities"> <<if $nexus_mode eq "map">> <img class="map-filter-img active" src="img/ico/ico-filter-all.webp" data-filter="all" @data-tooltip="setup.getLocalizedText('filter_all')"> <img class="map-filter-img" src="img/ico/ico-filter-loc.webp" data-filter="location" @data-tooltip="setup.getLocalizedText('filter_locations')"> <img class="map-filter-img" src="img/ico/ico-filter-char.webp" data-filter="character" @data-tooltip="setup.getLocalizedText('filter_characters')"> <<elseif $nexus_mode eq "storage">> <<set _filters to ItemSystem.getActiveFilters("filter_storage")>> <<if _filters && _filters.length > 0>> <div class="inventory-filters"> <<set _activeFilter to String($active_filter || "1")>> <<for _filter range _filters>> <<capture _filter>> <button @class="'inventory-filter-btn' + (String(_filter.id) === _activeFilter ? ' active' : '')" @data-inventory-filter="_filter.id" @data-filter-type="'filter_storage'" @data-tooltip="_filter.name"> <<if _filter.icon>><span><<print setup.renderIcon(_filter.icon, 'filter-icon')>></span><<else>><span>📦</span><</if>> </button> <</capture>> <</for>> </div> <</if>> <<elseif $nexus_mode eq "collection">> <<set _filters to ItemSystem.getActiveFilters("filter_collection")>> <<if _filters && _filters.length > 0>> <div class="inventory-filters"> <<set _activeFilter to String($active_filter || "1")>> <<for _filter range _filters>> <<capture _filter>> <button @class="'inventory-filter-btn' + (String(_filter.id) === _activeFilter ? ' active' : '')" @data-inventory-filter="_filter.id" @data-filter-type="'filter_collection'" @data-tooltip="_filter.name"> <<if _filter.icon>><span><<print setup.renderIcon(_filter.icon, 'filter-icon')>></span><<else>><span>🏺</span><</if>> </button> <</capture>> <</for>> </div> <</if>> <<elseif $nexus_mode eq "wardrobe">> <<set _filters to ItemSystem.getActiveFilters("filter_wardrobe")>> <<if _filters && _filters.length > 0>> <div class="inventory-filters"> <<set _activeFilter to String($active_filter || "1")>> <<for _filter range _filters>> <<capture _filter>> <button @class="'inventory-filter-btn' + (String(_filter.id) === _activeFilter ? ' active' : '')" @data-inventory-filter="_filter.id" @data-filter-type="'filter_wardrobe'" @data-tooltip="_filter.name"> <<if _filter.icon>><span><<print setup.renderIcon(_filter.icon, 'filter-icon')>></span><<else>><span>👗</span><</if>> </button> <</capture>> <</for>> </div> <</if>> <<elseif $nexus_mode eq "shop" || $nexus_mode eq "shop_wardrobe" || $nexus_mode eq "shop_body">> <<if $current_shop_filter>> <<set _filters to ItemSystem.getActiveFilters($current_shop_filter)>> <<if _filters && _filters.length > 0>> <div class="inventory-filters"> <<set _activeFilter to String($active_filter || "1")>> <<for _filter range _filters>> <<capture _filter>> <button @class="'inventory-filter-btn' + (String(_filter.id) === _activeFilter ? ' active' : '')" @data-inventory-filter="_filter.id" @data-filter-type="$current_shop_filter" @data-tooltip="_filter.name"> <<if _filter.icon>><span><<print setup.renderIcon(_filter.icon, 'filter-icon')>></span><<else>><span>📦</span><</if>> </button> <</capture>> <</for>> </div> <</if>> <</if>> <<elseif $nexus_mode eq "quest">> <img @class="'btn-toggle-completed' + ($show_completed ? ' active' : '')" src="img/ico/ico-scq.webp" data-action="toggle-completed" @data-tooltip="$show_completed ? setup.getLocalizedText('btn_toggle_available') : setup.getLocalizedText('btn_toggle_completed')"> <img @class="'btn-toggle-quest-list' + ($quest_list_hidden ? '' : ' active')" src="img/ico/ico-pan_quest.webp" data-action="toggle-quest-list" @data-tooltip="setup.getLocalizedText('btn_toggle_quest_list')"> <</if>> <img @class="'btn-toggle-quests' + ($quests_panel_hidden ? '' : ' active')" src="img/ico/ico-rightpanel.webp" data-action="toggle-quests" @data-tooltip="setup.getLocalizedText('btn_show_quests')"> </div> </div> <div class="div-nexus-container-box"> <<if $nexus_mode eq "storage">> <div class="div-nexus-container-storage"> <<displayInventoryCards>> </div> <<elseif $nexus_mode eq "shop_wardrobe">> <<displayShopWardrobeInterface>> <<elseif $nexus_mode eq "shop_body">> <<displayBodyShopInterface>> <<elseif $nexus_mode eq "shop">> <<displayShopInterface>> <<elseif $nexus_mode eq "wardrobe">> <<displayWardrobeInterface>> <<elseif $nexus_mode eq "collection">> <<displayCollectionInterface>> <<elseif $nexus_mode eq "map">> <<displayLocationInfo>> <<displayAllCategories>> <<else>> <<displayLocationInfo>> <<include "nexus-mode-quest">> <</if>> </div> </div> <div @class="'div-nexus-container-category' + ($quests_panel_hidden ? ' hidden' : '')"> <<displayCategoriesPanel>> </div> </div> </div> <</nobr>> <</widget>> <<widget "settingsModal">> <<nobr>> <<set _tab to _args[0] || 'game'>> <div class="settings-overlay" id="settings-overlay"> <div class="settings-modal"> <!-- Шапка --> <div class="settings-header"> <div class="settings-title"><<print setup.getLocalizedText('settings_title') || 'Настройки'>></div> <button class="settings-close" data-action="close-settings">✕</button> </div> <!-- Вкладки --> <div class="settings-tabs"> <button class="settings-tab<<if _tab eq 'game'>> active<</if>>" data-settab="game"> <span class="settings-tab-icon">🎮</span> <span class="settings-tab-text"><<print setup.getLocalizedText('settings_tab_game') || 'Игра'>></span> </button> <button class="settings-tab<<if _tab eq 'audio'>> active<</if>>" data-settab="audio"> <span class="settings-tab-icon">🔊</span> <span class="settings-tab-text"><<print setup.getLocalizedText('settings_tab_audio') || 'Звук'>></span> </button> <button class="settings-tab<<if _tab eq 'saves'>> active<</if>>" data-settab="saves"> <span class="settings-tab-icon">💾</span> <span class="settings-tab-text"><<print setup.getLocalizedText('settings_tab_saves') || 'Сохранения'>></span> </button> <button class="settings-tab<<if _tab eq 'links'>> active<</if>>" data-settab="links"> <span class="settings-tab-icon">🔗</span> <span class="settings-tab-text"><<print setup.getLocalizedText('settings_tab_links') || 'Ссылки'>></span> </button> </div> <!-- Контент --> <div class="settings-content"> <!-- ИГРА --> <div class="settings-panel<<if _tab eq 'game'>> active<</if>>" data-setpanel="game" @style="(_tab eq 'game') ? 'display:block' : 'display:none'"> <!-- Язык --> <div class="settings-group"> <div class="settings-group-title"><<print setup.getLocalizedText('settings_other') || 'Прочее'>></div> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('settings_language') || 'Язык'>></div> </div> <select class="settings-select" data-setvar="lang" id="settings-lang-select"> <<set _langs to setup.DataLoader?.config?.languages?.available || ['ru', 'en']>> <<set _langNames to setup.DataLoader?.config?.languages?.names || {ru: 'Русский', en: 'English'}>> <<for _code range _langs>> <<set _flag to (_code eq 'ru') ? '🇷🇺' : ((_code eq 'en') ? '🇬🇧' : '🌐')>> <option @value="_code"><<print _flag + ' ' + (_langNames[_code] || _code)>></option> <</for>> </select> </div> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('settings_show_tooltips') || 'Показывать подсказки интерфейса'>></div> </div> <label class="settings-checkbox"> <<if $show_tooltips>> <input type="checkbox" data-setcheck="show_tooltips" checked> <<else>> <input type="checkbox" data-setcheck="show_tooltips"> <</if>> <span class="checkmark"></span> </label> </div> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('settings_show_hints') || 'Показывать намёки в тексте'>></div> </div> <label class="settings-checkbox"> <<if $show_hints>> <input type="checkbox" data-setcheck="show_hints" checked> <<else>> <input type="checkbox" data-setcheck="show_hints"> <</if>> <span class="checkmark"></span> </label> </div> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('settings_show_scroll_buttons') || 'Кнопки прокрутки вниз/вверх'>></div> </div> <label class="settings-checkbox"> <<if $show_scroll_buttons>> <input type="checkbox" data-setcheck="show_scroll_buttons" checked> <<else>> <input type="checkbox" data-setcheck="show_scroll_buttons"> <</if>> <span class="checkmark"></span> </label> </div> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('settings_show_debuff_tooltips') || 'Показывать подсказку баффов/дебаффов'>></div> </div> <label class="settings-checkbox"> <<if $show_debuff_icons != false>> <input type="checkbox" data-setcheck="show_debuff_icons" checked> <<else>> <input type="checkbox" data-setcheck="show_debuff_icons"> <</if>> <span class="checkmark"></span> </label> </div> </div> <<set _hasCases to ($lang eq 'ru' || $lang eq 'uk' || $lang eq 'pl')>> <!-- Герой муж. --> <div class="settings-group"> <div class="settings-group-title"><<print setup.getLocalizedText('settings_hero_male') || 'Главный герой (муж.)'>></div> <!-- Именительный --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<if _hasCases>><<print setup.getLocalizedText('start_name_hint_who') || 'Имя (кто?)'>><<else>><<print setup.getLocalizedText('start_name_hint_name') || 'Name'>><</if>></div> </div> <input type="text" class="settings-input" data-setvar="gn_m" @value="$gn_m"> </div> <<if _hasCases>> <!-- Родительный --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('start_name_hint_whom_no') || 'Родительный (кого?)'>></div> </div> <input type="text" class="settings-input" data-setvar="gn_m_net" @value="$gn_m_net"> </div> <!-- Дательный --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('start_name_hint_whom_give') || 'Дательный (кому?)'>></div> </div> <input type="text" class="settings-input" data-setvar="gn_m_dam" @value="$gn_m_dam"> </div> <!-- Винительный --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('start_name_hint_whom_see') || 'Винительный (кого?)'>></div> </div> <input type="text" class="settings-input" data-setvar="gn_m_vizhu" @value="$gn_m_vizhu"> </div> <!-- Творительный --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('start_name_hint_whom_proud') || 'Творительный (кем?)'>></div> </div> <input type="text" class="settings-input" data-setvar="gn_m_kem" @value="$gn_m_kem"> </div> <!-- Предложный --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('start_name_hint_whom_about') || 'Предложный (о ком?)'>></div> </div> <input type="text" class="settings-input" data-setvar="gn_m_okom" @value="$gn_m_okom"> </div> <</if>> <!-- Краткое имя --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('start_name_hint_short') || 'Краткое имя'>></div> </div> <input type="text" class="settings-input" data-setvar="gn_m_short" @value="$gn_m_short"> </div> </div> <!-- Героиня жен. --> <div class="settings-group"> <div class="settings-group-title"><<print setup.getLocalizedText('settings_hero_female') || 'Главная героиня (жен.)'>></div> <!-- Именительный --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<if _hasCases>><<print setup.getLocalizedText('start_name_hint_who') || 'Имя (кто?)'>><<else>><<print setup.getLocalizedText('start_name_hint_name') || 'Name'>><</if>></div> </div> <input type="text" class="settings-input" data-setvar="gn_f" @value="$gn_f"> </div> <<if _hasCases>> <!-- Родительный --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('start_name_hint_whom_no') || 'Родительный (нет кого?)'>></div> </div> <input type="text" class="settings-input" data-setvar="gn_f_net" @value="$gn_f_net"> </div> <!-- Дательный --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('start_name_hint_whom_give') || 'Дательный (кому?)'>></div> </div> <input type="text" class="settings-input" data-setvar="gn_f_dam" @value="$gn_f_dam"> </div> <!-- Винительный --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('start_name_hint_whom_see') || 'Винительный (вижу кого?)'>></div> </div> <input type="text" class="settings-input" data-setvar="gn_f_vizhu" @value="$gn_f_vizhu"> </div> <!-- Творительный --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('start_name_hint_whom_proud') || 'Творительный (кем?)'>></div> </div> <input type="text" class="settings-input" data-setvar="gn_f_kem" @value="$gn_f_kem"> </div> <!-- Предложный --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('start_name_hint_whom_about') || 'Предложный (о ком?)'>></div> </div> <input type="text" class="settings-input" data-setvar="gn_f_okom" @value="$gn_f_okom"> </div> <</if>> <!-- Краткое имя --> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('start_name_hint_short') || 'Краткое имя'>></div> </div> <input type="text" class="settings-input" data-setvar="gn_f_short" @value="$gn_f_short"> </div> </div> </div> <!-- ЗВУК --> <div class="settings-panel<<if _tab eq 'audio'>> active<</if>>" data-setpanel="audio" @style="(_tab eq 'audio') ? 'display:block' : 'display:none'"> <div class="settings-group"> <div class="settings-group-title"><<print setup.getLocalizedText('settings_volume') || 'Громкость'>></div> <<set _volumes to setup.MediaSystem?.globalVolume || {master: 1, music: 1, sfx: 1, ambient: 1, video: 1}>> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('settings_volume_master') || 'Общая громкость'>></div> </div> <div class="settings-slider-wrap"> <input type="range" class="settings-slider" data-setvol="master" min="0" max="100" @value="Math.round(_volumes.master * 100)"> <span class="settings-slider-value"><<print Math.round(_volumes.master * 100)>>%</span> </div> </div> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('settings_volume_music') || 'Музыка'>></div> </div> <div class="settings-slider-wrap"> <input type="range" class="settings-slider" data-setvol="music" min="0" max="100" @value="Math.round(_volumes.music * 100)"> <span class="settings-slider-value"><<print Math.round(_volumes.music * 100)>>%</span> </div> </div> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('settings_volume_sfx') || 'Звуковые эффекты'>></div> </div> <div class="settings-slider-wrap"> <input type="range" class="settings-slider" data-setvol="sfx" min="0" max="100" @value="Math.round(_volumes.sfx * 100)"> <span class="settings-slider-value"><<print Math.round(_volumes.sfx * 100)>>%</span> </div> </div> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('settings_volume_ambient') || 'Фоновые звуки'>></div> </div> <div class="settings-slider-wrap"> <input type="range" class="settings-slider" data-setvol="ambient" min="0" max="100" @value="Math.round(_volumes.ambient * 100)"> <span class="settings-slider-value"><<print Math.round(_volumes.ambient * 100)>>%</span> </div> </div> <div class="settings-item"> <div class="settings-item-label"> <div class="settings-item-name"><<print setup.getLocalizedText('settings_volume_video') || 'Видео'>></div> </div> <div class="settings-slider-wrap"> <input type="range" class="settings-slider" data-setvol="video" min="0" max="100" @value="Math.round(_volumes.video * 100)"> <span class="settings-slider-value"><<print Math.round(_volumes.video * 100)>>%</span> </div> </div> </div> </div> <!-- СОХРАНЕНИЯ --> <div class="settings-panel<<if _tab eq 'saves'>> active<</if>>" data-setpanel="saves" @style="(_tab eq 'saves') ? 'display:block' : 'display:none'"> <div class="settings-group"> <div class="settings-group-title"><<print setup.getLocalizedText('settings_save_slots') || 'Слоты сохранений'>></div> <div class="save-slots-grid" id="settings-save-slots"> <!-- Заполняется через JS --> </div> </div> <div class="settings-group"> <div class="settings-group-title"><<print setup.getLocalizedText('settings_actions') || 'Действия'>></div> <div class="settings-btn-group"> <button type="button" class="settings-btn danger" data-setaction="restart"> 🔄 <<print setup.getLocalizedText('settings_restart') || 'Рестарт игры'>> </button> <button type="button" class="settings-btn" data-setaction="export"> 📤 <<print setup.getLocalizedText('settings_export') || 'Экспорт'>> </button> <button type="button" class="settings-btn" data-setaction="import"> 📥 <<print setup.getLocalizedText('settings_import') || 'Импорт'>> </button> </div> </div> </div> <!-- ССЫЛКИ --> <div class="settings-panel<<if _tab eq 'links'>> active<</if>>" data-setpanel="links" @style="(_tab eq 'links') ? 'display:block' : 'display:none'"> <div class="settings-links-container"> <!-- Авторские ссылки со скроллом --> <div class="settings-links-scroll"> <<set _links to setup.DataLoader?.config?.settings_links || []>> <<if _links.length > 0>> <div class="settings-links-grid"> <<for _link range _links>> <a @href="_link.url" target="_blank" class="settings-link"> <span class="settings-link-icon"><<print _link.icon>></span> <div class="settings-link-text"> <div class="settings-link-title"><<print _link.title>></div> <div class="settings-link-desc"><<print setup.getLocalizedText(_link.desc) || _link.desc>></div> </div> <span class="settings-link-arrow">→</span> </a> <</for>> </div> <</if>> </div> <!-- Обязательная ссылка Loom --> <div class="settings-loom-credit"> <a href="https://loomcraft.net/" target="_blank" class="loom-credit-link"> <span class="loom-icon">🧵</span> <span class="loom-text">Powered by Loom</span> </a> </div> </div> </div> </div> </div> </div> <<script>> setTimeout(function() { $('#settings-lang-select').val(State.variables.lang || 'ru'); if (typeof setup.SettingsModal !== 'undefined' && typeof setup.SettingsModal.updateSaveSlots === 'function') { setup.SettingsModal.updateSaveSlots(); } }, 50); <</script>> <</nobr>> <</widget>> <</nobr>>
<div class="page">\ <<include "page-header">>\ <div class="page-main">\ <<include "page-main-background">>\ <div class="page-main-body">\ <<include "page-gg-box">>\ <div class="div-story" id="quest-story-scroll">\ <<nobr>> <<if $currentQuestData and $currentQuestId>> <div class="quest-scroll-viewport"> <<displayQuestContent $currentQuestId>> </div> <<if $show_scroll_buttons !== false>> <div class="quest-scroll-buttons" id="quest-scroll-buttons"> <button type="button" class="quest-scroll-btn quest-scroll-btn-up" aria-label="Scroll up" title="<<print setup.getLocalizedText('scroll_up') || 'Прокрутить вверх'>>">▲</button> <button type="button" class="quest-scroll-btn quest-scroll-btn-down" aria-label="Scroll down" title="<<print setup.getLocalizedText('scroll_down') || 'Прокрутить вниз'>>">▼</button> </div> <</if>> <<else>> <div class="error"><<L "error_quest_not_loaded">> <a href="nexus"><<L "Return_Nexus">></a></div> <</if>> <</nobr>>\ </div>\ </div>\ </div>\ <<nobr>> <<set _questData to $currentQuestData>> <<set _currentPage to $currentQuestPage || 1>> <<if _questData and _questData.pages and _questData.pages[_currentPage] and _questData.pages[_currentPage].sceneButtons and _questData.pages[_currentPage].sceneButtons.length > 0>> <div class="page-futer"> <<renderSceneButtons>> </div>\ <</if>> <</nobr>>\ </div>\
<<nobr>> <!-- Проверяем тип категории: локация или персонаж --> <<if $current_category === 'location'>> <!-- РЕЖИМ ЛОКАЦИИ --> <div class="div-nexus-content"> <div class="div-nexus-content-center"> <div class="div-nexus-desc-category-box"> <<if $selected_quest>> <!-- Показываем описание выбранного квеста --> <<set _selectedQuest to setup.questDefinitions[$selected_quest]>> <<if _selectedQuest>> <h3><<print setup.L10n[_selectedQuest.title] || _selectedQuest.title>></h3> <p><<print setup.L10n[_selectedQuest.description] || _selectedQuest.description || "Описание квеста">></p> <<else>> <p><<L "error_quest_not_found">></p> <</if>> <<else>> <!-- Показываем описание локации --> <<displayCategoryContent $current_location "location">> <</if>> </div> <div class="div-nexus-desc-category-choice-menu"> <div class="div-nexus-desc-category-choice-box"> <<if $selected_quest>> <!-- Кнопки для выбранного квеста --> <<set _selQuest to setup.questDefinitions[$selected_quest]>> <<if _selQuest && _selQuest.status === "preview">> <button class="btn-accept-quest disabled" disabled> <<print setup.L10n.btn_quest_locked || "Квест недоступен">> </button> <<elseif $activeQuests.includes($selected_quest)>> <button class="btn-accept-quest"> <<print setup.L10n.btn_accept_quest || "Принять квест">> </button> <</if>> <button class="btn-back-quest"> <<print setup.L10n.btn_back || "Назад">> </button> <</if>> </div> </div> </div> <!-- Панель квестов справа --> <div @class="'div-quest-list-box' + ($quest_list_hidden ? ' hidden' : '')"> <!-- Заголовок списка квестов --> <div class="quest-list-header"> <h3><<if $show_completed>><<print setup.L10n.quest_list_completed || "Завершенные квесты">><<else>><<print setup.L10n.quest_list_available || "Доступные квесты">><</if>></h3> </div> <!-- Список квестов --> <div class="div-quest-list"> <<displayQuestList $current_location $show_completed>> </div> </div> </div> <<elseif $current_category === 'character'>> <!-- РЕЖИМ ПЕРСОНАЖА --> <div class="div-nexus-content"> <div class="div-nexus-content-center"> <div class="div-nexus-desc-category-box"> <<if $selected_quest>> <!-- Показываем описание выбранного квеста --> <<set _selectedQuest to setup.questDefinitions[$selected_quest]>> <<if _selectedQuest>> <h3><<print setup.L10n[_selectedQuest.title] || _selectedQuest.title>></h3> <p><<print setup.L10n[_selectedQuest.description] || _selectedQuest.description || "Описание квеста">></p> <<else>> <p><<L "error_quest_not_found">></p> <</if>> <<else>> <!-- Показываем описание персонажа --> <<displayCategoryContent $current_character "character">> <</if>> </div> <div class="div-nexus-desc-category-choice-menu"> <div class="div-nexus-desc-category-choice-box"> <<if $selected_quest>> <!-- Кнопки для выбранного квеста --> <<set _selQuest to setup.questDefinitions[$selected_quest]>> <<if _selQuest && _selQuest.status === "preview">> <button class="btn-accept-quest disabled" disabled> <<print setup.L10n.btn_quest_locked || "Квест недоступен">> </button> <<elseif $activeQuests.includes($selected_quest)>> <button class="btn-accept-quest"> <<print setup.L10n.btn_accept_quest || "Принять квест">> </button> <</if>> <button class="btn-back-quest"> <<print setup.L10n.btn_back || "Назад">> </button> <</if>> </div> </div> </div> <!-- Панель квестов персонажа справа (как у локаций; портрет/бары/кнопки НПС теперь в displayLocationInfo) --> <div @class="'div-quest-list-box' + ($quest_list_hidden ? ' hidden' : '')"> <div class="quest-list-header"> <h3><<if $show_completed>><<print setup.L10n.quest_list_completed || "Завершенные квесты">><<else>><<print setup.L10n.quest_list_available || "Доступные квесты">><</if>></h3> </div> <div class="div-quest-list"> <<displayQuestList $current_character $show_completed>> </div> </div> </div> <<else>> <!-- Неизвестный тип категории --> <div class="div-nexus-content"> <div class="div-nexus-content-center"> <div class="div-nexus-desc-category-box"> <p><<L "error_unknown_category">></p> <p>current_category: <<print $current_category>></p> <p>current_location: <<print $current_location>></p> <p>current_character: <<print $current_character>></p> </div> </div> </div> <</if>> <</nobr>>