| <!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/" }, |
| ]; |
| |
| |
| 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; |
| |
| |
| 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'); |
| |
| |
| 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="Удалить">×</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> |