GG / GG.html
Mycc's picture
Update GG.html
41dc983 verified
Raw
History Blame Contribute Delete
101 kB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Random Genshin Impact</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
transition: background-color 0.3s, color 0.3s;
}
.dark {
background-color: #1a202c;
color: #e2e8f0;
}
.light {
background-color: #FDF8F0;
color: #4A4A4A;
}
.loader {
width: 48px;
height: 48px;
border: 5px solid #FDF8F0;
border-bottom-color: #A089D0;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.settings-panel {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.settings-panel.open {
max-height: 1000px;
}
.roulette-item-spin {
animation: spin-in-place 0.2s ease-in-out infinite alternate;
}
@keyframes spin-in-place {
0% { transform: scale(0.9) rotate(-2deg); opacity: 0.7; }
100% { transform: scale(1.1) rotate(2deg); opacity: 1; }
}
.roulette-item-boom-enter {
opacity: 0;
transform: scale(0.8);
}
.roulette-item-boom-enter-active {
animation: boom-effect 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards;
}
@keyframes boom-effect {
0% {
opacity: 0;
transform: scale(0.8);
filter: brightness(1.5) drop-shadow(0 0 5px rgba(220, 168, 82, 0.0));
}
50% {
opacity: 1;
transform: scale(1.1);
filter: brightness(1.8) drop-shadow(0 0 20px rgba(220, 168, 82, 0.8));
}
100% {
opacity: 1;
transform: scale(1);
filter: brightness(1) drop-shadow(0 0 10px rgba(220, 168, 82, 0.5));
}
}
.player-char-card {
position: relative;
cursor: pointer;
border: 2px solid transparent;
transition: border-color 0.2s;
}
.player-char-card.selected {
border-color: #DCA852;
}
.player-char-card .remove-btn {
position: absolute;
top: -5px;
right: -5px;
background-color: red;
color: white;
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
font-size: 0.8rem;
font-weight: bold;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s;
}
.player-char-card:hover .remove-btn {
opacity: 1;
}
.full-history-character-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 8px;
border-radius: 8px;
background-color: #ffffff;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
text-align: center;
flex-shrink: 0;
width: 100px;
}
.dark .full-history-character-card {
background-color: #4a5568;
}
.full-history-character-card img {
width: 64px;
height: 64px;
border-radius: 50%;
margin-bottom: 4px;
object-fit: cover;
border: 2px solid #DCA852;
}
.full-history-character-card p {
font-size: 0.875rem;
font-weight: 500;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
}
.dark .full-history-character-card p {
color: #e2e8f0;
}
.custom-scrollbar::-webkit-scrollbar {
height: 8px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #555;
}
.dark .custom-scrollbar::-webkit-scrollbar-track {
background: #2d3748;
}
.dark .custom-scrollbar::-webkit-scrollbar-thumb {
background: #a0aec0;
}
.dark .custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #cbd5e0;
}
.boss-card {
flex: 1;
min-width: 200px;
max-width: 250px;
text-align: center;
padding: 10px;
border-radius: 12px;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
border: 2px solid #5BC0DE;
cursor: pointer;
}
.dark .boss-card {
background: #4a5568;
}
.boss-card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}
.boss-card img {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
border: 3px solid #5BC0DE;
margin-bottom: 8px;
}
.boss-card h3 {
font-weight: bold;
margin-bottom: 4px;
}
.boss-card p {
font-size: 0.9rem;
color: #666;
}
.dark .boss-card p {
color: #cbd5e0;
}
.category-select {
background-color: #FDF8F0;
border: 2px solid #EFEAE4;
border-radius: 12px;
padding: 12px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.dark .category-select {
background-color: #2d3748;
border-color: #4a5568;
}
.category-select label {
display: block;
font-weight: 600;
margin-bottom: 8px;
color: #4A4A4A;
}
.dark .category-select label {
color: #e2e8f0;
}
.category-select select {
width: 100%;
padding: 8px 12px;
border-radius: 8px;
border: 1px solid #ddd;
background-color: white;
font-size: 1rem;
}
.dark .category-select select {
background-color: #4a5568;
color: #e2e8f0;
border-color: #718096;
}
.boss-section {
margin-bottom: 32px;
}
.history-item {
border-left: 4px solid #DCA852;
padding-left: 12px;
}
.boss-history-item {
border-left: 4px solid #A089D0;
padding-left: 12px;
}
.legend-section {
background: linear-gradient(135deg, #9B59B6 0%, #8E44AD 100%);
color: white;
}
.legend-section h2 {
color: white;
}
.legend-card {
border: 2px solid #9B59B6;
}
.legend-settings {
background-color: #FDF8F0;
border: 2px solid #EFEAE4;
border-radius: 12px;
padding: 12px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.dark .legend-settings {
background-color: #2d3748;
border-color: #4a5568;
}
.legend-settings label {
display: block;
font-weight: 600;
margin-bottom: 8px;
color: #4A4A4A;
}
.dark .legend-settings label {
color: #e2e8f0;
}
.boss-toggle {
margin-bottom: 8px;
padding: 8px;
border-radius: 8px;
transition: background-color 0.2s;
}
.boss-toggle:hover {
background-color: #f0f0f0;
}
.dark .boss-toggle:hover {
background-color: #3d4a5c;
}
.boss-toggle input {
margin-right: 8px;
}
</style>
</head>
<body class="light">
<div id="app-container" class="container mx-auto px-4 py-8 md:py-12 max-w-4xl">
<header class="text-center mb-10">
<div class="flex justify-between items-center mb-4">
<button id="theme-toggle" class="p-2 rounded-full bg-gray-200 dark:bg-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-800 dark:text-yellow-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</button>
<button id="settings-toggle" class="p-2 rounded-full bg-gray-200 dark:bg-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</button>
</div>
<h1 class="text-4xl md:text-5xl font-bold text-[#4A4A4A] dark:text-[#e2e8f0] mb-2">Рулетка Genshin Impact</h1>
<p class="text-lg text-[#6B6B6B] dark:text-[#a0aec0] max-w-2xl mx-auto">
Случайный выбор персонажей, боссов и местных легенд для вашей игры в Genshin Impact
</p>
</header>
<main>
<div id="settings-panel" class="settings-panel bg-[#EFEAE4] dark:bg-gray-700 p-6 rounded-2xl shadow-lg mb-6">
<h3 class="text-xl font-bold mb-4 dark:text-white">Настройки рулетки</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div>
<label for="team-size" class="block text-sm font-medium mb-2 dark:text-gray-300">Количество персонажей в отряде</label>
<select id="team-size" class="w-full p-2 rounded border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white">
<option value="4">4</option>
<option value="3">3</option>
<option value="2">2</option>
<option value="1">1</option>
</select>
</div>
<div>
<label for="animation-speed" class="block text-sm font-medium mb-2 dark:text-gray-300">Скорость анимации</label>
<select id="animation-speed" class="w-full p-2 rounded border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white">
<option value="fast">Быстрая</option>
<option value="medium" selected>Средняя</option>
<option value="slow">Медленная</option>
</select>
</div>
</div>
<h3 class="text-xl font-bold mb-4 dark:text-white">Мои персонажи</h3>
<p class="text-sm text-[#6B6B6B] dark:text-[#a0aec0] mb-4">
Выберите персонажей, которыми вы владеете, чтобы рулетка использовала только их. Если ни один не выбран, будут использоваться все персонажи.
</p>
<div class="mb-4">
<input type="text" id="character-search" placeholder="Поиск персонажей..." class="w-full p-2 rounded border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white">
</div>
<div id="player-characters-selection" class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-3 mb-6 max-h-60 overflow-y-auto pr-2">
</div>
<div class="text-center">
<button id="clear-player-characters" class="bg-red-500 text-white font-bold py-2 px-4 rounded-lg text-sm shadow-md hover:bg-red-600 transition-transform duration-200 ease-in-out hover:scale-105 focus:outline-none focus:ring-4 focus:ring-red-500/50 hidden">
Очистить выбранных персонажей
</button>
</div>
<h3 class="text-xl font-bold mb-4 mt-6 dark:text-white">Настройки боссов</h3>
<p class="text-sm text-[#6B6B6B] dark:text-[#a0aec0] mb-4">
Выберите, какие боссы и местные легенды должны участвовать в рандомизации
</p>
<div class="mb-4">
<div class="legend-settings">
<label for="boss-category-select">Категория:</label>
<select id="boss-category-select" class="w-full p-2 rounded-lg border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white">
<option value="world-bosses">Мировые боссы</option>
<option value="weekly-bosses">Еженедельные боссы</option>
<option value="local-legends">Местные легенды</option>
</select>
</div>
</div>
<div id="bosses-settings-container" class="max-h-80 overflow-y-auto pr-2">
<!-- Динамически генерируемые настройки для боссов -->
</div>
</div>
<div class="category-select">
<label for="category-select">Выберите категорию для рандомизации:</label>
<select id="category-select" class="w-full p-2 rounded-lg border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white">
<option value="characters">Отряд персонажей</option>
<option value="world-bosses">Мировые боссы</option>
<option value="weekly-bosses">Еженедельные боссы</option>
<option value="local-legends">Местные легенды</option>
</select>
</div>
<!-- Секция для персонажей -->
<section id="characters-section" class="mb-16 bg-[#EFEAE4] dark:bg-gray-700 p-6 rounded-2xl shadow-lg">
<h2 class="text-3xl font-bold text-center text-[#4A4A4A] dark:text-white mb-8">Сформировать отряд персонажей</h2>
<div class="flex justify-center mb-8">
<button id="spin-characters-button" class="bg-[#DCA852] text-white font-bold py-4 px-10 rounded-lg text-xl shadow-md hover:bg-[#c3914a] transition-transform duration-200 ease-in-out hover:scale-105 focus:outline-none focus:ring-4 focus:ring-[#DCA852]/50">
Крутить отряд!
</button>
</div>
<div id="characters-result" class="min-h-[180px] bg-[#FDF8F0] dark:bg-gray-800 rounded-xl p-4 flex flex-wrap justify-center items-center gap-4 transition-all duration-300 flex-col sm:flex-row">
<p id="characters-placeholder" class="text-[#888] dark:text-gray-400">Ваш случайный отряд появится здесь.</p>
<div id="characters-loader" class="loader hidden"></div>
</div>
<div id="save-characters-section" class="mt-4 flex flex-col sm:flex-row justify-center items-center gap-4 hidden">
<input type="text" id="team-name-input" placeholder="Название отряда (необязательно)" class="p-2 rounded border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white w-full sm:w-auto flex-grow">
<button id="save-team-button" class="bg-[#A089D0] text-white font-bold py-2 px-6 rounded-lg shadow-md hover:bg-[#8e7abf] transition-transform duration-200 ease-in-out hover:scale-105 focus:outline-none focus:ring-4 focus:ring-[#A089D0]/50 w-full sm:w-auto">
Сохранить отряд
</button>
</div>
</section>
<!-- Секция для мировых боссов -->
<section id="world-bosses-section" class="mb-16 bg-[#EFEAE4] dark:bg-gray-700 p-6 rounded-2xl shadow-lg hidden">
<h2 class="text-3xl font-bold text-center text-[#4A4A4A] dark:text-white mb-8">Рандом мирового босса</h2>
<div class="flex justify-center mb-8">
<button id="spin-world-boss-button" class="bg-[#5BC0DE] text-white font-bold py-4 px-10 rounded-lg text-xl shadow-md hover:bg-[#3bb5d9] transition-transform duration-200 ease-in-out hover:scale-105 focus:outline-none focus:ring-4 focus:ring-[#5BC0DE]/50">
Выбрать босса!
</button>
</div>
<div id="world-boss-result" class="min-h-[180px] bg-[#FDF8F0] dark:bg-gray-800 rounded-xl p-4 flex flex-wrap justify-center items-center gap-4 transition-all duration-300 flex-col sm:flex-row">
<p id="world-boss-placeholder" class="text-[#888] dark:text-gray-400">Случайный мировой босс появится здесь.</p>
<div id="world-boss-loader" class="loader hidden"></div>
</div>
<div id="save-world-boss-section" class="mt-4 flex justify-center hidden">
<button id="save-world-boss-button" class="bg-[#A089D0] text-white font-bold py-2 px-6 rounded-lg shadow-md hover:bg-[#8e7abf] transition-transform duration-200 ease-in-out hover:scale-105 focus:outline-none focus:ring-4 focus:ring-[#A089D0]/50">
Сохранить в историю
</button>
</div>
</section>
<!-- Секция для еженедельных боссов -->
<section id="weekly-bosses-section" class="mb-16 bg-[#EFEAE4] dark:bg-gray-700 p-6 rounded-2xl shadow-lg hidden">
<h2 class="text-3xl font-bold text-center text-[#4A4A4A] dark:text-white mb-8">Рандом еженедельного босса</h2>
<div class="flex justify-center mb-8">
<button id="spin-weekly-boss-button" class="bg-[#FF7B7B] text-white font-bold py-4 px-10 rounded-lg text-xl shadow-md hover:bg-[#ff5f5f] transition-transform duration-200 ease-in-out hover:scale-105 focus:outline-none focus:ring-4 focus:ring-[#FF7B7B]/50">
Выбрать босса!
</button>
</div>
<div id="weekly-boss-result" class="min-h-[180px] bg-[#FDF8F0] dark:bg-gray-800 rounded-xl p-4 flex flex-wrap justify-center items-center gap-4 transition-all duration-300 flex-col sm:flex-row">
<p id="weekly-boss-placeholder" class="text-[#888] dark:text-gray-400">Случайный еженедельный босс появится здесь.</p>
<div id="weekly-boss-loader" class="loader hidden"></div>
</div>
<div id="save-weekly-boss-section" class="mt-4 flex justify-center hidden">
<button id="save-weekly-boss-button" class="bg-[#A089D0] text-white font-bold py-2 px-6 rounded-lg shadow-md hover:bg-[#8e7abf] transition-transform duration-200 ease-in-out hover:scale-105 focus:outline-none focus:ring-4 focus:ring-[#A089D0]/50">
Сохранить в историю
</button>
</div>
</section>
<!-- Секция для местных легенд -->
<section id="local-legends-section" class="mb-16 bg-[#EFEAE4] dark:bg-gray-700 p-6 rounded-2xl shadow-lg hidden">
<h2 class="text-3xl font-bold text-center text-[#4A4A4A] dark:text-white mb-8">Рандом местной легенды</h2>
<div class="flex justify-center mb-8">
<button id="spin-legend-button" class="bg-[#9B59B6] text-white font-bold py-4 px-10 rounded-lg text-xl shadow-md hover:bg-[#8e44ad] transition-transform duration-200 ease-in-out hover:scale-105 focus:outline-none focus:ring-4 focus:ring-[#9B59B6]/50">
Выбрать легенду!
</button>
</div>
<div id="legend-result" class="min-h-[180px] bg-[#FDF8F0] dark:bg-gray-800 rounded-xl p-4 flex flex-wrap justify-center items-center gap-4 transition-all duration-300 flex-col sm:flex-row">
<p id="legend-placeholder" class="text-[#888] dark:text-gray-400">Случайная местная легенда появится здесь.</p>
<div id="legend-loader" class="loader hidden"></div>
</div>
<div id="save-legend-section" class="mt-4 flex justify-center hidden">
<button id="save-legend-button" class="bg-[#A089D0] text-white font-bold py-2 px-6 rounded-lg shadow-md hover:bg-[#8e7abf] transition-transform duration-200 ease-in-out hover:scale-105 focus:outline-none focus:ring-4 focus:ring-[#A089D0]/50">
Сохранить в историю
</button>
</div>
</section>
<section id="history-section" class="bg-[#EFEAE4] dark:bg-gray-700 p-6 rounded-2xl shadow-lg">
<div class="flex justify-between items-center mb-8">
<h2 class="text-3xl font-bold text-[#4A4A4A] dark:text-white">История</h2>
<div class="flex gap-2">
<button id="show-full-history-button" class="bg-[#A089D0] text-white font-bold py-2 px-4 rounded-lg text-sm shadow-md hover:bg-[#8e7abf] transition-transform duration-200 ease-in-out hover:scale-105 focus:outline-none focus:ring-4 focus:ring-[#A089D0]/50 hidden">
Вся история
</button>
<button id="clear-history-button" class="bg-[#A089D0] text-white font-bold py-2 px-4 rounded-lg text-sm shadow-md hover:bg-[#8e7abf] transition-transform duration-200 ease-in-out hover:scale-105 focus:outline-none focus:ring-4 focus:ring-[#A089D0]/50 hidden">
Очистить
</button>
</div>
</div>
<div id="history-content" class="flex flex-col gap-6">
<p id="history-placeholder" class="text-center text-[#888] dark:text-gray-400">История ваших случайных выборов будет отображаться здесь.</p>
</div>
</section>
</main>
<footer class="text-center mt-16 pt-8 border-t border-[#EFEAE4] dark:border-gray-600">
<p class="text-sm text-[#888] dark:text-gray-400">
Это фанатский проект. Не аффилирован и не одобрен HoYoverse.
</p>
</footer>
</div>
<div id="confirm-modal" class="fixed inset-0 bg-black bg-opacity-70 z-50 flex justify-center items-center hidden p-4">
<div class="bg-[#FDF8F0] dark:bg-gray-800 rounded-2xl w-full max-w-sm shadow-2xl relative text-center p-6 transform transition-all duration-300 scale-95 opacity-0" id="confirm-modal-content">
<h3 class="text-2xl font-bold mb-4 text-[#4A4A4A] dark:text-white">Подтверждение</h3>
<p id="confirm-modal-message" class="text-lg text-[#6B6B6B] dark:text-gray-300 mb-6">Вы уверены, что хотите очистить историю?</p>
<div class="flex justify-around gap-4">
<button id="confirm-modal-cancel" class="bg-[#A089D0] text-white font-bold py-2 px-6 rounded-lg hover:bg-[#8e7abf]">Отмена</button>
<button id="confirm-modal-ok" class="bg-red-500 text-white font-bold py-2 px-6 rounded-lg hover:bg-red-600">Очистить</button>
</div>
</div>
</div>
<div id="full-history-modal" class="fixed inset-0 bg-black bg-opacity-70 z-50 flex justify-center items-center hidden p-4">
<div class="bg-[#FDF8F0] dark:bg-gray-800 rounded-2xl w-full max-w-4xl h-[90vh] shadow-2xl relative flex flex-col p-6 transform transition-all duration-300 scale-95 opacity-0" id="full-history-modal-content">
<h2 class="text-3xl md:text-4xl font-bold text-center mb-6 text-[#4A4A4A] dark:text-[#e2e8f0]">Вся История</h2>
<div id="full-history-modal-content-inner" class="flex flex-col gap-6 overflow-y-auto flex-grow pr-2">
</div>
<div class="text-center mt-6">
<button id="full-history-modal-close" class="bg-[#A089D0] text-white font-bold py-2 px-6 rounded-lg hover:bg-[#8e7abf]">Закрыть</button>
</div>
</div>
</div>
<audio id="boom-sound" src="boom.mp3" preload="auto" volume="1.0"></audio>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Локальные данные персонажей
const LOCAL_CHARACTERS = [
{ "name": "Альбедо", "icon": "pic/albedo.png", "element": "Geo" },
{ "name": "Аль-Хайтам", "icon": "pic/alhaitham.png", "element": "Dendro" },
{ "name": "Алой", "icon": "pic/aloy.png", "element": "Cryo" },
{ "name": "Эмбер", "icon": "pic/amber.png", "element": "Pyro" },
{ "name": "Аратаки Итто", "icon": "pic/arataki_itto.png", "element": "Geo" },
{ "name": "Арлекино", "icon": "pic/arlecchino.png", "element": "Pyro" },
{ "name": "Бай Чжу", "icon": "pic/baizhu.png", "element": "Dendro" },
{ "name": "Барбара", "icon": "pic/barbara.png", "element": "Hydro" },
{ "name": "Бэй Доу", "icon": "pic/beidou.png", "element": "Electro" },
{ "name": "Беннет", "icon": "pic/bennett.png", "element": "Pyro" },
{ "name": "Кандакия", "icon": "pic/candace.png", "element": "Hydro" },
{ "name": "Шарлотта", "icon": "pic/charlotte.png", "element": "Cryo" },
{ "name": "Часка", "icon": "pic/chasca.png", "element": "Anemo" },
{ "name": "Тиори", "icon": "pic/chiori.png", "element": "Geo" },
{ "name": "Чун Юнь", "icon": "pic/chongyun.png", "element": "Cryo" },
{ "name": "Ситлали", "icon": "pic/citlali.png", "element": "Cryo" },
{ "name": "Клоринда", "icon": "pic/clorinde.png", "element": "Electro" },
{ "name": "Коллеи", "icon": "pic/collei.png", "element": "Dendro" },
{ "name": "Сайно", "icon": "pic/cyno.png", "element": "Electro" },
{ "name": "Дэхья", "icon": "pic/dehya.png", "element": "Pyro" },
{ "name": "Дилюк", "icon": "pic/diluc.png", "element": "Pyro" },
{ "name": "Диона", "icon": "pic/diona.png", "element": "Cryo" },
{ "name": "Дори", "icon": "pic/dori.png", "element": "Electro" },
{ "name": "Эмилия", "icon": "pic/emilie.png" , "element": "Dendro" },
{ "name": "Эскофье", "icon": "pic/escoffier.png", "element": "Cryo" },
{ "name": "Эола", "icon": "pic/eula.png", "element": "Cryo" },
{ "name": "Фарузан", "icon": "pic/faruzan.png", "element": "Anemo" },
{ "name": "Фишль", "icon": "pic/fischl.png", "element": "Electro" },
{ "name": "Фремине", "icon": "pic/freminet.png", "element": "Cryo" },
{ "name": "Фурина", "icon": "pic/furina.png", "element": "Hydro" },
{ "name": "Га Мин", "icon": "pic/gaming.png", "element": "Pyro" },
{ "name": "Гань Юй", "icon": "pic/ganyu.png", "element": "Cryo" },
{ "name": "Горо", "icon": "pic/gorou.png", "element": "Geo" },
{ "name": "Ху Тао", "icon": "pic/hu_tao.png", "element": "Pyro" },
{ "name": "Ифу", "icon": "pic/ifa.png", "element": "Anemo" },
{ "name": "Иансан", "icon": "pic/iansan.png", "element": "Electro" },
{ "name": "Джинн", "icon": "pic/jean.png", "element": "Anemo" },
{ "name": "Кадзуха", "icon": "pic/kaedehara_kazuha.png", "element": "Anemo" },
{ "name": "Кэйа", "icon": "pic/kaeya.png", "element": "Cryo" },
{ "name": "Качина", "icon": "pic/Kachina.webp", "element": "Geo" },
{ "name": "Аяка", "icon": "pic/kamisato_ayaka.png", "element": "Cryo" },
{ "name": "Аято", "icon": "pic/kamisato_ayato.png", "element": "Hydro" },
{ "name": "Кавэх", "icon": "pic/kaveh.png", "element": "Dendro" },
{ "name": "Кэ Цин", "icon": "pic/keqing.png", "element": "Electro" },
{ "name": "Кинич", "icon": "pic/kinich.png", "element": "Dendro" },
{ "name": "Кирара", "icon": "pic/kirara.png", "element": "Dendro" },
{ "name": "Кли", "icon": "pic/klee.png", "element": "Pyro" },
{ "name": "Сара", "icon": "pic/kujou_sara.png", "element": "Electro" },
{ "name": "Шинобу", "icon": "pic/kuki_shinobu.png", "element": "Electro" },
{ "name": "Лань Янь", "icon": "pic/lan_yan.png", "element": "Anemo" },
{ "name": "Лейла", "icon": "pic/layla.png", "element": "Cryo" },
{ "name": "Лиза", "icon": "pic/lisa.png", "element": "Electro" },
{ "name": "Линнет", "icon": "pic/lynette.png", "element": "Anemo" },
{ "name": "Лини", "icon": "pic/lyney.png", "element": "Pyro" },
{ "name": "Мавуика", "icon": "pic/mavuika.png", "element": "Pyro" },
{ "name": "Мика", "icon": "pic/mika.png", "element": "Cryo" },
{ "name": "Мона", "icon": "pic/mona.png", "element": "Hydro" },
{ "name": "Муалани", "icon": "pic/mualani.png", "element": "Hydro" },
{ "name": "Нахида", "icon": "pic/nahida.png", "element": "Dendro" },
{ "name": "Навия", "icon": "pic/navia.png", "element": "Geo" },
{ "name": "Нёвиллет", "icon": "pic/neuvillette.png", "element": "Hydro" },
{ "name": "Нилу", "icon": "pic/nilou.png", "element": "Hydro" },
{ "name": "Нин Гуан", "icon": "pic/ningguang.png", "element": "Geo" },
{ "name": "Ноэлль", "icon": "pic/noelle.png", "element": "Geo" },
{ "name": "Оророн", "icon": "pic/ororon.png", "element": "Electro" },
{ "name": "Ци Ци", "icon": "pic/qiqi.png", "element": "Cryo" },
{ "name": "Райдэн", "icon": "pic/raiden_shogun.png", "element": "Electro" },
{ "name": "Рэйзор", "icon": "pic/razor.png", "element": "Electro" },
{ "name": "Розалья", "icon": "pic/rosaria.png", "element": "Cryo" },
{ "name": "Кокоми", "icon": "pic/sangonomiya_kokomi.png", "element": "Hydro" },
{ "name": "Саю", "icon": "pic/sayu.png", "element": "Anemo" },
{ "name": "Сетос", "icon": "pic/sethos.png", "element": "Electro" },
{ "name": "Шэнь Хэ", "icon": "pic/shenhe.png", "element": "Cryo" },
{ "name": "Хэйдзо", "icon": "pic/shikanoin_heizou.png", "element": "Anemo" },
{ "name": "Сиджвин", "icon": "pic/sigewinne.png", "element": "Hydro" },
{ "name": "Сахароза", "icon": "pic/sucrose.png", "element": "Anemo" },
{ "name": "Тарталья", "icon": "pic/tartaglia.png", "element": "Hydro" },
{ "name": "Тома", "icon": "pic/thoma.png", "element": "Pyro" },
{ "name": "Тигнари", "icon": "pic/tighnari.png", "element": "Dendro" },
{ "name": "Вареса", "icon": "pic/varesa.png", "element": "Electro" },
{ "name": "Венти", "icon": "pic/venti.png", "element": "Anemo" },
{ "name": "Странник", "icon": "pic/wanderer.png", "element": "Anemo" },
{ "name": "Ризли", "icon": "pic/wriothesley.png", "element": "Cryo" },
{ "name": "Сяо", "icon": "pic/xiao.png", "element": "Anemo" },
{ "name": "Сян Линь", "icon": "pic/xiangling.png", "element": "Pyro" },
{ "name": "Сянь Юнь", "icon": "pic/xianyun.png", "element": "Anemo" },
{ "name": "Шилонен", "icon": "pic/xilonen.png", "element": "Geo" },
{ "name": "Син Цю", "icon": "pic/xingqiu.png", "element": "Hydro" },
{ "name": "Синь Янь", "icon": "pic/xinyan.png", "element": "Pyro" },
{ "name": "Яэ Мико", "icon": "pic/yae_miko.png", "element": "Electro" },
{ "name": "Янь Фэй", "icon": "pic/yanfei.png", "element": "Pyro" },
{ "name": "Яо Яо", "icon": "pic/yaoyao.png", "element": "Dendro" },
{ "name": "Е Лань", "icon": "pic/yelan.png", "element": "Hydro" },
{ "name": "Ёимия", "icon": "pic/yoimiya.png", "element": "Pyro" },
{ "name": "Юнь Цзинь", "icon": "pic/yun_jin.png", "element": "Geo" },
{ "name": "Паймон", "icon": "pic/paimon.png", "element": "Anemo" },
{ "name": "ГГ(Анемо)", "icon": "pic/traveler.webp", "element": "Anemo" },
{ "name": "ГГ (Гео)", "icon": "pic/traveler.webp", "element": "Geo" },
{ "name": "ГГ (Электро)", "icon": "pic/traveler.webp", "element": "Electro" },
{ "name": "ГГ (Дендро)", "icon": "pic/traveler.webp", "element": "Dendro" },
{ "name": "ГГ (Гидро)", "icon": "pic/traveler.webp", "element": "Hydro" },
{ "name": "Мидзуки", "icon": "pic/yumemizuki_mizuki.png", "element": "Anemo" },
{ "name": "Чжун Ли", "icon": "pic/zhongli.png", "element": "Geo" },
{ "name": "Скирк", "icon": "pic/skirk.png", "element": "Cryo" },
{ "name": "Далия", "icon": "pic/daliya.png", "element": "Hydro" },
{ "name": "Инеффа", "icon": "pic/ineffa.png", "element": "Electro" },
{ "name": "Лаума", "icon": "pic/lauma_icon.webp", "element": "Dendro" },
{ "name": "Айно", "icon": "pic/aino_icon.webp", "element": "Hydro" },
{ "name": "Флинс", "icon": "pic/flins_icon.webp", "element": "Electro" },
{ "name": "Нефер", "icon": "pic/Nefer.webp", "element": "Dendro" },
{ "name": "Ягода", "icon": "pic/YAgoda.webp", "element": "Anemo" },
{ "name": "Дурин", "icon": "pic/Durin.webp", "element": "Pyro" },
{ "name": "Иллуги", "icon": "pic/Illuga.png", "element": "Geo" },
{ "name": "Цзы Бай", "icon": "pic/zibai.png", "element": "Geo" },
{ "name": "Коломбина", "icon": "pic/Columbina.png", "element": "Hydro" },
];
// Мировые боссы
const WORLD_BOSSES = [
{ "name": "Сверхтяжёлый сухопутный крейсер: Мобильная крепость", "icon": "pic/WorldBoss/Sverkhtyazhyelyy-sukhoputnyy-kreyser.webp" },
{ "name": "Повелитель морозной ночи", "icon": "pic/WorldBoss/Povelitel-moroznoy-nochi.webp" },
{ "name": "Лучезарный мотылёк-призрак", "icon": "pic/WorldBoss/Luchezarnyy-motylyek_prizrak.webp" },
{ "name": "Железный утёнок", "icon": "pic/WorldBoss/ZHeleznyy-utyenok.webp" },
{ "name": "Автоматон: Надсмотрщик", "icon": "pic/WorldBoss/Secret-Source-Automaton-Overseer-Device.webp" },
{ "name": "Статуя лавового дракона", "icon": "pic/WorldBoss/Statuya-lavovogo-drakona.webp" },
{ "name": "Говорящая с духами отшельница", "icon": "pic/WorldBoss/Govoryashchaya-s-dukhami-otshelnitsa.webp" },
{ "name": "Сумрачная папилла", "icon": "pic/WorldBoss/Sumrachnaya-papilla.webp" },
{ "name": "Автоматон: Конфигуратор", "icon": "pic/WorldBoss/Avtomaton-tainstvennogo-istochnika.webp" },
{ "name": "Прожорливый Горный король-юмказавр", "icon": "pic/WorldBoss/Prozhorlivyy-gornyy-korol-yumkazavr.webp" },
{ "name": "Кукузавр-тиран золотого пламени", "icon": "pic/WorldBoss/Prozhorlivyi_-Gornyi_-korol_yumkazavr.webp" },
{ "name": "Легат големов", "icon": "pic/WorldBoss/Kapitan-golemov.webp" },
{ "name": "Суаньни-отшельник", "icon": "pic/WorldBoss/Suanni_otshelnik.webp" },
{ "name": "Гидротульпа", "icon": "pic/WorldBoss/Probuzhdyennaya-zhizn.webp" },
{ "name": "Экспериментальный генератор поля", "icon": "pic/WorldBoss/Eksperimentalnyy-generator-polya.webp" },
{ "name": "Морской конёк тысячелетней жемчужины", "icon": "pic/WorldBoss/Morskoy-konyek-tysyacheletney-zhemchuzhiny.webp" },
{ "name": "Император огня и железа", "icon": "pic/WorldBoss/Imperator-ognya-i-zheleza.webp" },
{ "name": "Сюита ледяного ветра", "icon": "pic/WorldBoss/Syuita-ledyanogo-vetra.webp" },
{ "name": "Чудовищный креститель", "icon": "pic/WorldBoss/CHudovishchnyy-krestitel.webp" },
{ "name": "Унут Сетеха", "icon": "pic/WorldBoss/Unut-Setekha.webp" },
{ "name": "Дендро Гипостазис", "icon": "pic/WorldBoss/Dendro-Gipostazis.png" },
{ "name": "Алгоритм полуизвечной матрицы оператора наблюдения", "icon": "pic/WorldBoss/Poluvechnaya-matritsa-upravleniya.webp" },
{ "name": "Дракон вечных бедствий", "icon": "pic/WorldBoss/Drakon-vechnykh-bedstviy.webp" },
{ "name": "Электро папоротник", "icon": "pic/WorldBoss/Elektro-paporotnik.webp" },
{ "name": "Пернатый плесенник", "icon": "pic/WorldBoss/Pernatyy-plesennik.webp" },
{ "name": "Змей руин", "icon": "pic/WorldBoss/Zmey-ruin.webp" },
{ "name": "Стая вишапов глубин", "icon": "pic/WorldBoss/Staya-vishapov-glubin.webp" },
{ "name": "Золотой волчий вожак", "icon": "pic/WorldBoss/Golden-Wolflord.png" },
{ "name": "Гидро Гипостазис", "icon": "pic/WorldBoss/Gidro-Gipostazis.png" },
{ "name": "Манифестация грома", "icon": "pic/WorldBoss/Elektro-Okeanid.png" },
{ "name": "Бесконечный механический массив", "icon": "pic/WorldBoss/Beskonechnyy-mekhanicheskiy-massiv.png" },
{ "name": "Пиро гипостазис", "icon": "pic/WorldBoss/Piro-gipostazis.png" },
{ "name": "Магу Кенки", "icon": "pic/WorldBoss/Magu-Kenki.webp" },
{ "name": "Крио гипостазис", "icon": "pic/WorldBoss/Cryo_Hypostasis.webp" },
{ "name": "Древний геовишап", "icon": "pic/WorldBoss/Drevniy-geovishap.webp" },
{ "name": "Гео гипостазис", "icon": "pic/WorldBoss/UI_MonsterIcon_Effigy_Rock.png" },
{ "name": "Электро гипостазис", "icon": "pic/WorldBoss/UI_MonsterIcon_Effigy_Electric.png" },
{ "name": "Анемо гипостазис", "icon": "pic/WorldBoss/UI_MonsterIcon_Effigy_Wind.png" },
{ "name": "Крио папоротник", "icon": "pic/WorldBoss/UI_MonsterIcon_cryo_regisvine.png" },
{ "name": "Пиро орхидея", "icon": "pic/WorldBoss/UI_MonsterIcon_Regisvine_Fire.png" },
{ "name": "Океанид", "icon": "pic/WorldBoss/Okeanid.webp" }
];
// Еженедельные боссы
const WEEKLY_BOSSES = [
{ "name": "Игра перед вратами", "icon": "pic/Allweeklybosses/Igra-pered-vratami.webp" },
{ "name": "Владыка истлевшего первобытного пламени", "icon": "pic/Allweeklybosses/Lord-of-Eroded-Primal-Fire.webp" },
{ "name": "Слуга", "icon": "pic/Allweeklybosses/Nomer-chetyre-iz-Predvestnikov-Fatui_-Arlekino.webp" },
{ "name": "Всепожирающий нарвал", "icon": "pic/Allweeklybosses/Vsepozhirayushchiy-narval.webp" },
{ "name": "Страж оазиса Апепа", "icon": "pic/Allweeklybosses/Strazh-oazisa-Apepa.webp" },
{ "name": "Сёки но Ками", "icon": "pic/Allweeklybosses/Syeki-no-Kami.webp" },
{ "name": "Магацу митакэ Наруками но микото", "icon": "pic/Allweeklybosses/m_29060201.webp" },
{ "name": "Синьора", "icon": "pic/Allweeklybosses/Sinora.png" },
{ "name": "Аждаха", "icon": "pic/Allweeklybosses/Azhdaha.png" },
{ "name": "Чайльд", "icon": "pic/Allweeklybosses/Childe.png" },
{ "name": "Лупус Бореалис, Волчий лорд", "icon": "pic/Allweeklybosses/UI_MonsterIcon_LupiBoreas.png" },
{ "name": "Ужас бури", "icon": "pic/Allweeklybosses/Stormterror.png" }
];
// Местные Легенды
const LOCAL_LEGENDS = [
{ "name": "Сигурд", "icon": "pic/LocalLegends/Sigurd.png", "guideUrl": "#" },
{ "name": "Волшебные рыцари-близнецы", "icon": "https://wotpack.ru/wp-content/uploads/2023/08/word-image-284766-1-800x476.jpg", "guideUrl": "https://wotpack.ru/volshebnye-rycari-bliznecy-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Окружной судья океана", "icon": "https://wotpack.ru/wp-content/uploads/2023/08/word-image-284964-4.jpg", "guideUrl": "https://wotpack.ru/okruzhnoj-sudja-okeana-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Дева озера Вивианна", "icon": "https://wotpack.ru/wp-content/uploads/2023/08/word-image-283768-1-800x425.jpg", "guideUrl": "https://wotpack.ru/deva-ozera-vivianna-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Дева озера Нинианна", "icon": "https://wotpack.ru/wp-content/uploads/2023/08/word-image-283708-1-800x423.jpg", "guideUrl": "https://wotpack.ru/deva-ozera-ninianna-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Угасающий старый солдат", "icon": "https://wotpack.ru/wp-content/uploads/2023/08/word-image-283590-1-800x450.jpeg", "guideUrl": "https://wotpack.ru/ugasajushhij-staryj-soldat-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Владыка отшельников Доварху", "icon": "https://wotpack.ru/wp-content/uploads/2023/08/word-image-283520-1-800x450.jpeg", "guideUrl": "https://wotpack.ru/vladyka-otshelnikov-dovarhu-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Меч ущелья", "icon": "https://wotpack.ru/wp-content/uploads/2023/08/word-image-283554-1-800x450.jpeg", "guideUrl": "https://wotpack.ru/mech-ushhelja-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Железный виконт", "icon": "https://wotpack.ru/wp-content/uploads/2023/08/word-image-284964-10.jpg", "guideUrl": "https://wotpack.ru/zheleznyj-vikont-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Автоматический генератор полей сверхвычислений", "icon": "https://wotpack.ru/wp-content/uploads/2023/09/word-image-301036-4.jpeg", "guideUrl": "https://wotpack.ru/avtomaticheskij-generator-polej-sverhvychislenij-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Лиахра Блистательная", "icon": "https://wotpack.ru/wp-content/uploads/2023/09/word-image-301327-4.jpeg", "guideUrl": "https://wotpack.ru/liahra-blistatelnaja-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Изольт", "icon": "https://wotpack.ru/wp-content/uploads/2023/09/word-image-301389-1-1-800x450.jpeg", "guideUrl": "https://wotpack.ru/izolt-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Шассанион", "icon": "https://wotpack.ru/wp-content/uploads/2023/11/word-image-321383-7-800x450.jpeg", "guideUrl": "https://wotpack.ru/shassanion-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Деянейра Снежневна", "icon": "https://wotpack.ru/wp-content/uploads/2023/11/1-10-800x450.jpg", "guideUrl": "https://wotpack.ru/dejanejra-snezhnevna-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Роки Авилдсен", "icon": "https://wotpack.ru/wp-content/uploads/2023/11/word-image-321618-1-800x450.jpeg", "guideUrl": "https://wotpack.ru/roki-avildsen-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Магический меч Круж", "icon": "https://wotpack.ru/wp-content/uploads/2023/11/word-image-322447-1-800x450.jpeg", "guideUrl": "https://wotpack.ru/magicheskij-mech-kruzh-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Лиям", "icon": "https://wotpack.ru/wp-content/uploads/2023/11/word-image-321469-1-800x450.jpeg", "guideUrl": "https://wotpack.ru/lijam-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Киней", "icon": "https://wotpack.ru/wp-content/uploads/2024/04/Kinej-v-Genshin-Impact-800x450.jpg", "guideUrl": "https://wotpack.ru/kinej-v-genshin-impact-kak-najti-i-pobedit/" },
{ "name": "Три полихромные звезды", "icon": "https://wotpack.ru/wp-content/uploads/2024/08/mestnaya-legenda-tri-polihromnye-zvezdy-3-800x449.jpg", "guideUrl": "https://wotpack.ru/tri-polihromnye-zvezdy-v-genshin-impact-kak-nayti-i-pobedit-vasiliya-sidorenko-i-nomonkonova/" },
{ "name": "Косихо", "icon": "https://wotpack.ru/wp-content/uploads/2024/08/kosiho-v-genshin-impact-kak-nayti-i-pobedit-800x450.jpg", "guideUrl": "https://wotpack.ru/kosiho-v-genshin-impact-kak-nayti-i-pobedit-cherepahu/" },
{ "name": "Сапфо среди волн", "icon": "https://wotpack.ru/wp-content/uploads/2024/08/sapfo-sredi-voln-v-genshin-impact-kak-nayti-i-pobedit-800x450.jpg", "guideUrl": "https://wotpack.ru/sapfo-sredi-voln-v-genshin-impact-kak-nayti-i-pobedit/" },
{ "name": "Балачко", "icon": "https://wotpack.ru/wp-content/uploads/2024/08/balachko-v-genshin-impact-kak-nayti-i-pobedit-397x206.jpg", "guideUrl": "https://wotpack.ru/balachko-v-genshin-impact-kak-nayti-i-pobedit/" },
{ "name": "Рилай", "icon": "https://wotpack.ru/wp-content/uploads/2024/11/rilay-v-genshin-impact-800x450.jpg", "guideUrl": "https://wotpack.ru/rilay-v-genshin-impact-kak-nayti-i-pobedit/" },
{ "name": "Короля кабанов", "icon": "https://wotpack.ru/wp-content/uploads/2021/11/%D0%BA%D0%BE%D1%80%D0%BE%D0%BB%D1%8C-%D1%81%D0%BD%D0%B5%D0%B6%D0%BD%D1%8B%D1%85-%D0%BA%D0%B0%D0%B1%D0%B0%D0%BD%D0%BE%D0%B2-348x220.jpg", "guideUrl": "https://wotpack.ru/genshin-impact-ah-svezhee-mjaso-prohozhdenie-kvesta-gajd/" },
{ "name": "Бронзовый замок", "icon": "https://wotpack.ru/wp-content/uploads/2024/11/bronzovyy-2.jpg", "guideUrl": "https://wotpack.ru/bronzovyy-zamok-v-genshin-impact-kak-nayti-i-pobedit/" },
{ "name": "Ничтожный", "icon": "https://wotpack.ru/wp-content/uploads/2025/03/nichtozhnyy-v-mire-vid.jpg", "guideUrl": "https://wotpack.ru/nichtozhnyy-v-genshin-impact-kak-nayti-i-pobedit/" },
{ "name": "Пик", "icon": "https://wotpack.ru/wp-content/uploads/2025/03/pik-v-genshin-impact-kak-pobedit-mutanta-730x411.jpg", "guideUrl": "https://wotpack.ru/pik-v-genshin-impact-kak-nayti-i-pobedit-mutanta/" },
{ "name": "Боевой козел и Железный коготь", "icon": "https://wotpack.ru/wp-content/uploads/2025/03/boevoy-kozyol-i-zheleznyy-kogot-v-genshin-impact_-kak-nayti-i-pobedit-730x411.jpg", "guideUrl": "https://wotpack.ru/boevoy-kozyol-i-zheleznyy-kogot-v-genshin-impact-kak-nayti-i-pobedit/" },
{ "name": "Теночцитока", "icon": "https://wotpack.ru/wp-content/uploads/2025/07/posledniy-vyzhivshiy-iz-tenochtsitoka-v-genshin-impact-730x455.jpg", "guideUrl": "https://wotpack.ru/posledniy-vyzhivshiy-iz-tenochtsitoka-v-genshin-impact-kak-nayti-i-pobedit/" },
{ "name": "Царь-краб, хозяин дворца", "icon": "https://wotpack.ru/wp-content/uploads/2025/09/word-image-546429-2.jpeg", "guideUrl": "https://wotpack.ru/tsarskiy-krab-v-genshin-impact-kak-pobedit-i-proyti-ispytanie/" },
{ "name": "Сигурд", "icon": "https://wotpack.ru/wp-content/uploads/2025/09/sigurd-v-genshin-impact-730x411.jpg", "guideUrl": "https://wotpack.ru/sigurd-v-genshin-impact-kak-pobedit-i-proyti-ispytanie/" },
{ "name": "Раскольников", "icon": "https://wotpack.ru/wp-content/uploads/2025/09/raskolnikov-v-genshin-impact-730x411.jpg", "guideUrl": "https://wotpack.ru/raskolnikov-v-genshin-impact-kak-pobedit-i-proyti-ispytanie/" },
{ "name": "Ицзи и Тяньюнь", "icon": "https://wotpack.ru/wp-content/uploads/2024/01/20240131070630.jpg", "guideUrl": "https://wotpack.ru/vladyki-gor-chichzhan-v-genshin-impact-kak-najti-i-pobedit-iczi-i-tjanjun/" },
];
// Генерация или получение UID
function getOrCreateUID() {
let uid = localStorage.getItem('genshinRouletteUID');
if (!uid) {
uid = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
localStorage.setItem('genshinRouletteUID', uid);
}
return uid;
}
const currentUserUID = getOrCreateUID();
// Конфигурация
const config = {
teamSize: parseInt(localStorage.getItem(`${currentUserUID}_teamSize`)) || 4,
animationSpeed: localStorage.getItem(`${currentUserUID}_animationSpeed`) || 'medium',
darkMode: localStorage.getItem('theme') === 'dark',
category: localStorage.getItem('genshinCategory') || 'characters',
excludedBosses: JSON.parse(localStorage.getItem(`${currentUserUID}_excludedBosses`)) || {
'world-bosses': [],
'weekly-bosses': [],
'local-legends': []
}
};
// Состояние приложения
let allCharacters = [...LOCAL_CHARACTERS];
let historyTeams = JSON.parse(localStorage.getItem(`${currentUserUID}_genshinTeamsHistory`)) || [];
let historyBosses = JSON.parse(localStorage.getItem(`${currentUserUID}_genshinBossesHistory`)) || [];
let playerCharacters = JSON.parse(localStorage.getItem(`${currentUserUID}_playerCharacters`)) || [];
let isSpinning = false;
let currentGeneratedTeam = [];
let currentGeneratedBoss = null;
let isWorldBoss = true;
let isLegend = false;
// Элементы DOM
const spinCharactersButton = document.getElementById('spin-characters-button');
const spinWorldBossButton = document.getElementById('spin-world-boss-button');
const spinWeeklyBossButton = document.getElementById('spin-weekly-boss-button');
const spinLegendButton = document.getElementById('spin-legend-button');
const categorySelect = document.getElementById('category-select');
const charactersResult = document.getElementById('characters-result');
const worldBossResult = document.getElementById('world-boss-result');
const weeklyBossResult = document.getElementById('weekly-boss-result');
const legendResult = document.getElementById('legend-result');
const charactersPlaceholder = document.getElementById('characters-placeholder');
const worldBossPlaceholder = document.getElementById('world-boss-placeholder');
const weeklyBossPlaceholder = document.getElementById('weekly-boss-placeholder');
const legendPlaceholder = document.getElementById('legend-placeholder');
const charactersLoader = document.getElementById('characters-loader');
const worldBossLoader = document.getElementById('world-boss-loader');
const weeklyBossLoader = document.getElementById('weekly-boss-loader');
const legendLoader = document.getElementById('legend-loader');
const historyContent = document.getElementById('history-content');
const historyPlaceholder = document.getElementById('history-placeholder');
const clearHistoryButton = document.getElementById('clear-history-button');
const showFullHistoryButton = document.getElementById('show-full-history-button');
const themeToggle = document.getElementById('theme-toggle');
const settingsToggle = document.getElementById('settings-toggle');
const settingsPanel = document.getElementById('settings-panel');
const teamSizeSelect = document.getElementById('team-size');
const animationSpeedSelect = document.getElementById('animation-speed');
const confirmModal = document.getElementById('confirm-modal');
const confirmModalContent = document.getElementById('confirm-modal-content');
const confirmModalMessage = document.getElementById('confirm-modal-message');
const confirmModalCancel = document.getElementById('confirm-modal-cancel');
const confirmModalOk = document.getElementById('confirm-modal-ok');
const playerCharactersSelection = document.getElementById('player-characters-selection');
const characterSearchInput = document.getElementById('character-search');
const clearPlayerCharactersButton = document.getElementById('clear-player-characters');
const saveTeamSection = document.getElementById('save-characters-section');
const teamNameInput = document.getElementById('team-name-input');
const saveTeamButton = document.getElementById('save-team-button');
const saveWorldBossSection = document.getElementById('save-world-boss-section');
const saveWorldBossButton = document.getElementById('save-world-boss-button');
const saveWeeklyBossSection = document.getElementById('save-weekly-boss-section');
const saveWeeklyBossButton = document.getElementById('save-weekly-boss-button');
const saveLegendSection = document.getElementById('save-legend-section');
const saveLegendButton = document.getElementById('save-legend-button');
const boomSound = document.getElementById('boom-sound');
const charactersSection = document.getElementById('characters-section');
const worldBossesSection = document.getElementById('world-bosses-section');
const weeklyBossesSection = document.getElementById('weekly-bosses-section');
const localLegendsSection = document.getElementById('local-legends-section');
const bossCategorySelect = document.getElementById('boss-category-select');
const bossesSettingsContainer = document.getElementById('bosses-settings-container');
// Full History Modal elements
const fullHistoryModal = document.getElementById('full-history-modal');
const fullHistoryModalContent = document.getElementById('full-history-modal-content');
const fullHistoryModalContentInner = document.getElementById('full-history-modal-content-inner');
const fullHistoryModalCloseButton = document.getElementById('full-history-modal-close');
// Инициализация темы
function initTheme() {
if (config.darkMode) {
document.body.classList.remove('light');
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
document.body.classList.add('light');
}
}
// Переключение темы
function toggleTheme() {
config.darkMode = !config.darkMode;
initTheme();
localStorage.setItem('theme', config.darkMode ? 'dark' : 'light');
}
// Переключение настроек
function toggleSettings() {
settingsPanel.classList.toggle('open');
}
// Изменение категории
function changeCategory() {
config.category = categorySelect.value;
localStorage.setItem('genshinCategory', config.category);
// Скрываем все секции
charactersSection.classList.add('hidden');
worldBossesSection.classList.add('hidden');
weeklyBossesSection.classList.add('hidden');
localLegendsSection.classList.add('hidden');
// Показываем выбранную секцию
if (config.category === 'characters') {
charactersSection.classList.remove('hidden');
} else if (config.category === 'world-bosses') {
worldBossesSection.classList.remove('hidden');
} else if (config.category === 'weekly-bosses') {
weeklyBossesSection.classList.remove('hidden');
} else if (config.category === 'local-legends') {
localLegendsSection.classList.remove('hidden');
}
}
// Создание карточки персонажа
function createCharacterCard(character) {
const card = document.createElement('div');
card.className = `flex flex-col items-center p-2 rounded-lg bg-white dark:bg-gray-700 shadow-sm text-center flex-shrink-0 w-[100px] sm:w-[120px] md:w-[130px] lg:w-[140px]`;
card.innerHTML = `
<img src="${character.icon}" alt="${character.name}"
class="w-16 h-16 sm:w-20 sm:h-20 rounded-full mb-1 object-cover border-2"
onerror="this.onerror=null;this.src='https://placehold.co/80x80/EFEAE4/4A4A4A?text=N/A'">
<p class="font-medium text-sm truncate w-full dark:text-white">${character.name}</p>
`;
return card;
}
// Создание карточки босса
function createBossCard(boss) {
const card = document.createElement('div');
card.className = `boss-card`;
card.innerHTML = `
<img src="${boss.icon}" alt="${boss.name}"
onerror="this.onerror=null;this.src='https://placehold.co/80x80/EFEAE4/4A4A4A?text=N/A'">
<h3 class="font-bold text-lg dark:text-white">${boss.name}</h3>
`;
return card;
}
// Создание карточки местной легенды (кликабельной)
function createLegendCard(legend) {
const card = document.createElement('div');
card.className = `boss-card legend-card`;
// Делаем карточку кликабельной только если есть ссылка на гайд
const isClickable = legend.guideUrl && legend.guideUrl !== "#";
card.innerHTML = `
<img src="${legend.icon}" alt="${legend.name}"
onerror="this.onerror=null;this.src='https://placehold.co/80x80/EFEAE4/4A4A4A?text=N/A'">
<h3 class="font-bold text-lg dark:text-white">${legend.name}</h3>
<p class="text-sm dark:text-gray-300">${isClickable ? 'Нажмите чтобы узнать про Легенду' : 'Нет ссылки на гайд'}</p>
`;
if (isClickable) {
card.style.cursor = 'pointer';
card.addEventListener('click', () => {
window.open(legend.guideUrl, '_blank');
});
}
return card;
}
// Создание карточки персонажа для выбора (в секции "Мои персонажи")
function createPlayerCharacterSelectionCard(character) {
const card = document.createElement('div');
card.className = `player-char-card flex flex-col items-center p-1 rounded-lg bg-white dark:bg-gray-800 shadow-sm text-center cursor-pointer relative`;
card.setAttribute('data-name', character.name);
const isSelected = playerCharacters.some(c => c.name === character.name);
if (isSelected) {
card.classList.add('selected');
}
const elementIconHtml = character.element ?
`<img src="pic/Elements/Element_${character.element}.webp" alt="${character.element}"
class="w-5 h-5 absolute top-0 right-0 -mt-1 -mr-1" title="${character.element}">` : '';
card.innerHTML = `
<div class="relative">
<img src="${character.icon}" alt="${character.name}"
class="w-12 h-12 rounded-full mb-1 object-cover border-2 border-transparent"
onerror="this.onerror=null;this.src='https://placehold.co/48x48/EFEAE4/4A4A4A?text=N/A'">
${elementIconHtml}
</div>
<p class="font-medium text-xs truncate w-full dark:text-white">${character.name}</p>
${isSelected ? '<button class="remove-btn" title="Удалить">&times;</button>' : ''}
`;
const img = card.querySelector('img');
if (isSelected) {
img.classList.remove('border-transparent');
img.classList.add('border-[#DCA852]');
}
card.addEventListener('click', (event) => {
if (event.target.classList.contains('remove-btn')) {
removePlayerCharacter(character.name);
} else {
togglePlayerCharacter(character);
}
});
return card;
}
// Рендеринг настроек для боссов
function renderBossSettings() {
const category = bossCategorySelect.value;
let bosses = [];
if (category === 'world-bosses') {
bosses = WORLD_BOSSES;
} else if (category === 'weekly-bosses') {
bosses = WEEKLY_BOSSES;
} else if (category === 'local-legends') {
bosses = LOCAL_LEGENDS;
}
bossesSettingsContainer.innerHTML = '';
bosses.forEach(boss => {
const isExcluded = config.excludedBosses[category].includes(boss.name);
const bossToggle = document.createElement('div');
bossToggle.className = 'boss-toggle flex items-center';
bossToggle.innerHTML = `
<input type="checkbox" id="boss-${category}-${boss.name.replace(/\s+/g, '-')}"
${isExcluded ? '' : 'checked'} class="boss-toggle-checkbox">
<label for="boss-${category}-${boss.name.replace(/\s+/g, '-')}" class="ml-2">${boss.name}</label>
`;
bossesSettingsContainer.appendChild(bossToggle);
const checkbox = bossToggle.querySelector('.boss-toggle-checkbox');
checkbox.addEventListener('change', (e) => {
const excludedList = config.excludedBosses[category];
if (e.target.checked) {
// Включить босса - убрать из списка исключенных
const index = excludedList.indexOf(boss.name);
if (index > -1) {
excludedList.splice(index, 1);
}
} else {
// Исключить босса - добавить в список исключенных
if (!excludedList.includes(boss.name)) {
excludedList.push(boss.name);
}
}
localStorage.setItem(`${currentUserUID}_excludedBosses`, JSON.stringify(config.excludedBosses));
});
});
}
// Рендеринг списка персонажей для выбора
function renderPlayerCharactersSelection(filter = '') {
playerCharactersSelection.innerHTML = '';
const filteredCharacters = allCharacters.filter(char =>
char.name.toLowerCase().includes(filter.toLowerCase())
);
filteredCharacters.forEach(char => {
playerCharactersSelection.appendChild(createPlayerCharacterSelectionCard(char));
});
updateClearPlayerCharactersButtonVisibility();
}
// Добавление/удаление персонажа из списка игрока
function togglePlayerCharacter(character) {
const index = playerCharacters.findIndex(c => c.name === character.name);
if (index > -1) {
playerCharacters.splice(index, 1);
} else {
playerCharacters.push(character);
}
localStorage.setItem(`${currentUserUID}_playerCharacters`, JSON.stringify(playerCharacters));
renderPlayerCharactersSelection(characterSearchInput.value);
updateClearPlayerCharactersButtonVisibility();
}
// Удаление персонажа из списка игрока по имени
function removePlayerCharacter(characterName) {
playerCharacters = playerCharacters.filter(c => c.name !== characterName);
localStorage.setItem(`${currentUserUID}_playerCharacters`, JSON.stringify(playerCharacters));
renderPlayerCharactersSelection(characterSearchInput.value);
updateClearPlayerCharactersButtonVisibility();
}
// Очистка всех персонажей игрока
function clearPlayerCharacters() {
playerCharacters = [];
localStorage.removeItem(`${currentUserUID}_playerCharacters`);
renderPlayerCharactersSelection('');
hideConfirmModal();
}
// Обновление видимости кнопки "Очистить выбранных персонажей"
function updateClearPlayerCharactersButtonVisibility() {
if (playerCharacters.length > 0) {
clearPlayerCharactersButton.classList.remove('hidden');
} else {
clearPlayerCharactersButton.classList.add('hidden');
}
}
// Отображение результата рулетки с плавной анимацией и "бум" эффектом
async function renderCharactersResult(team) {
charactersResult.innerHTML = '';
charactersPlaceholder.classList.add('hidden');
saveTeamSection.classList.add('hidden');
for (let i = 0; i < team.length; i++) {
const char = team[i];
const card = createCharacterCard(char);
card.classList.add('roulette-item-boom-enter');
charactersResult.appendChild(card);
if (boomSound) {
boomSound.currentTime = 0;
boomSound.play().catch(e => console.error("Error playing sound:", e));
}
await new Promise(resolve => setTimeout(resolve, 100));
card.classList.add('roulette-item-boom-enter-active');
await new Promise(resolve => setTimeout(resolve, 500));
}
saveTeamSection.classList.remove('hidden');
teamNameInput.value = '';
currentGeneratedTeam = team;
}
// Отображение результата для босса
async function renderBossResult(boss, isWorld) {
const resultDiv = isWorld ? worldBossResult : weeklyBossResult;
const placeholder = isWorld ? worldBossPlaceholder : weeklyBossPlaceholder;
const saveSection = isWorld ? saveWorldBossSection : saveWeeklyBossSection;
resultDiv.innerHTML = '';
placeholder.classList.add('hidden');
saveSection.classList.add('hidden');
const card = createBossCard(boss);
card.classList.add('roulette-item-boom-enter');
resultDiv.appendChild(card);
if (boomSound) {
boomSound.currentTime = 0;
boomSound.play().catch(e => console.error("Error playing sound:", e));
}
await new Promise(resolve => setTimeout(resolve, 100));
card.classList.add('roulette-item-boom-enter-active');
await new Promise(resolve => setTimeout(resolve, 500));
saveSection.classList.remove('hidden');
currentGeneratedBoss = boss;
isWorldBoss = isWorld;
isLegend = false;
}
// Отображение результата для местной легенды
async function renderLegendResult(legend) {
legendResult.innerHTML = '';
legendPlaceholder.classList.add('hidden');
saveLegendSection.classList.add('hidden');
const card = createLegendCard(legend);
card.classList.add('roulette-item-boom-enter');
legendResult.appendChild(card);
if (boomSound) {
boomSound.currentTime = 0;
boomSound.play().catch(e => console.error("Error playing sound:", e));
}
await new Promise(resolve => setTimeout(resolve, 100));
card.classList.add('roulette-item-boom-enter-active');
await new Promise(resolve => setTimeout(resolve, 500));
saveLegendSection.classList.remove('hidden');
currentGeneratedBoss = legend;
isWorldBoss = false;
isLegend = true;
}
// Отображение истории
function renderHistory(limit = 3) {
historyContent.innerHTML = '';
const allHistory = [...historyTeams, ...historyBosses].sort((a, b) => new Date(b.date) - new Date(a.date));
if (allHistory.length === 0) {
historyPlaceholder.classList.remove('hidden');
clearHistoryButton.classList.add('hidden');
showFullHistoryButton.classList.add('hidden');
} else {
historyPlaceholder.classList.add('hidden');
clearHistoryButton.classList.remove('hidden');
showFullHistoryButton.classList.remove('hidden');
const itemsToShow = allHistory.slice(0, limit);
itemsToShow.forEach((item) => {
const itemElement = document.createElement('div');
itemElement.className = `bg-[#FDF8F0] dark:bg-gray-800 rounded-xl p-4 shadow-sm border border-[#EFEAE4] dark:border-gray-600 ${item.type === 'team' ? 'history-item' : 'boss-history-item'}`;
const title = document.createElement('h3');
title.className = 'text-lg font-semibold mb-3 dark:text-white';
if (item.type === 'team') {
const teamName = item.name ? `"${item.name}" - ` : '';
title.textContent = `${teamName}Отряд (${new Date(item.date).toLocaleString()})`;
} else if (item.type === 'legend') {
title.textContent = `${item.name} (Местная легенда) (${new Date(item.date).toLocaleString()})`;
} else {
title.textContent = `${item.name} (${new Date(item.date).toLocaleString()})`;
}
itemElement.appendChild(title);
const container = document.createElement('div');
container.className = 'flex flex-wrap justify-center gap-3';
if (item.type === 'team') {
item.characters.forEach(char => {
container.appendChild(createCharacterCard(char));
});
} else if (item.type === 'legend') {
container.appendChild(createLegendCard(item));
} else {
container.appendChild(createBossCard(item));
}
itemElement.appendChild(container);
historyContent.appendChild(itemElement);
});
}
}
// Показать модальное окно всей истории
function showFullHistoryModal() {
fullHistoryModalContentInner.innerHTML = '';
const allHistory = [...historyTeams, ...historyBosses].sort((a, b) => new Date(b.date) - new Date(a.date));
if (allHistory.length === 0) {
fullHistoryModalContentInner.innerHTML = '<p class="text-center text-[#888] dark:text-gray-400">История пуста.</p>';
} else {
allHistory.forEach((item) => {
const itemElement = document.createElement('div');
itemElement.className = `bg-[#EFEAE4] dark:bg-gray-700 rounded-xl p-4 shadow-sm border border-[#EFEAE4] dark:border-gray-600 ${item.type === 'team' ? 'history-item' : 'boss-history-item'}`;
const title = document.createElement('h3');
title.className = 'text-lg font-semibold mb-3 text-[#4A4A4A] dark:text-white';
if (item.type === 'team') {
const teamName = item.name ? `"${item.name}" - ` : '';
title.textContent = `${teamName}Отряд (${new Date(item.date).toLocaleString()})`;
} else if (item.type === 'legend') {
title.textContent = `${item.name} (Местная легенда) (${new Date(item.date).toLocaleString()})`;
} else {
title.textContent = `${item.name} (${new Date(item.date).toLocaleString()})`;
}
itemElement.appendChild(title);
const container = document.createElement('div');
container.className = 'flex gap-3 overflow-x-auto pb-2 custom-scrollbar';
if (item.type === 'team') {
item.characters.forEach(char => {
const card = document.createElement('div');
card.className = 'full-history-character-card';
card.innerHTML = `
<img src="${char.icon}" alt="${char.name}"
class="w-16 h-16 rounded-full mb-1 object-cover border-2"
onerror="this.onerror=null;this.src='https://placehold.co/64x64/EFEAE4/4A4A4A?text=N/A'">
<p class="font-medium text-sm truncate w-full">${char.name}</p>
`;
container.appendChild(card);
});
} else if (item.type === 'legend') {
const legendCard = document.createElement('div');
legendCard.className = 'full-history-character-card';
legendCard.innerHTML = `
<img src="${item.icon}" alt="${item.name}"
class="w-16 h-16 rounded-full mb-1 object-cover border-2"
onerror="this.onerror=null;this.src='https://placehold.co/64x64/EFEAE4/4A4A4A?text=N/A'">
<p class="font-medium text-sm truncate w-full">${item.name}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">Местная легенда</p>
`;
container.appendChild(legendCard);
} else {
const bossCard = document.createElement('div');
bossCard.className = 'full-history-character-card';
bossCard.innerHTML = `
<img src="${item.icon}" alt="${item.name}"
class="w-16 h-16 rounded-full mb-1 object-cover border-2"
onerror="this.onerror=null;this.src='https://placehold.co/64x64/EFEAE4/4A4A4A?text=N/A'">
<p class="font-medium text-sm truncate w-full">${item.name}</p>
`;
container.appendChild(bossCard);
}
itemElement.appendChild(container);
fullHistoryModalContentInner.appendChild(itemElement);
});
}
fullHistoryModal.classList.remove('hidden');
setTimeout(() => {
fullHistoryModalContent.classList.remove('scale-95', 'opacity-0');
fullHistoryModalContent.classList.add('scale-100', 'opacity-100');
}, 10);
}
// Скрыть модальное окно всей истории
function hideFullHistoryModal() {
fullHistoryModalContent.classList.add('scale-95', 'opacity-0');
fullHistoryModalContent.classList.remove('scale-100', 'opacity-100');
setTimeout(() => {
fullHistoryModal.classList.add('hidden');
}, 300);
}
// Сохранение отряда в историю
function saveTeamToHistory(team, name = '') {
historyTeams.unshift({
date: new Date().toISOString(),
characters: team,
name: name,
type: 'team'
});
localStorage.setItem(`${currentUserUID}_genshinTeamsHistory`, JSON.stringify(historyTeams));
renderHistory();
}
// Сохранение босса в историю
function saveBossToHistory(boss, isWorld, isLegendType = false) {
const type = isLegendType ? 'legend' : (isWorld ? 'world-boss' : 'weekly-boss');
const bossData = {
date: new Date().toISOString(),
name: boss.name,
icon: boss.icon,
type: type
};
historyBosses.unshift(bossData);
localStorage.setItem(`${currentUserUID}_genshinBossesHistory`, JSON.stringify(historyBosses));
renderHistory();
}
// Очистка истории
function clearHistory() {
historyTeams = [];
historyBosses = [];
localStorage.removeItem(`${currentUserUID}_genshinTeamsHistory`);
localStorage.removeItem(`${currentUserUID}_genshinBossesHistory`);
renderHistory();
hideConfirmModal();
}
// Запуск рулетки персонажей
async function spinCharacters() {
const availableCharsForSpin = playerCharacters.length > 0 ? playerCharacters : allCharacters;
if (isSpinning || availableCharsForSpin.length < config.teamSize) {
alert(`Недостаточно персонажей для формирования отряда из ${config.teamSize} человек. Доступно: ${availableCharsForSpin.length}`);
return;
}
isSpinning = true;
spinCharactersButton.disabled = true;
spinCharactersButton.textContent = 'Крутим...';
charactersResult.innerHTML = '';
charactersPlaceholder.classList.add('hidden');
charactersLoader.classList.remove('hidden');
saveTeamSection.classList.add('hidden');
const teamSize = parseInt(teamSizeSelect.value);
let charsPool = [...availableCharsForSpin];
const selectedTeam = [];
let spinDurationMs;
switch (animationSpeedSelect.value) {
case 'fast': spinDurationMs = 1500; break;
case 'slow': spinDurationMs = 4000; break;
default: spinDurationMs = 2500;
}
const individualSpinTime = spinDurationMs / teamSize;
for (let i = 0; i < teamSize; i++) {
const tempDiv = document.createElement('div');
tempDiv.className = 'flex flex-col items-center flex-shrink-0 w-[100px] sm:w-[120px] md:w-[130px] lg:w-[140px]';
charactersResult.appendChild(tempDiv);
let startTime = Date.now();
let animationFrame;
const animateSpin = () => {
const elapsedTime = Date.now() - startTime;
if (elapsedTime < individualSpinTime) {
const randomChar = charsPool[Math.floor(Math.random() * charsPool.length)];
tempDiv.innerHTML = '';
const tempCard = createCharacterCard(randomChar);
tempCard.classList.add('roulette-item-spin');
tempDiv.appendChild(tempCard);
animationFrame = requestAnimationFrame(animateSpin);
} else {
cancelAnimationFrame(animationFrame);
const selectedIndex = Math.floor(Math.random() * charsPool.length);
const selectedChar = charsPool.splice(selectedIndex, 1)[0];
selectedTeam.push(selectedChar);
tempDiv.innerHTML = '';
}
};
animateSpin();
await new Promise(resolve => setTimeout(resolve, individualSpinTime + 100));
}
// Завершение рулетки
charactersLoader.classList.add('hidden');
spinCharactersButton.disabled = false;
spinCharactersButton.textContent = 'Крутить ещё!';
isSpinning = false;
renderCharactersResult(selectedTeam);
}
// Запуск рулетки мирового босса
async function spinWorldBoss() {
if (isSpinning) return;
isSpinning = true;
spinWorldBossButton.disabled = true;
spinWorldBossButton.textContent = 'Выбираем...';
worldBossResult.innerHTML = '';
worldBossPlaceholder.classList.add('hidden');
worldBossLoader.classList.remove('hidden');
saveWorldBossSection.classList.add('hidden');
await new Promise(resolve => setTimeout(resolve, 1000));
// Фильтруем исключенных боссов
const availableBosses = WORLD_BOSSES.filter(boss =>
!config.excludedBosses['world-bosses'].includes(boss.name)
);
if (availableBosses.length === 0) {
worldBossResult.innerHTML = '<p class="text-center text-red-500">Нет доступных боссов! Проверьте настройки.</p>';
worldBossLoader.classList.add('hidden');
spinWorldBossButton.disabled = false;
spinWorldBossButton.textContent = 'Выбрать ещё!';
isSpinning = false;
return;
}
const randomBoss = availableBosses[Math.floor(Math.random() * availableBosses.length)];
worldBossLoader.classList.add('hidden');
spinWorldBossButton.disabled = false;
spinWorldBossButton.textContent = 'Выбрать ещё!';
isSpinning = false;
renderBossResult(randomBoss, true);
}
// Запуск рулетки еженедельного босса
async function spinWeeklyBoss() {
if (isSpinning) return;
isSpinning = true;
spinWeeklyBossButton.disabled = true;
spinWeeklyBossButton.textContent = 'Выбираем...';
weeklyBossResult.innerHTML = '';
weeklyBossPlaceholder.classList.add('hidden');
weeklyBossLoader.classList.remove('hidden');
saveWeeklyBossSection.classList.add('hidden');
await new Promise(resolve => setTimeout(resolve, 1000));
// Фильтруем исключенных боссов
const availableBosses = WEEKLY_BOSSES.filter(boss =>
!config.excludedBosses['weekly-bosses'].includes(boss.name)
);
if (availableBosses.length === 0) {
weeklyBossResult.innerHTML = '<p class="text-center text-red-500">Нет доступных боссов! Проверьте настройки.</p>';
weeklyBossLoader.classList.add('hidden');
spinWeeklyBossButton.disabled = false;
spinWeeklyBossButton.textContent = 'Выбрать ещё!';
isSpinning = false;
return;
}
const randomBoss = availableBosses[Math.floor(Math.random() * availableBosses.length)];
weeklyBossLoader.classList.add('hidden');
spinWeeklyBossButton.disabled = false;
spinWeeklyBossButton.textContent = 'Выбрать ещё!';
isSpinning = false;
renderBossResult(randomBoss, false);
}
// Запуск рулетки местной легенды
async function spinLocalLegend() {
if (isSpinning) return;
isSpinning = true;
spinLegendButton.disabled = true;
spinLegendButton.textContent = 'Выбираем...';
legendResult.innerHTML = '';
legendPlaceholder.classList.add('hidden');
legendLoader.classList.remove('hidden');
saveLegendSection.classList.add('hidden');
await new Promise(resolve => setTimeout(resolve, 1000));
// Фильтруем исключенных легенд
const availableLegends = LOCAL_LEGENDS.filter(legend =>
!config.excludedBosses['local-legends'].includes(legend.name)
);
if (availableLegends.length === 0) {
legendResult.innerHTML = '<p class="text-center text-red-500">Нет доступных легенд! Проверьте настройки.</p>';
legendLoader.classList.add('hidden');
spinLegendButton.disabled = false;
spinLegendButton.textContent = 'Выбрать ещё!';
isSpinning = false;
return;
}
const randomLegend = availableLegends[Math.floor(Math.random() * availableLegends.length)];
legendLoader.classList.add('hidden');
spinLegendButton.disabled = false;
spinLegendButton.textContent = 'Выбрать ещё!';
isSpinning = false;
renderLegendResult(randomLegend);
}
// Модальное окно подтверждения
function showConfirmModal(message, callback) {
confirmModalMessage.textContent = message;
confirmModal.classList.remove('hidden');
setTimeout(() => {
confirmModalContent.classList.remove('scale-95', 'opacity-0');
confirmModalContent.classList.add('scale-100', 'opacity-100');
}, 10);
const handleConfirm = () => {
callback();
confirmModalOk.removeEventListener('click', handleConfirm);
confirmModalCancel.removeEventListener('click', handleCancel);
};
const handleCancel = () => {
hideConfirmModal();
confirmModalOk.removeEventListener('click', handleConfirm);
confirmModalCancel.removeEventListener('click', handleCancel);
};
confirmModalOk.addEventListener('click', handleConfirm);
confirmModalCancel.addEventListener('click', handleCancel);
}
function hideConfirmModal() {
confirmModalContent.classList.add('scale-95', 'opacity-0');
confirmModalContent.classList.remove('scale-100', 'opacity-100');
setTimeout(() => {
confirmModal.classList.add('hidden');
}, 300);
}
// Обработчики событий
themeToggle.addEventListener('click', toggleTheme);
settingsToggle.addEventListener('click', toggleSettings);
categorySelect.addEventListener('change', changeCategory);
bossCategorySelect.addEventListener('change', renderBossSettings);
spinCharactersButton.addEventListener('click', spinCharacters);
spinWorldBossButton.addEventListener('click', spinWorldBoss);
spinWeeklyBossButton.addEventListener('click', spinWeeklyBoss);
spinLegendButton.addEventListener('click', spinLocalLegend);
clearHistoryButton.addEventListener('click', () => {
showConfirmModal('Вы уверены, что хотите очистить всю историю?', clearHistory);
});
clearPlayerCharactersButton.addEventListener('click', () => {
showConfirmModal('Вы уверены, что хотите очистить список ваших персонажей?', clearPlayerCharacters);
});
showFullHistoryButton.addEventListener('click', showFullHistoryModal);
fullHistoryModalCloseButton.addEventListener('click', hideFullHistoryModal);
fullHistoryModal.addEventListener('click', (e) => {
if (e.target === fullHistoryModal) hideFullHistoryModal();
});
saveTeamButton.addEventListener('click', () => {
const teamName = teamNameInput.value.trim();
if (currentGeneratedTeam.length > 0) {
saveTeamToHistory(currentGeneratedTeam, teamName);
saveTeamSection.classList.add('hidden');
}
});
saveWorldBossButton.addEventListener('click', () => {
if (currentGeneratedBoss) {
saveBossToHistory(currentGeneratedBoss, true);
saveWorldBossSection.classList.add('hidden');
}
});
saveWeeklyBossButton.addEventListener('click', () => {
if (currentGeneratedBoss) {
saveBossToHistory(currentGeneratedBoss, false);
saveWeeklyBossSection.classList.add('hidden');
}
});
saveLegendButton.addEventListener('click', () => {
if (currentGeneratedBoss) {
saveBossToHistory(currentGeneratedBoss, false, true);
saveLegendSection.classList.add('hidden');
}
});
confirmModal.addEventListener('click', (e) => {
if (e.target === confirmModal) hideConfirmModal();
});
characterSearchInput.addEventListener('input', (e) => {
renderPlayerCharactersSelection(e.target.value);
});
// Сохранение настроек при изменении
teamSizeSelect.addEventListener('change', (e) => {
config.teamSize = parseInt(e.target.value);
localStorage.setItem(`${currentUserUID}_teamSize`, config.teamSize);
});
animationSpeedSelect.addEventListener('change', (e) => {
config.animationSpeed = e.target.value;
localStorage.setItem(`${currentUserUID}_animationSpeed`, config.animationSpeed);
});
// Установка сохраненных настроек при загрузке
teamSizeSelect.value = config.teamSize;
animationSpeedSelect.value = config.animationSpeed;
categorySelect.value = config.category;
bossCategorySelect.value = 'world-bosses';
// Инициализация приложения
initTheme();
renderPlayerCharactersSelection();
renderHistory();
renderBossSettings();
changeCategory(); // Показать выбранную категорию при загрузке
});
</script>
</body>
</html>