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 $hiddenQuests = []>> <<set $lockedQuests = []>> <<set $questProgress = {}>> <<set $questCompletionDay = {}>> <<set $questCompletionAbsoluteDay = {}>> <<set $questCompletedBy = {}>> <<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 $story_side_panel_hidden to false>> /* === НАВИГАЦИЯ === */ <<set $current_location to "home">> <<set $current_character to "">> <<set $current_category to "location">> <<set $nexus_mode to "quest">> <<set $phone_tab to "journal">> <<set $selected_quest to "">> <<set $show_completed to false>> <<set $quest_view_mode to "available">> <<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 $layer_states = {}>> <<set $activated_skins = {}>> <<set $slot_collection_unlocked = []>> <<set $slot_collection_equipped = {}>> <<set $active_buffs = { gg_m: [], gg_f: [] }>> <<set $permanent_buffs = { gg_m: {}, gg_f: {}, shared: {} }>> <<set $professions = { gg_m: {}, gg_f: {} }>> <<set $outfitPresets = { gg_m: { active: 1, slots: {} }, gg_f: { active: 1, slots: {} } }>> <<set $inventory_filter = "main_storage">> <<set $collection_view_mode = "items">> <<set $selected_collection_item = "">> <<set $achievement_filter = "all">> <<set $selected_achievement = "">> <<set $unlockedAchievements = {}>> <<set $discoveredAchievements = {}>> <<set $seenAchievements = {}>> <<set $hiddenAchievements = []>> <<set $hiddenAchievementTags = []>> <<set $achievementRewardClaims = {}>> <<set $achievementUnlockOrder = []>> /* === НАСТРОЙКИ ЗВУКА === */ <<set $settings = { masterVolume: 1.0, musicVolume: 1.0, sfxVolume: 1.0, ambientVolume: 1.0, videoVolume: 1.0, isMuted: false, musicPaused: false }>> /* === UI === */ <<set $active_gg_tab to "character">> <<set $systemState = { minigames: { unlocked: {}, progress: {}, records: {} }, inventory: { version: 1, nextLotId: 1, backpackId: "backpack_basic", lots: [] }, statistics: { protagonists: {}, characters: {}, locations: {} } }>> <<set $saveSchemaVersion = 8>> /* === ЛОКАЛИЗАЦИЯ === */ <<set $lang = "en">> /* === ФЛАГ ИНИЦИАЛИЗАЦИИ === */ <<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') || 'Показать/Скрыть панель'">\ <img @class="'btn-toggle-story-side-panel' + ($story_side_panel_hidden ? ' active' : '')" src="img/ico/ico-rightpanel.webp" data-action="toggle-story-side-panel" @data-tooltip="setup.getLocalizedText('tooltip_toggle_story_side_panel') || 'Показать/Скрыть боковых персонажей'">\ <</if>>\ <<mediaControls>>\ <<set _langConfig to setup.DataLoader?.config?.languages>>\ <<set _availableLangs to _langConfig?.available || ['ru', 'en']>>\ <<set _currentLang to _availableLangs.includes($lang) ? $lang : (_langConfig?.default || 'ru')>>\ <<set _langFlags to _langConfig?.flags || {}>>\ <button type="button" class="language-picker-toggle" data-action="toggle-language-menu" @data-current-lang="_currentLang" @aria-label="setup.getLocalizedText('settings_language')" @data-tooltip="setup.getLocalizedText('settings_language')" aria-haspopup="listbox" aria-expanded="false">\ <img class="language-picker-current-flag" @src="_langFlags[_currentLang] || ('img/ico/flag_' + _currentLang + '.webp')" alt="">\ <span class="language-picker-caret" aria-hidden="true">⌄</span>\ </button>\ /* История (мелкие шаги) */ <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 { 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; } } } if (setup.MusicManager && State.variables.current_location) { setup.MusicManager.startLocationMusic(State.variables.current_location); } 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 (setup.updateQuestEndSpacing) setup.updateQuestEndSpacing(container); 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 && !State.temporary.preserveQuestBackground>> <<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())>> <<set _isWaitChoiceMenu to ($isInteraction && $currentQuestId === "interaction_home_wait")>> <<set _isSleepChoiceMenu to ($isInteraction && $currentQuestId === "interaction_home_sleep")>> <div @class="'choice-story-menu' + (_pickBlocked ? ' pick-blocked' : '') + ($show_hints ? ' hints-on' : ' hints-off') + (_isWaitChoiceMenu ? ' choices-compact wait-choice-menu' : '') + (_isSleepChoiceMenu ? ' choices-compact sleep-choice-menu' : '')"> <<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">> <<if _isWaitChoiceMenu && setup.WaitSystem>> <<link setup.renderChoiceLabel(_choice)>> <<run setup.WaitSystem.performChoice(_choiceIndex, _page.choices[_choiceIndex].effects, _page.choices[_choiceIndex].value)>> <</link>> <<else>> <<link setup.renderChoiceLabel(_choice)>> <<run setup.navigateQuestPage(_choiceIndex, _page.choices[_choiceIndex].effects, _page.choices[_choiceIndex].value)>> <</link>> <</if>> <<elseif _choice.action === "passage">> <<link setup.renderChoiceLabel(_choice)>> <<run setup.navigateQuestPassage(_choiceIndex, _page.choices[_choiceIndex].effects, _choice.target)>> <</link>> <<elseif _choice.action === "complete">> <<link setup.renderChoiceLabel(_choice)>> <<run setup.completeQuestTransition(_choiceIndex, _page.choices[_choiceIndex].effects)>> <</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.navigateSceneButton(_btn)>> <</link>> <</capture>> <<else>> <!-- Обычный переход на пассаж --> <<capture _btn>> <<set _btnText to setup.L10n[_btn.l10n] || _btn.l10n>> <<link _btnText>> <<run setup.navigateSceneButton(_btn)>> <</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 _viewMode to _args.length > 1 ? _args[1] : setup.getQuestViewMode()>> <<if _viewMode is true>> <<set _viewMode to "completed">> <<elseif _viewMode is false>> <<set _viewMode to "available">> <</if>> <<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>> <<if _questData.data && _questData.data.type === "forced">> <<continue>> <</if>> <<set _questGender to _questData.data.gender || "any">> <<if _questGender === "any" || _questGender === _currentHeroId>> <<set _meetsRequirements to setup.checkRequirements(_questId)>> <<set _isCompleted to $completedQuests.includes(_questId)>> <<set _isHidden to setup.isQuestHidden(_questId)>> <<set _showQuest to false>> <<if _viewMode === "completed">> <<set _showQuest to _isCompleted>> <<elseif _viewMode === "hidden">> <<set _showQuest to _isHidden && !_isCompleted>> <<else>> <<set _showQuest to !_isCompleted && !_isHidden && _meetsRequirements && ($activeQuests.includes(_questId) || _questData.status === "active" || _questData.status === "dev" || _questData.status === "preview")>> <</if>> <<if _showQuest>> <<set _categoryQuests.push({ id: _questId, data: _questData, completed: _isCompleted, hidden: _isHidden })>> <</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 + (_quest.completed ? ' quest-completed' : '') + (_quest.hidden ? ' quest-hidden' : '')" @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 _viewMode === "completed">> <<print setup.getLocalizedText('no_completed_quests') || 'Нет завершённых квестов'>> <<elseif _viewMode === "hidden">> <<print setup.getLocalizedText('no_hidden_quests') || 'Нет скрытых квестов'>> <<else>> <<print setup.getLocalizedText('no_available_quests') || 'Нет доступных квестов'>> <</if>> </div> <</if>> <</nobr>> <</widget>> <<widget "displayQuestJournal">> <<nobr>> <<set _journalEntries to setup.getQuestJournalEntries()>> <section class="quest-journal"> <div class="quest-journal-heading"> <div class="quest-journal-title-row"> <img src="img/ico/ico-journal.webp" class="quest-journal-heading-icon"> <div> <span class="quest-journal-kicker"><<print setup.getLocalizedText('journal_subtitle')>></span> <h2><<print setup.getLocalizedText('journal_title')>></h2> </div> <span class="quest-journal-count"><<print _journalEntries.length>></span> </div> <p><<print setup.getLocalizedText('journal_intro')>></p> </div> <<if _journalEntries.length>> <div class="quest-journal-grid"> <<for _entry range _journalEntries>> <<set _entryTitle to setup.getLocalizedText(_entry.titleKey)>> <<set _entryHint to setup.getLocalizedText(_entry.hintKey)>> <<set _entryColor to setup.getQuestTypeColor(_entry.type)>> <article class="quest-journal-card" @style="'--journal-accent:' + _entryColor"> <div class="quest-journal-card-top"> <span class="quest-journal-status"><<print setup.getLocalizedText(_entry.statusKey)>></span> <span class="quest-journal-location"><<print _entry.categoryName>></span> </div> <h3><<print _entryTitle>></h3> <p><<print _entryHint>></p> </article> <</for>> </div> <<else>> <div class="quest-journal-empty"> <img src="img/ico/ico-journal.webp"> <h3><<print setup.getLocalizedText('journal_empty_title')>></h3> <p><<print setup.getLocalizedText('journal_empty_text')>></p> </div> <</if>> </section> <</nobr>> <</widget>> <<widget "displayBank">> <<nobr>> <<set _bank to setup.EconomySystem.getViewModel()>> <<set _nextPaydayText to setup.getLocalizedText('economy_next_payday_value').replace('{day}', setup.getLocalizedText('title_' + _bank.nextPaydayWeekday)).replace('{time}', String(_bank.paydayHour).padStart(2, '0') + ':00')>> <<set _nextLoanPaymentText to "">> <<if _bank.loan.active>> <<set _nextLoanPaymentText to setup.getLocalizedText('economy_next_loan_payment_value').replace('{day}', setup.getLocalizedText('title_' + _bank.loan.nextPaymentWeekday)).replace('{days}', _bank.loan.nextPaymentDays).replace('{time}', String(_bank.paydayHour).padStart(2, '0') + ':00')>> <</if>> <section class="bank-page"> <header class="bank-hero"> <div class="bank-brand"> <span class="bank-brand-mark">WH</span> <div> <span class="bank-kicker"><<print setup.getLocalizedText('economy_bank_kicker')>></span> <h2><<print setup.getLocalizedText('economy_title')>></h2> <p><<print setup.getLocalizedText('economy_bank_subtitle')>></p> </div> </div> <div class="bank-balance-compact"> <span aria-hidden="true">$</span> <strong><<print Number($money.dollar) || 0>></strong> </div> </header> <div class="bank-layout"> <aside class="bank-jobs-panel"> <div class="bank-section-title"> <span><<print setup.getLocalizedText('economy_jobs_kicker')>></span> <h3><<print setup.getLocalizedText('economy_jobs_title')>></h3> </div> <<if _bank.jobs.length>> <div class="bank-jobs-list"> <<for _job range _bank.jobs>> <article class="bank-job-card"> <div class="bank-job-heading"> <span class="bank-job-icon"><<print _job.icon>></span> <div> <h4><<print setup.getLocalizedText(_job.titleKey)>></h4> <p><<print setup.getLocalizedText(_job.roleKey)>></p> </div> <strong>$<<print _job.pendingGross + _job.pendingBonus>></strong> </div> <div class="bank-pay-range"> <span><small><<print setup.getLocalizedText('economy_pay_min')>></small><b>$<<print _job.min>></b></span> <span class="typical"><small><<print setup.getLocalizedText('economy_pay_typical')>></small><b>$<<print _job.typical>></b></span> <span><small><<print setup.getLocalizedText('economy_pay_max')>></small><b>$<<print _job.max>></b></span> </div> <div class="bank-job-foot"> <span><<print setup.getLocalizedText('economy_job_pending')>></span> <b>$<<print _job.pendingGross>><<if _job.pendingBonus > 0>> + $<<print _job.pendingBonus>><</if>></b> </div> </article> <</for>> </div> <<else>> <div class="bank-empty-card"> <span>⌁</span> <p><<print setup.getLocalizedText('economy_no_jobs')>></p> </div> <</if>> </aside> <div class="bank-main-column"> <section class="bank-payday-card"> <div class="bank-payday-top"> <div> <span class="bank-kicker"><<print setup.getLocalizedText('economy_next_payday')>></span> <h3><<print _nextPaydayText>></h3> </div> <span class="bank-tax-seal">−<<print _bank.taxRate>>%</span> </div> <div class="bank-payday-total"> <span><<print setup.getLocalizedText('economy_expected_deposit')>></span> <strong>$<<print _bank.pending.net>></strong> </div> <div class="bank-payday-breakdown"> <div><span><<print setup.getLocalizedText('economy_pending_wages')>></span><b>$<<print _bank.pending.gross>></b></div> <div><span><<print setup.getLocalizedText('economy_pending_bonus')>></span><b>$<<print _bank.pending.bonus>></b></div> <div><span><<print setup.getLocalizedText('economy_tax')>></span><b>−$<<print _bank.pending.tax>></b></div> </div> <p><<print setup.getLocalizedText('economy_payday_note')>></p> </section> <section class="bank-loan-card"> <div class="bank-section-title"> <span><<print setup.getLocalizedText('economy_credit_kicker')>></span> <h3><<print setup.getLocalizedText('economy_credit_title')>></h3> </div> <<if _bank.loan.active>> <<set _loanPaidPercent to _bank.loan.totalDue > 0 ? Math.round((1 - _bank.loan.remaining / _bank.loan.totalDue) * 100) : 100>> <div class="bank-loan-active"> <<if _bank.loan.blacklisted>> <div class="bank-loan-status danger"><<print setup.getLocalizedText('economy_loan_blacklisted_status').replace('{debt}', _bank.loan.remaining)>></div> <<elseif _bank.loan.overdue>> <div class="bank-loan-status warning"><<print setup.getLocalizedText('economy_loan_overdue_status').replace('{weeks}', _bank.loan.missedWeeks).replace('{multiplier}', _bank.loan.currentMultiplier)>></div> <</if>> <div class="bank-loan-figures"> <div><span><<print setup.getLocalizedText('economy_loan_remaining')>></span><strong>$<<print _bank.loan.remaining>></strong></div> <div><span><<print setup.getLocalizedText('economy_weekly_payment')>></span><strong>$<<print _bank.loan.weeklyPayment>></strong></div> <div><span><<print setup.getLocalizedText('economy_next_loan_payment')>></span><strong><<print _nextLoanPaymentText>></strong></div> <div><span><<print setup.getLocalizedText('economy_next_payment_amount')>></span><strong>$<<print _bank.loan.nextPaymentAmount>></strong></div> </div> <div class="bank-loan-progress"><span @style="'width:' + _loanPaidPercent + '%'"></span></div> <div class="bank-loan-progress-label"><span><<print setup.getLocalizedText('economy_repaid')>></span><b><<print _loanPaidPercent>>%</b></div> <div class="bank-loan-payment"> <label> <span><<print setup.getLocalizedText('economy_advance_amount')>></span> <input class="bank-loan-payment-input" type="number" min="1" @max="_bank.loan.remaining" @value="Math.min(_bank.balance, _bank.loan.nextPaymentAmount || _bank.loan.remaining)" inputmode="numeric"> </label> <div class="bank-loan-payment-actions"> <button type="button" class="bank-primary-action" data-bank-action="advance" @disabled="_bank.balance <= 0 ? true : undefined"><<print setup.getLocalizedText('economy_make_advance_payment')>></button> <button type="button" class="bank-secondary-action" data-bank-action="payoff" @disabled="_bank.balance < _bank.loan.remaining ? true : undefined"><<print setup.getLocalizedText('economy_pay_off')>></button> </div> <p><<print setup.getLocalizedText('economy_advance_payment_note')>></p> </div> <p class="bank-loan-terms"><<print setup.getLocalizedText('economy_loan_late_terms').replace('{max}', _bank.loan.maxDebtMultiplier * 100)>></p> </div> <<elseif _bank.loan.offer>> <div class="bank-loan-offer"> <div class="bank-loan-offer-main"> <span><<print setup.getLocalizedText('economy_available_limit')>></span> <strong>$<<print _bank.loan.offer.principal>></strong> <small><<print setup.getLocalizedText('economy_loan_rate').replace('{rate}', _bank.loan.offer.interestRate).replace('{weeks}', _bank.loan.offer.termWeeks)>></small> </div> <div class="bank-loan-offer-payment"> <span><<print setup.getLocalizedText('economy_weekly_payment')>></span> <b>$<<print _bank.loan.offer.weeklyPayment>></b> </div> <button type="button" class="bank-primary-action" data-bank-action="loan" @data-offer-id="_bank.loan.offer.id"><<print setup.getLocalizedText('economy_take_loan')>></button> <<if _bank.loan.offer.riskAdjusted>> <p class="bank-loan-risk-note"><<print setup.getLocalizedText('economy_loan_risk_rate').replace('{rate}', _bank.loan.offer.interestRate)>></p> <</if>> <p class="bank-loan-terms"><<print setup.getLocalizedText('economy_loan_auto_terms').replace('{max}', _bank.loan.maxDebtMultiplier * 100)>></p> </div> <<else>> <div class="bank-loan-complete"> <span>✓</span> <div> <h4><<print setup.getLocalizedText('economy_all_loans_complete')>></h4> <p><<print setup.getLocalizedText('economy_all_loans_complete_text')>></p> </div> </div> <</if>> </section> <section class="bank-history-card"> <div class="bank-section-title"> <span><<print setup.getLocalizedText('economy_history_kicker')>></span> <h3><<print setup.getLocalizedText('economy_history')>></h3> </div> <<if _bank.history.length>> <div class="bank-history-list"> <<for _payment range _bank.history>> <article class="bank-history-row"> <div class="bank-history-date"> <span><<print setup.EconomySystem.formatHistoryDate(_payment)>></span> <small><<print setup.getLocalizedText('economy_payday_received')>></small> </div> <div class="bank-history-numbers"> <span><small><<print setup.getLocalizedText('economy_history_gross')>></small><b>$<<print _payment.gross + _payment.bonus>></b></span> <span><small><<print setup.getLocalizedText('economy_tax')>></small><b>−$<<print _payment.tax>></b></span> <<if _payment.loan_payment > 0>> <span><small><<print setup.getLocalizedText('economy_credit_title')>></small><b>−$<<print _payment.loan_payment>></b></span> <</if>> <strong>+$<<print _payment.deposited>></strong> </div> </article> <</for>> </div> <<else>> <div class="bank-empty-card"> <span>▤</span> <p><<print setup.getLocalizedText('economy_no_payments')>></p> </div> <</if>> </section> </div> </div> </section> <</nobr>> <</widget>> <<widget "displayCondition">> <<nobr>> <<set _condition to setup.PlayerConditionSystem.getViewModel()>> <<set _energyPercent to Math.min(100, Math.round((_condition.energy / Math.max(1, _condition.energyMax)) * 100))>> <<set _bulkPercent to Math.min(100, Math.round((_condition.carry.load.bulk / Math.max(0.01, _condition.carry.capacity.bulk)) * 100))>> <<set _weightPercent to Math.min(100, Math.round((_condition.carry.load.weight / Math.max(0.01, _condition.carry.capacity.weight)) * 100))>> <section class="condition-page"> <header class="condition-hero"> <div class="condition-hero-mark">+</div> <div class="condition-hero-copy"> <span><<print setup.getLocalizedText('condition_kicker')>></span> <h2><<print setup.getLocalizedText('condition_title')>></h2> <p><<print setup.getLocalizedText('condition_subtitle')>></p> </div> <div class="condition-hero-summary"> <strong><<print _condition.wellness>>%</strong> <small><<print setup.getLocalizedText('condition_wellness')>></small> <span><<print setup.getLocalizedText(_condition.wellnessLabelKey)>></span> <<if _condition.energyPenaltyPercent > 0>> <em><<print setup.getLocalizedText('condition_energy_penalty').replace('{value}', _condition.energyPenaltyPercent)>></em> <</if>> </div> </header> <div class="condition-layout"> <section class="condition-panel condition-vitals"> <div class="condition-section-title"> <span><<print setup.getLocalizedText('condition_vitals')>></span> </div> <div class="condition-vital-list"> <div class="condition-vital-card satiety"> <div class="condition-vital-heading"> <span class="condition-vital-icon">●</span> <b><<print setup.getLocalizedText('condition_satiety')>></b> <strong><<print _condition.satiety>>%</strong> </div> <div class="condition-meter"><span @style="'width:' + _condition.satiety + '%'"></span></div> </div> <div class="condition-vital-card hydration"> <div class="condition-vital-heading"> <span class="condition-vital-icon">◆</span> <b><<print setup.getLocalizedText('condition_hydration')>></b> <strong><<print _condition.hydration>>%</strong> </div> <div class="condition-meter"><span @style="'width:' + _condition.hydration + '%'"></span></div> </div> <div class="condition-vital-card energy"> <div class="condition-vital-heading"> <span class="condition-vital-icon">ϟ</span> <b><<print setup.getLocalizedText('condition_energy')>></b> <strong><<print _condition.energy>>/<<print _condition.energyMax>></strong> </div> <div class="condition-meter"><span @style="'width:' + _energyPercent + '%'"></span></div> </div> </div> </section> <section @class="'condition-panel condition-carry' + (_condition.carry.overloaded ? ' overloaded' : '')"> <div class="condition-section-title"> <div class="condition-backpack-heading"> <img @src="_condition.carry.capacity.icon" alt=""> <span><<print setup.getLocalizedText('condition_load')>></span> </div> <b><<print setup.getLocalizedText(_condition.carry.capacity.title)>></b> </div> <div class="condition-carry-metric"> <div> <span><<print setup.getLocalizedText('condition_bulk')>></span> <b><<print _condition.carry.load.bulk>> / <<print _condition.carry.capacity.bulk>></b> </div> <div class="condition-meter carry"><span @style="'width:' + _bulkPercent + '%'"></span></div> </div> <div class="condition-carry-metric"> <div> <span><<print setup.getLocalizedText('condition_weight')>></span> <b><<print _condition.carry.load.weight>> / <<print _condition.carry.capacity.weight>> <<print setup.getLocalizedText('condition_kg')>></b> </div> <div class="condition-meter carry"><span @style="'width:' + _weightPercent + '%'"></span></div> </div> <<if _condition.carry.overloaded>> <div class="condition-overload"> <strong><<print setup.getLocalizedText('condition_overloaded')>></strong> <span><<print setup.getLocalizedText('condition_overloaded_text')>></span> </div> <</if>> </section> <section class="condition-panel condition-body"> <div class="condition-section-title"> <span><<print setup.getLocalizedText('condition_body')>></span> </div> <div class="condition-body-grid"> <div><span><<print setup.getLocalizedText('condition_height')>></span><b><<print _condition.height>> <<print setup.getLocalizedText('condition_cm')>></b></div> <div><span><<print setup.getLocalizedText('condition_body_weight')>></span><b><<print _condition.weight>> <<print setup.getLocalizedText('condition_kg')>></b></div> <div><span><<print setup.getLocalizedText('condition_chest')>></span><b><<print _condition.body.chest_cm>> <<print setup.getLocalizedText('condition_cm')>></b></div> <div><span><<print setup.getLocalizedText('condition_waist')>></span><b><<print _condition.body.waist_cm>> <<print setup.getLocalizedText('condition_cm')>></b></div> <div><span><<print setup.getLocalizedText('condition_hips')>></span><b><<print _condition.body.hips_cm>> <<print setup.getLocalizedText('condition_cm')>></b></div> <<if _condition.body.penis_cm>> <div><span><<print setup.getLocalizedText('condition_penis')>></span><b><<print _condition.body.penis_cm>> <<print setup.getLocalizedText('condition_cm')>></b></div> <</if>> </div> </section> <section class="condition-panel condition-nutrition"> <div class="condition-section-title"> <span><<print setup.getLocalizedText('condition_nutrition')>></span> </div> <div class="condition-calories"> <span><<print setup.getLocalizedText('condition_calories_today')>></span> <strong><<print _condition.calories>></strong> <small><<print setup.getLocalizedText('condition_kcal')>></small> </div> </section> </div> </section> <</nobr>> <</widget>> <<widget "displayPhone">> <<nobr>> <<if !['journal', 'condition', 'bank'].includes($phone_tab)>><<set $phone_tab to "journal">><</if>> <section class="phone-workspace"> <header class="phone-workspace-bar"> <div class="phone-workspace-brand"> <img src="img/ico/ico-phone.svg"> <div> <span><<print setup.getLocalizedText('phone_owner')>></span> <strong><<print setup.getLocalizedText('phone_title')>></strong> </div> </div> <div class="phone-workspace-status"> <span><<print String($gameTime.hour).padStart(2, '0') + ':' + String($gameTime.minute).padStart(2, '0')>></span> <i></i><i></i><i></i> <b>▰</b> </div> </header> <div class="phone-workspace-content"> <<if $phone_tab eq "condition">> <<displayCondition>> <<elseif $phone_tab eq "bank">> <<displayBank>> <<else>> <<displayQuestJournal>> <</if>> </div> </section> <</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 _charLocId to (_cat.type === 'character') ? ((setup.NPCSimulation ? setup.NPCSimulation.getCachedLocation(_cat.id) : null) || _cat.data.location) : null>> <<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})) : []) || []>> <<if _cat.type === 'character' && _charLocId>> <<set _catBgLayers to setup.LayerSystem.getLayers('@' + _charLocId, {type: 'card'}) || setup.LayerSystem.getLayers('@' + _charLocId, {type: 'background'}) || []>> <<else>> <<set _catBgLayers to [setup.getLocationCardImage(_cat.id)]>> <</if>> <<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 _infoBars to []>> <<for _bar range _catBars>> <<if _bar.type isnot "quest">> <<set _value to _bar.data.value || 0>> <<set _percent to (_bar.data.max > 0) ? Math.min(100, Math.round((_value / _bar.data.max) * 100)) : 0>> <<set _infoBars.push({icon: _bar.icon || "📊", title: setup.getLocalizedText(_bar.title) || _bar.title, value: _percent + "%"})>> <</if>> <</for>> <<set _statsScope to _cat.type === 'character' ? 'characters' : 'locations'>> <<set _activeStatistics to setup.StatisticsSystem ? setup.StatisticsSystem.getVisible(_statsScope, _cat.id) : []>> <<for _stat range _activeStatistics>> <<set _infoBars.push({icon: _stat.icon || "◆", title: setup.getLocalizedText(_stat.title) || _stat.title || _stat.id, value: String(_stat.value)})>> <</for>> <<set _hasInfo to _cat.type === 'character' || _infoBars.length > 0>> <!-- Для персонажа: если его текущая (по расписанию) локация сейчас недоступна игровой механикой (закрыта по времени/дню, или ещё не готова) — затемняем карточку и глушим клик, как у закрытых карточек-переходов между локациями (.location-closed). --> <<set _baseLocked to (_charLocId && !setup.isLocationAccessible(_charLocId))>> <<set _travelTarget to (_cat.type === 'character' ? (_charLocId || State.variables.previous_location || State.variables.current_location) : _cat.id)>> <<set _travelCheck to setup.getTravelAccessForWidget(State.variables.previous_location || State.variables.current_location, _travelTarget, _cat.id, 'map')>> <<set _catLocked to _baseLocked || !_travelCheck.allowed>> <<set _lockText to _baseLocked ? (setup.getLocalizedText('npc_unavailable_now') || 'Сейчас недоступно') : (setup.TravelAccessSystem ? setup.TravelAccessSystem.getReasonText(_travelCheck) : '')>> <div @class="'map-card ' + _priorityClass + ' quest-type-' + _cat.questType + (_catLocked ? ' category-locked' : '')" @style="'border-color: ' + _borderColor + '; --quest-glow-color: ' + _borderColor" @data-category="_catLocked ? '' : _cat.id" @data-type="_cat.type"> <<if _catLocked>> <div class="category-locked-badge" @data-tooltip="_lockText" data-tooltip-required="true">🔒</div> <</if>> <div class="map-card-image-container"> <div class="category-layers-grid"> <<if _catBgLayers && _catBgLayers.length > 0>> <<for _background range _catBgLayers>> <img class="category-layer map-card-location-layer" @src="_background"> <</for>> <</if>> <<if _catLayers && _catLayers.length > 0>> <<for _layer range _catLayers>> <img @class="'category-layer map-card-character-layer' + (_layer.anim ? ' anim-' + _layer.anim : '')" @src="_layer.image"> <</for>> <<elseif !_catBgLayers || _catBgLayers.length === 0>> <img class="category-layer" src="img/placeholder.webp"> <</if>> </div> <<if _hasInfo>> <<if _infoBars.length === 0>> <<set _infoBars.push({icon: "ℹ", title: _catName, value: ""})>> <</if>> <div class="map-card-info-btn" data-info-toggle @data-character-card="_cat.type === 'character' ? _cat.id : ''" @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 _mapQuestBars to _catBars.filter(function(bar) { return bar.type === 'quest'; })>> <<if _mapQuestBars.length gt 0>> <div class="map-card-progress-overlay"> <<universalProgressBars _cat.id "compact" 1 "quest">> </div> <</if>> </div> </div> <</for>> </div> <</nobr>> <</widget>> <<widget "displayStatisticsRows">> <<nobr>> <<set _statisticsScope to _args[0]>> <<set _statisticsOwner to _args[1]>> <<set _statisticsMode to _args[2] || "default">> <<set _statisticsRows to setup.StatisticsSystem ? setup.StatisticsSystem.getVisible(_statisticsScope, _statisticsOwner) : []>> <<if _statisticsRows.length > 0>> <div @class="'action-statistics action-statistics-' + _statisticsMode"> <div class="action-statistics-header"> <span class="action-statistics-header-icon">≡</span> <span><<print setup.getLocalizedText('statistics_title') || 'Статистика'>></span> </div> <div class="action-statistics-list"> <<for _statisticsEntry range _statisticsRows>> <<set _statisticsTitle to setup.getLocalizedText(_statisticsEntry.title) || _statisticsEntry.title || _statisticsEntry.id>> <div class="action-statistics-row"> <span class="action-statistics-icon"><<print _statisticsEntry.icon || '◆'>></span> <span class="action-statistics-title" @data-tooltip="_statisticsTitle" data-tooltip-required="true"><span class="action-statistics-title-text"><<print _statisticsTitle>></span></span> <span class="action-statistics-value"><<print _statisticsEntry.value>></span> </div> <</for>> </div> </div> <<script>> setTimeout(function() { setup.refreshStatisticsOverflow(document); }, 0); <</script>> <</if>> <</nobr>> <</widget>> <<widget "characterProfileModal">> <<nobr>> <<set _profileCharId to _args[0]>> <<set _profileChar to (setup.characters && setup.characters[_profileCharId]) || (setup.characters_minor && setup.characters_minor[_profileCharId])>> <<if _profileChar>> <<set _profileLocationId to setup.CharacterProfileSystem.getLocation(_profileCharId)>> <<set _profileBackgrounds to setup.LayerSystem.getLayers('@' + _profileLocationId, {type: 'background'}) || []>> <<set _profileLayers to setup.LayerSystem.getLayers('#' + _profileCharId, {type: 'skin', withMeta: true}) || []>> <<set _profileBars to setup.getCategoryBars(_profileCharId, 'full')>> <<set _profileQuestBars to _profileBars.filter(function(bar) { return bar.type === 'quest'; })>> <<set _profileOtherBars to _profileBars.filter(function(bar) { return bar.type !== 'quest'; })>> <<set _profileCollectionItems to setup.CharacterProfileSystem.getCompatibleCollectionItems(_profileCharId)>> <<set _profileInventoryItems to setup.CharacterProfileSystem.getInventoryItems(_profileCharId)>> <<set _profileMode to setup.CharacterProfileSystem.mode || 'overview'>> <<set _profileDescription to setup.getLocalizedText(_profileChar.description) || _profileChar.description || ''>> <div class="character-profile-overlay"> <div class="character-profile-dialog" role="dialog" aria-modal="true"> <button type="button" class="character-profile-close" data-action="close-character-profile" @data-tooltip="setup.getLocalizedText('character_profile_close') || 'Закрыть карточку'">×</button> <div class="character-profile-stage"> <div class="character-profile-backgrounds"> <<for _profileBackground range _profileBackgrounds>> <img class="character-profile-background" @src="_profileBackground" alt=""> <</for>> </div> <div class="character-profile-layers"> <<for _profileLayer range _profileLayers>> <img @class="'character-profile-layer' + (_profileLayer.anim ? ' anim-' + _profileLayer.anim : '')" @src="_profileLayer.image" alt=""> <</for>> </div> </div> <aside class="character-profile-items character-profile-collection"> <div class="character-profile-section-title"><<print setup.getLocalizedText('character_profile_collection') || 'Коллекция'>></div> <div class="character-profile-items-scroll"> <<if _profileCollectionItems.length > 0>> <<for _profileItem range _profileCollectionItems>> <button type="button" @class="'character-profile-item' + (_profileItem.active ? ' active' : '') + (!_profileItem.action ? ' passive' : '')" @data-action="_profileItem.action" @data-item-id="_profileItem.id" data-character-profile-item @data-profile-character="_profileCharId" @data-tooltip="_profileItem.title"> <<if _profileItem.image>> <img @src="_profileItem.image" alt=""> <<else>> <span>◆</span> <</if>> <<if _profileItem.count > 1>><span class="character-profile-item-count"><<print _profileItem.count>></span><</if>> </button> <</for>> <<else>> <div class="character-profile-items-empty"><<print setup.getLocalizedText('character_profile_collection_empty') || 'Коллекционных предметов пока нет'>></div> <</if>> </div> </aside> <aside @class="'character-profile-info character-profile-mode-' + _profileMode"> <<set _profileNextMode to _profileMode === 'inventory' ? 'overview' : 'inventory'>> <<set _profileToggleTooltip to _profileMode === 'inventory' ? (setup.getLocalizedText('character_profile_show_overview') || 'Вернуться к сведениям') : (setup.getLocalizedText('character_profile_show_inventory') || 'Посмотреть инвентарь')>> <button type="button" class="character-profile-view-toggle" data-action="toggle-character-profile-view" @data-profile-character="_profileCharId" @data-profile-mode="_profileNextMode" @data-tooltip="_profileToggleTooltip"> <img @src="_profileMode === 'inventory' ? 'img/ico/ico-journal.webp' : 'img/ico/ico-tab-inventory.webp'" alt=""> </button> <header class="character-profile-header"> <h2><<print setup.getCategoryName(_profileCharId)>></h2> <<if _profileDescription>><p><<print _profileDescription>></p><</if>> <div class="character-profile-location"><span>⌖</span><span><<print setup.getLocalizedText('character_profile_location') || 'Сейчас находится'>>: <<print setup.getCategoryName(_profileLocationId)>></span></div> </header> <<if _profileMode === 'inventory'>> <section class="character-profile-inventory-view"> <div class="character-profile-inventory-heading"> <img src="img/ico/ico-tab-inventory.webp" alt=""> <span><<print setup.getLocalizedText('character_profile_inventory') || 'Инвентарь'>></span> <<if _profileInventoryItems.length > 0>><small><<print _profileInventoryItems.length>></small><</if>> </div> <div class="character-profile-inventory-scroll"> <<if _profileInventoryItems.length > 0>> <div class="character-profile-inventory-grid"> <<for _inventoryItem range _profileInventoryItems>> <article class="character-profile-inventory-card" @data-tooltip="_inventoryItem.title + (_inventoryItem.description ? ': ' + _inventoryItem.description : '')"> <div class="character-profile-inventory-image"> <<if _inventoryItem.image>> <img @src="_inventoryItem.image" alt=""> <<else>> <span>◆</span> <</if>> <<if _inventoryItem.count !== 1>><b><<print _inventoryItem.countLabel>></b><</if>> </div> <div class="character-profile-inventory-text"> <h3><<print _inventoryItem.title>></h3> <<if _inventoryItem.description>><p><<print _inventoryItem.description>></p><</if>> </div> </article> <</for>> </div> <<else>> <div class="character-profile-inventory-empty"> <img src="img/ico/ico-tab-inventory.webp" alt=""> <span><<print setup.getLocalizedText('character_profile_inventory_empty') || 'Инвентарь пуст'>></span> </div> <</if>> </div> </section> <<else>> <<if _profileQuestBars.length > 0>> <section class="character-profile-block character-profile-quests"> <div class="character-profile-section-title"><<print setup.getLocalizedText('character_profile_quests') || 'Квесты'>></div> <<universalProgressBars _profileCharId "full" 999 "quest">> </section> <</if>> <<if _profileOtherBars.length > 0>> <section class="character-profile-block character-profile-bars"> <div class="character-profile-section-title"><<print setup.getLocalizedText('character_profile_bars') || 'Отношения и состояние'>></div> <<universalProgressBars _profileCharId "full" 999 "nonquest">> </section> <</if>> <section class="character-profile-statistics-scroll"> <<displayStatisticsRows "characters" _profileCharId "profile">> </section> <</if>> </aside> </div> </div> <</if>> <</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.getLocationTransitionName(_transitionId, _locationId)>> <<set _catDisplayData to setup.getCategoryDisplayData(_transitionId)>> <<set _locBaseAccessible to setup.isLocationAccessible(_transitionId)>> <<set _travelCheck to setup.getTravelAccessForWidget(_locationId, _transitionId, _transitionId, 'transition')>> <<set _locAccessible to _locBaseAccessible && _travelCheck.allowed>> <<set _lockText to !_locBaseAccessible ? (setup.getLocalizedText('location_unavailable_now') || 'Сейчас недоступно') : (setup.TravelAccessSystem ? setup.TravelAccessSystem.getReasonText(_travelCheck) : '')>> <div @class="'transition-card' + (_locAccessible ? '' : ' location-closed')" @style="'border-color: ' + (_locAccessible ? _catDisplayData.borderColor : '#555')" @data-cat-id="_locAccessible ? _transitionId : ''" @data-tooltip="_transitionName + (_locAccessible ? '' : ' 🔒<br>' + _lockText)" @data-tooltip-required="_locAccessible ? 'false' : 'true'"> <<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.getLocationTransitionName(_transitionId, _locationId)>> <<set _catDisplayData to setup.getCategoryDisplayData(_transitionId)>> <<set _locBaseAccessible to setup.isLocationAccessible(_transitionId)>> <<set _travelCheck to setup.getTravelAccessForWidget(_locationId, _transitionId, _transitionId, 'transition')>> <<set _locAccessible to _locBaseAccessible && _travelCheck.allowed>> <<set _lockText to !_locBaseAccessible ? (setup.getLocalizedText('location_unavailable_now') || 'Сейчас недоступно') : (setup.TravelAccessSystem ? setup.TravelAccessSystem.getReasonText(_travelCheck) : '')>> <div @class="'transition-card' + (_locAccessible ? '' : ' location-closed')" @style="'border-color: ' + (_locAccessible ? _catDisplayData.borderColor : '#555')" @data-cat-id="_locAccessible ? _transitionId : ''" @data-tooltip="_transitionName + (_locAccessible ? '' : ' 🔒<br>' + _lockText)" @data-tooltip-required="_locAccessible ? 'false' : 'true'"> <<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.getLocalizedText(_data.short_title) || 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 _baseLocked2 to (_charLocId2 && !setup.isLocationAccessible(_charLocId2))>> <<set _travelTarget2 to (_cat.type === 'character' ? (_charLocId2 || State.variables.current_location) : _cat.id)>> <<set _travelCheck2 to setup.getTravelAccessForWidget(State.variables.current_location, _travelTarget2, _cat.id, 'quick')>> <<set _catLocked2 to _baseLocked2 || !_travelCheck2.allowed>> <<set _lockText2 to _baseLocked2 ? (setup.getLocalizedText('npc_unavailable_now') || 'Сейчас недоступно') : (setup.TravelAccessSystem ? setup.TravelAccessSystem.getReasonText(_travelCheck2) : '')>> <div @class="'quest-card-link quest-type-' + _cat.questType + ' quest-gender-' + _cat.gender + (_catLocked2 ? ' category-locked' : '')" @style="'border-color: ' + setup.getQuestTypeColor(_cat.questType) + '; --quest-glow-color: ' + setup.getQuestTypeColor(_cat.questType)" @data-category="_catLocked2 ? '' : _cat.id" @data-type="_cat.type" @data-cat-id="_catLocked2 ? '' : _cat.id"> <<if _catLocked2>> <div class="category-locked-badge" @data-tooltip="_lockText2" data-tooltip-required="true">🔒</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>> <<set _filterType to _args[3] || "">> <<if _categoryId>> <<set _allBars to setup.getCategoryBars(_categoryId, _mode)>> <<set _filteredBars to _filterType === "quest" ? _allBars.filter(function(bar) { return bar.type === "quest"; }) : (_filterType === "nonquest" ? _allBars.filter(function(bar) { return bar.type !== "quest"; }) : _allBars)>> <<set _displayBars to _filteredBars.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 (_bar.data.max > 0) ? Math.min(100, Math.round((_value / _bar.data.max) * 100)) : 0>> <<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 (_bar.data.max > 0) ? Math.min(100, Math.round((_value / _bar.data.max) * 100)) : 0>> <<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>> <!-- Вычисляем максимум через JS функцию. ВАЖНО: getStatLimits, а не «голый» calculateStatMax — иначе для energy бар показывает потолок БЕЗ штрафа от голода/усталости (calculateStatMax), а clampEnergyToCurrentMax реально зажимает текущее значение по ДРУГОМУ, заниженному потолку (getStatLimits). Из-за этого бар показывал, например, «100 из 300» — сытость/сон урезали текущее значение, а денежная часть дроби («из 300») это игнорировала. --> <<set _maxValue to setup.StatsSystem.getStatLimits(_statName, _currentGG).max>> <!-- Значение не отрицательное И не выше максимума (иначе бар >100%, напр. 293/120) --> <<set _displayValue to Math.max(0, Math.min(_currentValue, _maxValue))>> <<set _percentage to (_maxValue > 0) ? Math.min(100, Math.round((_displayValue / _maxValue) * 100)) : 0>> <!-- Получаем иконку: сначала из конфига, потом дефолтную --> <<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 !== "")>> <<set _ggTabs to setup.GGBoxInterface.getTabs()>> <<if !_ggTabs.some(function(tab) { return tab.id === $active_gg_tab; })>> <<set $active_gg_tab to "character">> <</if>> <div @class="'page-gg-box-skin' + (_isNarrator ? ' narrator-mode' : '')"> <div class="skin-tabs"> <<for _tab range _ggTabs>> <<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="gg-equipment-layout"> <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> <<GGEquipmentEffectsDisplay>> </div> <</nobr>> <</widget>> <<widget "GGEquipmentEffectsDisplay">> <<nobr>> <<set _hero to ($gg === 0) ? "gg_m" : "gg_f">> <<set _effects to setup.EquipmentEffectsSummary.get(_hero)>> <<if _effects.length > 0>> <div class="gg-equipment-effects"> <div class="gg-equipment-effects-scroll"> <<for _effect range _effects>> <<capture _effect>> <<set _effectColor to _effect.total < 0 ? "red" : (_effect.color || "gold")>> <div class="gg-stat-container gg-equipment-effect-row" @data-tooltip="_effect.tooltip"> <div class="gg-stat-background gg-equipment-effect-background" @style="'--gg-bar-percentage: ' + _effect.meter + '%; --gg-bar-color: ' + setup.convertColorToRGBA(_effectColor, 0.3) + ';'"> <div class="gg-stat-content"> <div class="gg-stat-left"> <<if _effect.icon && (_effect.icon.includes("/") || _effect.icon.includes("."))>> <img class="gg-stat-icon-img" @src="_effect.icon" alt=""> <<elseif _effect.icon>> <span class="gg-stat-icon"><<print _effect.icon>></span> <</if>> <span class="gg-stat-title"><<print _effect.label>></span> </div> <div class="gg-stat-right"> <span @class="'gg-stat-percentage gg-equipment-effect-total' + (_effect.total < 0 ? ' negative' : '')"><<print _effect.valueText>></span> </div> </div> </div> </div> <</capture>> <</for>> </div> </div> <</if>> <</nobr>> <</widget>> <<widget "GGStatsDisplay">> <<nobr>> <!-- Отображаем profession_bar (профессии) --> <<displayProfessionBars>> <<set _statsHero to ($gg === 0) ? "gg_m" : "gg_f">> <<displayStatisticsRows "protagonists" _statsHero "gg">> <</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') || 'Общая громкость'"> <div class="music-player-compact"> <span id="music-track-display" class="music-track-display"> <span class="music-track-symbol">♫</span> <span class="music-track-name">—</span> </span> <button type="button" id="music-pause-toggle" class="media-control-btn music-player-btn"> <span class="music-pause-icon">❚❚</span> </button> <button type="button" id="music-next-track" class="media-control-btn music-player-btn"> <span class="music-next-icon">▶▌</span> </button> </div> <!-- Кнопка настроек звука --> <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 $trackDisplay = $('#music-track-display'); const $trackName = $trackDisplay.find('.music-track-name'); const $pauseBtn = $('#music-pause-toggle'); const $pauseIcon = $pauseBtn.find('.music-pause-icon'); const $nextBtn = $('#music-next-track'); if (!State.variables.settings || typeof State.variables.settings !== 'object') { State.variables.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); $pauseBtn.on('click', function() { if (setup.MusicManager) setup.MusicManager.togglePause(); }); $nextBtn.on('click', function() { if (setup.MusicManager) setup.MusicManager.skipCurrentTrack(); }); $(document) .off('musicplayerchange.mediaControls') .on('musicplayerchange.mediaControls', function(event, state) { updateMusicControls(state); }); $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); $volumeSlider.val(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 (value <= 0) { State.variables.settings.isMuted = true; updateMuteButton(true); } else { State.variables.settings.savedVolume = 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); } function localized(key, fallback) { return setup.getLocalizedText ? (setup.getLocalizedText(key) || fallback) : fallback; } function updateMusicControls(state) { const current = state || (setup.MusicManager ? setup.MusicManager.getPlaybackState() : null) || {}; const name = current.name ? String(current.name) : '—'; const paused = current.paused === true; const canSkip = current.canSkip === true; const pauseText = paused ? localized('tooltip_music_resume', 'Продолжить музыку') : localized('tooltip_music_pause', 'Поставить музыку на паузу'); const trackText = current.name ? localized('music_track_now', 'Сейчас играет: {track}').replace('{track}', name) : localized('music_track_none', 'Музыка не играет'); $trackName.text(name); $trackDisplay.attr('data-tooltip', trackText).attr('aria-label', trackText); $pauseIcon.text(paused ? '▶' : '❚❚'); $pauseBtn .attr('data-tooltip', pauseText) .attr('aria-label', pauseText) .attr('aria-pressed', paused ? 'true' : 'false') .toggleClass('music-paused', paused); $nextBtn .prop('disabled', !canSkip) .attr('data-tooltip', localized('tooltip_music_next', 'Следующая мелодия')) .attr('aria-label', localized('tooltip_music_next', 'Следующая мелодия')); } updateMusicControls(); }); <</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' : '') + ($nexus_mode === 'collection' && $selected_collection_item === _itemId ? ' collection-selected' : '')" @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>> <<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"> <span class="btn-emoji">⚡</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>> <<set _dropTitle to setup.L10n.inv_btn_drop || "Выбросить">> <<if _itemData.discardable>> <button class="inv-btn drop" @data-item-id="_itemId" data-action="drop" @title="_dropTitle"> <span class="btn-emoji">🗑</span> </button> <</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-item-id="_slot.equipped || ''" @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"> <<set _presetState to setup.OutfitPresetSystem.ensure(_hero)>> <div class="wardrobe-outfit-presets"> <<for _presetIndex = 1; _presetIndex <= setup.OutfitPresetSystem.count; _presetIndex++>> <<set _preset to _presetState.slots[String(_presetIndex)] || null>> <<set _presetImages to setup.OutfitPresetSystem.getPreviewImages(_preset)>> <button type="button" @class="'wardrobe-outfit-preset' + (_presetState.active === _presetIndex ? ' active' : '') + (_preset ? ' saved' : ' empty')" data-action="apply-outfit-preset" @data-outfit-preset="_presetIndex" @data-tooltip="setup.OutfitPresetSystem.slotTooltip(_hero, _presetIndex)"> <span class="wardrobe-outfit-preset-number"><<print _presetIndex>></span> <<if _presetImages.length > 0>> <span class="wardrobe-outfit-preset-thumbs" aria-hidden="true"> <<for _presetImage range _presetImages>> <img @src="_presetImage.image" alt=""> <</for>> </span> <</if>> </button> <</for>> <<set _activePreset to _presetState.slots[String(_presetState.active)] || null>> <button type="button" @class="'wardrobe-outfit-delete' + (_activePreset ? '' : ' disabled')" data-action="request-delete-outfit-preset" @aria-disabled="_activePreset ? 'false' : 'true'" @data-tooltip="setup.OutfitPresetSystem.deleteTooltip(_hero)"> <span class="wardrobe-outfit-delete-icon" aria-hidden="true"></span> </button> <button type="button" class="wardrobe-outfit-save" data-action="save-outfit-preset" @data-tooltip="setup.OutfitPresetSystem.saveTooltip(_hero)"> <img src="img/ico/ico-save.webp" alt=""> </button> </div> <div class="wardrobe-outfit-delete-confirm" role="dialog" aria-modal="false" aria-hidden="true"> <div class="wardrobe-outfit-delete-confirm-text"></div> <div class="wardrobe-outfit-delete-confirm-actions"> <button type="button" data-action="confirm-delete-outfit-preset"> <<print setup.getLocalizedText('wardrobe_outfit_delete_yes') || 'Удалить'>> </button> <button type="button" data-action="cancel-delete-outfit-preset"> <<print setup.getLocalizedText('wardrobe_outfit_delete_no') || 'Отмена'>> </button> </div> </div> <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">> <<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 || []>> <<set _collectionMode to $collection_view_mode || "items">> <<set _achievementCounts to setup.AchievementSystem ? setup.AchievementSystem.getCounts() : {total: 0, unlocked: 0, unread: 0}>> <div class="collection-container"> <div class="collection-mode-tabs"> <button @class="'collection-mode-tab' + (_collectionMode === 'items' ? ' active' : '')" data-action="set-collection-mode" data-collection-mode="items"> <span class="collection-mode-icon">◆</span> <span><<print setup.L10n?.collection_tab_items || "Коллекции">></span> </button> <button @class="'collection-mode-tab' + (_collectionMode === 'artifacts' ? ' active' : '')" data-action="set-collection-mode" data-collection-mode="artifacts"> <span class="collection-mode-icon">✦</span> <span><<print setup.L10n?.collection_tab_artifacts || "Артефакты">></span> </button> <button @class="'collection-mode-tab' + (_collectionMode === 'achievements' ? ' active' : '')" data-action="set-collection-mode" data-collection-mode="achievements"> <span class="collection-mode-icon">★</span> <span><<print setup.L10n?.collection_tab_achievements || "Достижения">></span> <<if _achievementCounts.unread > 0>> <span class="collection-unread-badge"><<print _achievementCounts.unread>></span> <</if>> </button> <<if _collectionMode === "achievements">> <div class="collection-mode-summary"> <<print setup.L10n?.achievement_count || "Открыто">>: <strong><<print _achievementCounts.unlocked>></strong> / <<print _achievementCounts.total>> </div> <</if>> </div> <<if _collectionMode === "achievements">> <<set _achievementFilter to $achievement_filter || "all">> <<set _achievementEntries to setup.AchievementSystem ? setup.AchievementSystem.getEntries(_achievementFilter) : []>> <<if (!$selected_achievement || !setup.AchievementSystem || !setup.AchievementSystem.definitions || !setup.AchievementSystem.definitions[$selected_achievement]) && _achievementEntries.length > 0>> <<set $selected_achievement to _achievementEntries[0].id>> <</if>> <<set _selectedAchievement to setup.AchievementSystem ? setup.AchievementSystem.getView($selected_achievement) : null>> <div class="collection-workspace achievement-workspace"> <div class="collection-grid-panel"> <<if _achievementEntries.length === 0>> <div class="collection-empty-state"> <<print setup.L10n?.achievement_empty || "В этом разделе пока нет достижений.">> </div> <<else>> <div class="achievement-grid"> <<for _achievement range _achievementEntries>> <button @class="'achievement-card' + (_achievement.unlocked ? ' unlocked' : '') + (_achievement.unread ? ' unread' : '') + ($selected_achievement === _achievement.id ? ' selected' : '')" @data-achievement-id="_achievement.id" type="button"> <span class="achievement-card-icon"><<print setup.AchievementSystem.iconHtml(_achievement.icon)>></span> <span class="achievement-card-copy"> <span class="achievement-card-heading"> <strong><<print _achievement.title>></strong> <<if _achievement.unlocked>><span class="achievement-card-check">✓</span><</if>> </span> <span class="achievement-card-progress"> <span class="achievement-progress-track"> <span @style="'width:' + _achievement.progress.percent + '%'"></span> </span> <span><<print _achievement.progress.current>>/<<print _achievement.progress.total>></span> </span> <<if _achievement.reward>> <span class="achievement-card-reward">◆ <<print _achievement.reward>></span> <</if>> </span> </button> <</for>> </div> <</if>> </div> <aside class="collection-detail-panel achievement-detail-panel"> <<if _selectedAchievement>> <div class="achievement-detail-icon"><<print setup.AchievementSystem.iconHtml(_selectedAchievement.icon)>></div> <div @class="'achievement-detail-status ' + (_selectedAchievement.unlocked ? 'unlocked' : 'progress')"> <<if _selectedAchievement.unlocked>> <<print setup.L10n?.achievement_unlocked || "Получено">> <<else>> <<print setup.L10n?.achievement_in_progress || "В процессе">> <</if>> </div> <h2><<print _selectedAchievement.title>></h2> <p class="achievement-detail-description"><<print _selectedAchievement.description>></p> <div class="achievement-detail-progress"> <span><<print setup.L10n?.achievement_progress || "Прогресс">></span> <strong><<print _selectedAchievement.progress.current>> / <<print _selectedAchievement.progress.total>></strong> </div> <div class="achievement-progress-track large"> <span @style="'width:' + _selectedAchievement.progress.percent + '%'"></span> </div> <h3><<print setup.L10n?.achievement_steps || "Условия">></h3> <ul class="achievement-step-list"> <<for _step range _selectedAchievement.progress.steps>> <li @class="_step.done ? 'done' : ''"> <span><<if _step.done>>✓<<else>>○<</if>></span> <span><<print _step.title>></span> </li> <</for>> </ul> <div class="achievement-reward-box"> <span><<print setup.L10n?.achievement_reward || "Награда">></span> <strong><<print _selectedAchievement.reward || setup.L10n?.achievement_no_reward || "Без отдельной награды">></strong> </div> <<if _selectedAchievement.hidden>> <p class="achievement-hidden-note"><<print setup.L10n?.achievement_hidden_notice || "Скрыто из списка и уведомлений. Прогресс продолжает учитываться.">></p> <button class="achievement-visibility-btn restore" data-action="restore-achievement" @data-achievement-id="_selectedAchievement.id"> <<print setup.L10n?.achievement_restore || "Вернуть">> </button> <<else>> <button class="achievement-visibility-btn" data-action="hide-achievement" @data-achievement-id="_selectedAchievement.id"> <<print setup.L10n?.achievement_hide || "Скрыть">> </button> <</if>> <<else>> <div class="collection-empty-state"> <<print setup.L10n?.achievement_empty || "В этом разделе пока нет достижений.">> </div> <</if>> </aside> </div> <<elseif _collectionMode === "artifacts">> <<set $active_filter to "4">> <div class="collection-workspace artifact-workspace"> <div class="collection-grid-panel collection-inventory artifact-inventory-panel"> <div class="artifact-panel-heading"> <h2><<print setup.L10n?.collection_artifacts_inventory || "Доступные артефакты">></h2> <p><<print setup.L10n?.collection_artifacts_inventory_desc || "Выберите артефакт и нажмите кнопку установки на его карточке.">></p> </div> <div class="collection-inventory-wrapper"> <<set $inventory_filter to "main_storage">> <<set $nexus_mode to "collection">> <<displayInventoryCards>> </div> </div> <aside class="collection-detail-panel artifact-management-panel"> <section class="artifact-panel-heading artifact-panel-heading-main"> <h2><<print setup.L10n?.collection_artifacts_title || "Артефактный стенд">></h2> <p><<print setup.L10n?.collection_artifacts_desc || "Это общие усиления героя. Они устанавливаются здесь и не относятся к выбранному предмету коллекции.">></p> </section> <section class="collection-artifact-panel collection-artifact-panel-global"> <h3><<print setup.L10n?.collection_trophy_slots || "Слоты артефактов">></h3> <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 _slotItemData to _itemId ? ItemSystem.getItemData(_itemId) : null>> <<set _slotTitle to !_isUnlocked ? (setup.L10n?.collection_slot_locked || "Слот заблокирован") : (_slotItemData ? (setup.getLocalizedText(_slotItemData.title) || _slotItemData.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 _slotItemData>> <<set _slotIcon to _slotItemData.image?.pristine || _slotItemData.image || _slotItemData.icon || "★">> <div class="collection-slot-image"><<print setup.AchievementSystem.iconHtml(_slotIcon)>></div> <button class="collection-unequip-btn" data-action="unequip-collection" @data-slot="_slotId" @data-item="_itemId" @title="setup.L10n?.collection_btn_unequip || 'Снять'">×</button> <<else>> <div class="collection-slot-placeholder"><span>☆</span></div> <</if>> </div> <</for>> </div> </section> <section class="collection-effects-panel artifact-effects-panel"> <h3><<print setup.L10n?.collection_active_effects || setup.L10n?.collection_effects_title || "Активные эффекты">></h3> <<if _equipped && Object.keys(_equipped).length > 0>> <div class="collection-active-effects"> <<print setup.L10n?.collection_equipped_count || "Установлено:">> <strong><<print Object.keys(_equipped).length>> / <<print _unlockedSlots.length>></strong> </div> <<else>> <div class="collection-empty-state"> <<print setup.L10n?.collection_empty_state || "Нет установленных артефактов">> </div> <</if>> </section> </aside> </div> <<else>> <<set _selectedItemData to $selected_collection_item ? ItemSystem.getItemData($selected_collection_item) : null>> <div class="collection-workspace"> <div class="collection-grid-panel collection-inventory"> <div class="collection-inventory-wrapper"> <<set $inventory_filter to "main_storage">> <<set $nexus_mode to "collection">> <<displayInventoryCards>> </div> </div> <aside class="collection-detail-panel"> <section class="collection-item-detail"> <h2><<print setup.L10n?.collection_item_details || "Сведения о предмете">></h2> <<if _selectedItemData>> <<set _selectedTitle to setup.getLocalizedText(_selectedItemData.title) || _selectedItemData.title || $selected_collection_item>> <<set _selectedDesc to setup.getLocalizedText(_selectedItemData.description) || _selectedItemData.description || "">> <<set _selectedLore to setup.getLocalizedText(_selectedItemData.lore) || _selectedItemData.lore || "">> <<set _selectedIcon to _selectedItemData.image?.pristine || _selectedItemData.image || _selectedItemData.icon || "◆">> <div class="collection-detail-icon"><<print setup.AchievementSystem.iconHtml(_selectedIcon)>></div> <h3><<print _selectedTitle>></h3> <<if _selectedDesc>><p><<print _selectedDesc>></p><</if>> <<if _selectedLore>><p class="collection-detail-lore"><<print _selectedLore>></p><</if>> <<else>> <p class="collection-select-hint"> <<print setup.L10n?.collection_select_item || "Выберите карточку, чтобы увидеть описание и способ установки.">> </p> <</if>> </section> </aside> </div> <</if>> </div> <</nobr>> <</widget>> <<widget "displayMoney">> <<nobr>> <<set _carry to ItemSystem.getCarryStatus()>> <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 class="carry-compact" @data-tooltip="setup.getLocalizedText(_carry.capacity.title)"> <img @src="_carry.capacity.icon" alt=""> <span><<print _carry.load.bulk>>/<<print _carry.capacity.bulk>></span> <small><<print _carry.load.weight>>/<<print _carry.capacity.weight>> <<print setup.getLocalizedText('condition_kg')>></small> </div> </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')"> <img @class="'img_test' + ($nexus_mode eq 'phone' ? ' active' : '')" src="img/ico/ico-phone.svg" data-page-id="5" @data-tooltip="setup.getLocalizedText('btn_phone')"> <img @class="'img_test' + ($nexus_mode eq 'map' ? ' active' : '')" src="img/ico/ico-map.webp" data-page-id="4" @data-tooltip="setup.getLocalizedText('btn_world_map')"> <<if $nexus_mode neq "map">> <<set _waitAccess to setup.getWaitAccessForWidget($current_location)>> <img @class="'img_test btn-wait-time' + (!_waitAccess.allowed ? ' wait-unavailable' : '')" src="img/ico/ico-time.webp" data-action="open-wait" @data-tooltip="setup.WaitSystem ? setup.WaitSystem.getTooltip(_waitAccess) : 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">> <<if ($collection_view_mode || "items") === "achievements">> <<set _achievementFilters to setup.AchievementSystem?.categories || []>> <div class="inventory-filters achievement-filters"> <<for _filter range _achievementFilters>> <<capture _filter>> <button @class="'inventory-filter-btn' + ((_filter.id === ($achievement_filter || 'all')) ? ' active' : '')" @data-achievement-filter="_filter.id" @data-tooltip="setup.L10n?.[_filter.title] || _filter.title"> <span><<print setup.AchievementSystem.iconHtml(_filter.icon)>></span> </button> <</capture>> <</for>> </div> <<elseif ($collection_view_mode || "items") !== "artifacts">> <<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>> <</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 "phone">> <div class="inventory-filters phone-filters"> <button @class="'inventory-filter-btn' + ($phone_tab eq 'journal' ? ' active' : '')" data-phone-tab="journal" @data-tooltip="setup.getLocalizedText('btn_journal')"> <span class="phone-filter-glyph">▤</span> </button> <button @class="'inventory-filter-btn' + ($phone_tab eq 'condition' ? ' active' : '')" data-phone-tab="condition" @data-tooltip="setup.getLocalizedText('btn_condition')"> <span class="phone-filter-glyph">♥</span> </button> <button @class="'inventory-filter-btn' + ($phone_tab eq 'bank' ? ' active' : '')" data-phone-tab="bank" @data-tooltip="setup.getLocalizedText('btn_bank')"> <span class="phone-filter-glyph">$</span> </button> </div> <<elseif $nexus_mode eq "quest">> <<set _questViewMode to setup.getQuestViewMode()>> <<set _questViewTooltip to _questViewMode === "available" ? setup.getLocalizedText('btn_toggle_completed') : (_questViewMode === "completed" ? setup.getLocalizedText('btn_toggle_hidden') : setup.getLocalizedText('btn_toggle_available'))>> <img @class="'btn-toggle-completed quest-view-' + _questViewMode + (_questViewMode !== 'available' ? ' active' : '')" src="img/ico/ico-scq.webp" data-action="toggle-completed" @data-tooltip="_questViewTooltip"> <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>> <<if $nexus_mode neq "phone">> <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')"> <</if>> </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 "phone">> <<displayPhone>> <<elseif $nexus_mode eq "map">> <<displayLocationInfo>> <<displayAllCategories>> <<else>> <<displayLocationInfo>> <<include "nexus-mode-quest">> <</if>> </div> </div> <<if $nexus_mode neq "phone">> <div @class="'div-nexus-container-category' + ($quests_panel_hidden ? ' hidden' : '')"> <<displayCategoriesPanel>> </div> <</if>> </div> </div> <</nobr>> <</widget>> <<widget "settingsModal">> <<nobr>> <<set _tab to _args[0] || 'game'>> <<set _availableHeroes to setup.ProtagonistSystem?.getAvailableHeroes?.() || setup.game_parameters?.game_settings?.available_protagonists || ['gg_m']>> <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'}>> <<set _langSymbols to setup.DataLoader?.config?.languages?.symbols || {ru: '🇷🇺', en: '🇬🇧'}>> <<for _code range _langs>> <<set _flag to _langSymbols[_code] || '🌐'>> <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')>> <<if _availableHeroes.includes('gg_m')>> <!-- Герой муж. --> <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> <</if>> <<if _availableHeroes.includes('gg_f')>> <!-- Героиня жен. --> <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> <</if>> </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">> <<link setup.getLocalizedText("Return_Nexus") || "Вернуться к выбору квестов">><<run Engine.play("nexus")>><</link>></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> <<set _selectedQuestDesc to ($completedQuests.includes($selected_quest) && _selectedQuest.description_completed) ? _selectedQuest.description_completed : _selectedQuest.description>> <p><<print setup.L10n[_selectedQuestDesc] || _selectedQuestDesc || "Описание квеста">></p> <<if !$completedQuests.includes($selected_quest)>> <<set _selectedQuestAcceptState to setup.getQuestAcceptState($selected_quest)>> <<if _selectedQuestAcceptState && !_selectedQuestAcceptState.allowed>> <div class="quest-accept-block-reasons"> <<for _failure range _selectedQuestAcceptState.failures>> <p>⚠ <<print setup.getQuestFailureText(_failure)>></p> <</for>> </div> <</if>> <</if>> <<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]>> <<set _selQuestHidden to setup.isQuestHidden($selected_quest)>> <<if !_selQuestHidden>> <<if _selQuest && _selQuest.status === "preview">> <button class="btn-accept-quest disabled" disabled> <<print setup.L10n.btn_quest_locked || "Квест недоступен">> </button> <<elseif $activeQuests.includes($selected_quest)>> <<set _selQuestAcceptState to setup.getQuestAcceptState($selected_quest)>> <<if _selQuestAcceptState.allowed>> <button class="btn-accept-quest"> <<print setup.L10n.btn_accept_quest || "Принять квест">> </button> <<else>> <button class="btn-accept-quest disabled" disabled> <<print setup.L10n.btn_accept_quest || "Принять квест">> </button> <</if>> <</if>> <</if>> <<if _selQuest && !$completedQuests.includes($selected_quest) && _selQuest.data.type !== "forced">> <<if _selQuestHidden>> <button class="btn-restore-quest" data-action="restore-quest"> <<print setup.getLocalizedText('btn_restore_quest')>> </button> <<else>> <button class="btn-hide-quest" data-action="hide-quest"> <<print setup.getLocalizedText('btn_hide_quest')>> </button> <</if>> <</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"> <<set _questViewMode to setup.getQuestViewMode()>> <h3><<if _questViewMode === "completed">><<print setup.getLocalizedText('quest_list_completed')>><<elseif _questViewMode === "hidden">><<print setup.getLocalizedText('quest_list_hidden')>><<else>><<print setup.getLocalizedText('quest_list_available')>><</if>></h3> </div> <!-- Список квестов --> <div class="div-quest-list"> <<displayQuestList $current_location _questViewMode>> </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> <<set _selectedQuestDesc to ($completedQuests.includes($selected_quest) && _selectedQuest.description_completed) ? _selectedQuest.description_completed : _selectedQuest.description>> <p><<print setup.L10n[_selectedQuestDesc] || _selectedQuestDesc || "Описание квеста">></p> <<if !$completedQuests.includes($selected_quest)>> <<set _selectedQuestAcceptState to setup.getQuestAcceptState($selected_quest)>> <<if _selectedQuestAcceptState && !_selectedQuestAcceptState.allowed>> <div class="quest-accept-block-reasons"> <<for _failure range _selectedQuestAcceptState.failures>> <p>⚠ <<print setup.getQuestFailureText(_failure)>></p> <</for>> </div> <</if>> <</if>> <<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]>> <<set _selQuestHidden to setup.isQuestHidden($selected_quest)>> <<if !_selQuestHidden>> <<if _selQuest && _selQuest.status === "preview">> <button class="btn-accept-quest disabled" disabled> <<print setup.L10n.btn_quest_locked || "Квест недоступен">> </button> <<elseif $activeQuests.includes($selected_quest)>> <<set _selQuestAcceptState to setup.getQuestAcceptState($selected_quest)>> <<if _selQuestAcceptState.allowed>> <button class="btn-accept-quest"> <<print setup.L10n.btn_accept_quest || "Принять квест">> </button> <<else>> <button class="btn-accept-quest disabled" disabled> <<print setup.L10n.btn_accept_quest || "Принять квест">> </button> <</if>> <</if>> <</if>> <<if _selQuest && !$completedQuests.includes($selected_quest) && _selQuest.data.type !== "forced">> <<if _selQuestHidden>> <button class="btn-restore-quest" data-action="restore-quest"> <<print setup.getLocalizedText('btn_restore_quest')>> </button> <<else>> <button class="btn-hide-quest" data-action="hide-quest"> <<print setup.getLocalizedText('btn_hide_quest')>> </button> <</if>> <</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"> <<set _questViewMode to setup.getQuestViewMode()>> <h3><<if _questViewMode === "completed">><<print setup.getLocalizedText('quest_list_completed')>><<elseif _questViewMode === "hidden">><<print setup.getLocalizedText('quest_list_hidden')>><<else>><<print setup.getLocalizedText('quest_list_available')>><</if>></h3> </div> <div class="div-quest-list"> <<displayQuestList $current_character _questViewMode>> </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>>\