Update index.html
Browse files- index.html +309 -142
index.html
CHANGED
|
@@ -50,25 +50,64 @@
|
|
| 50 |
transition: max-height 0.3s ease-out;
|
| 51 |
}
|
| 52 |
.settings-panel.open {
|
| 53 |
-
max-height:
|
| 54 |
}
|
| 55 |
-
/*
|
| 56 |
-
.roulette-
|
| 57 |
-
animation: spin-
|
| 58 |
}
|
| 59 |
-
@keyframes spin-
|
| 60 |
-
0% { transform:
|
| 61 |
-
|
| 62 |
-
100% { transform: translateY(0) rotate(0deg); opacity: 1; }
|
| 63 |
}
|
| 64 |
-
|
|
|
|
|
|
|
| 65 |
opacity: 0;
|
| 66 |
-
transform: scale(0.
|
|
|
|
|
|
|
| 67 |
}
|
| 68 |
-
.roulette-item-enter-active {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
opacity: 1;
|
| 70 |
-
transform: scale(1);
|
| 71 |
-
transition: opacity 0.5s ease-out, transform 0.5s ease-out;
|
| 72 |
}
|
| 73 |
</style>
|
| 74 |
</head>
|
|
@@ -88,7 +127,7 @@
|
|
| 88 |
</svg>
|
| 89 |
</button>
|
| 90 |
</div>
|
| 91 |
-
<h1 class="text-4xl md:text-5xl font-bold text-[#4A4A4A] dark:text-[#e2e8f0] mb-2">
|
| 92 |
<p class="text-lg text-[#6B6B6B] dark:text-[#a0aec0] max-w-2xl mx-auto">
|
| 93 |
Сгенерируйте случайный отряд из четырех персонажей Genshin Impact!
|
| 94 |
</p>
|
|
@@ -97,7 +136,7 @@
|
|
| 97 |
<main>
|
| 98 |
<div id="settings-panel" class="settings-panel bg-[#EFEAE4] dark:bg-gray-700 p-6 rounded-2xl shadow-lg mb-6">
|
| 99 |
<h3 class="text-xl font-bold mb-4 dark:text-white">Настройки рулетки</h3>
|
| 100 |
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 101 |
<div>
|
| 102 |
<label for="team-size" class="block text-sm font-medium mb-2 dark:text-gray-300">Количество персонажей в отряде</label>
|
| 103 |
<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">
|
|
@@ -116,6 +155,21 @@
|
|
| 116 |
</select>
|
| 117 |
</div>
|
| 118 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
</div>
|
| 120 |
|
| 121 |
<section id="roulette-section" class="mb-16 bg-[#EFEAE4] dark:bg-gray-700 p-6 rounded-2xl shadow-lg">
|
|
@@ -129,14 +183,30 @@
|
|
| 129 |
<p id="roulette-placeholder" class="text-[#888] dark:text-gray-400">Ваш случайный отряд появится здесь.</p>
|
| 130 |
<div id="roulette-loader" class="loader hidden"></div>
|
| 131 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
</section>
|
| 133 |
|
| 134 |
<section id="history-section" class="bg-[#EFEAE4] dark:bg-gray-700 p-6 rounded-2xl shadow-lg">
|
| 135 |
<div class="flex justify-between items-center mb-8">
|
| 136 |
<h2 class="text-3xl font-bold text-[#4A4A4A] dark:text-white">История Отрядов</h2>
|
| 137 |
-
<
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
</div>
|
| 141 |
<div id="history-content" class="flex flex-col gap-6">
|
| 142 |
<p id="history-placeholder" class="text-center text-[#888] dark:text-gray-400">История ваших отрядов будет отображаться здесь.</p>
|
|
@@ -154,7 +224,7 @@
|
|
| 154 |
<div id="confirm-modal" class="fixed inset-0 bg-black bg-opacity-70 z-50 flex justify-center items-center hidden p-4">
|
| 155 |
<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">
|
| 156 |
<h3 class="text-2xl font-bold mb-4 text-[#4A4A4A] dark:text-white">Подтверждение</h3>
|
| 157 |
-
<p id="confirm-modal-message" class="text-lg text-[#6B6B6B] dark:text-gray-300 mb-6">
|
| 158 |
<div class="flex justify-around gap-4">
|
| 159 |
<button id="confirm-modal-cancel" class="bg-[#A089D0] text-white font-bold py-2 px-6 rounded-lg hover:bg-[#8e7abf]">Отмена</button>
|
| 160 |
<button id="confirm-modal-ok" class="bg-red-500 text-white font-bold py-2 px-6 rounded-lg hover:bg-red-600">Очистить</button>
|
|
@@ -178,10 +248,10 @@
|
|
| 178 |
{ "name": "Беннет", "icon": "https://game-cdn.appsample.com/gim/items/bennett.png" },
|
| 179 |
{ "name": "Кандакия", "icon": "https://game-cdn.appsample.com/gim/items/candace.png" },
|
| 180 |
{ "name": "Шарлотта", "icon": "https://game-cdn.appsample.com/gim/items/charlotte.png" },
|
| 181 |
-
{ "name": "Час
|
| 182 |
-
{ "name": "
|
| 183 |
{ "name": "Чун Юнь", "icon": "https://game-cdn.appsample.com/gim/items/chongyun.png" },
|
| 184 |
-
{ "name": "Сит
|
| 185 |
{ "name": "Клоринда", "icon": "https://game-cdn.appsample.com/gim/items/clorinde.png" },
|
| 186 |
{ "name": "Коллеи", "icon": "https://game-cdn.appsample.com/gim/items/collei.png" },
|
| 187 |
{ "name": "Сайно", "icon": "https://game-cdn.appsample.com/gim/items/cyno.png" },
|
|
@@ -216,7 +286,7 @@
|
|
| 216 |
{ "name": "Сара", "icon": "https://game-cdn.appsample.com/gim/items/kujou_sara.png" },
|
| 217 |
{ "name": "Шинобу", "icon": "https://game-cdn.appsample.com/gim/items/kuki_shinobu.png" },
|
| 218 |
{ "name": "Лань Янь", "icon": "https://game-cdn.appsample.com/gim/items/lan_yan.png" },
|
| 219 |
-
{ "name": "Л
|
| 220 |
{ "name": "Лиза", "icon": "https://game-cdn.appsample.com/gim/items/lisa.png" },
|
| 221 |
{ "name": "Линнет", "icon": "https://game-cdn.appsample.com/gim/items/lynette.png" },
|
| 222 |
{ "name": "Лини", "icon": "https://game-cdn.appsample.com/gim/items/lyney.png" },
|
|
@@ -232,15 +302,15 @@
|
|
| 232 |
{ "name": "Ноэлль", "icon": "https://game-cdn.appsample.com/gim/items/noelle.png" },
|
| 233 |
{ "name": "Оророн", "icon": "https://game-cdn.appsample.com/gim/items/ororon.png" },
|
| 234 |
{ "name": "Ци Ци", "icon": "https://game-cdn.appsample.com/gim/items/qiqi.png" },
|
| 235 |
-
{ "name": "Райдэн
|
| 236 |
{ "name": "Рэйзор", "icon": "https://game-cdn.appsample.com/gim/items/razor.png" },
|
| 237 |
-
{ "name": "Роза
|
| 238 |
-
{ "name": "
|
| 239 |
{ "name": "Саю", "icon": "https://game-cdn.appsample.com/gim/items/sayu.png" },
|
| 240 |
-
{ "name": "Сетус
|
| 241 |
{ "name": "Шэнь Хэ", "icon": "https://game-cdn.appsample.com/gim/items/shenhe.png" },
|
| 242 |
-
{ "name": "
|
| 243 |
-
{ "name": "Си
|
| 244 |
{ "name": "Сахароза", "icon": "https://game-cdn.appsample.com/gim/items/sucrose.png" },
|
| 245 |
{ "name": "Тарталья", "icon": "https://game-cdn.appsample.com/gim/items/tartaglia.png" },
|
| 246 |
{ "name": "Тома", "icon": "https://game-cdn.appsample.com/gim/items/thoma.png" },
|
|
@@ -248,11 +318,11 @@
|
|
| 248 |
{ "name": "Вареса", "icon": "https://game-cdn.appsample.com/gim/items/varesa.png" },
|
| 249 |
{ "name": "Венти", "icon": "https://game-cdn.appsample.com/gim/items/venti.png" },
|
| 250 |
{ "name": "Странник", "icon": "https://game-cdn.appsample.com/gim/items/wanderer.png" },
|
| 251 |
-
{ "name": "Р
|
| 252 |
{ "name": "Сяо", "icon": "https://game-cdn.appsample.com/gim/items/xiao.png" },
|
| 253 |
{ "name": "Сян Линь", "icon": "https://game-cdn.appsample.com/gim/items/xiangling.png" },
|
| 254 |
{ "name": "Сянь Юнь", "icon": "https://game-cdn.appsample.com/gim/items/xianyun.png" },
|
| 255 |
-
{ "name": "
|
| 256 |
{ "name": "Син Цю", "icon": "https://game-cdn.appsample.com/gim/items/xingqiu.png" },
|
| 257 |
{ "name": "Синь Янь", "icon": "https://game-cdn.appsample.com/gim/items/xinyan.png" },
|
| 258 |
{ "name": "Яэ Мико", "icon": "https://game-cdn.appsample.com/gim/items/yae_miko.png" },
|
|
@@ -261,7 +331,7 @@
|
|
| 261 |
{ "name": "Е Лань", "icon": "https://game-cdn.appsample.com/gim/items/yelan.png" },
|
| 262 |
{ "name": "Ёимия", "icon": "https://game-cdn.appsample.com/gim/items/yoimiya.png" },
|
| 263 |
{ "name": "Юнь Цзинь", "icon": "https://game-cdn.appsample.com/gim/items/yun_jin.png" },
|
| 264 |
-
{ "name": "
|
| 265 |
{ "name": "Чжун Ли", "icon": "https://game-cdn.appsample.com/gim/items/zhongli.png" }
|
| 266 |
];
|
| 267 |
|
|
@@ -287,7 +357,9 @@
|
|
| 287 |
// Состояние приложения
|
| 288 |
let allCharacters = [...LOCAL_CHARACTERS];
|
| 289 |
let historyTeams = JSON.parse(localStorage.getItem(`${currentUserUID}_genshinTeamsHistory`)) || [];
|
|
|
|
| 290 |
let isSpinning = false;
|
|
|
|
| 291 |
|
| 292 |
// Элементы DOM
|
| 293 |
const spinButton = document.getElementById('spin-button');
|
|
@@ -307,6 +379,14 @@
|
|
| 307 |
const confirmModalMessage = document.getElementById('confirm-modal-message');
|
| 308 |
const confirmModalCancel = document.getElementById('confirm-modal-cancel');
|
| 309 |
const confirmModalOk = document.getElementById('confirm-modal-ok');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
|
| 311 |
// Инициализация темы
|
| 312 |
function initTheme() {
|
|
@@ -331,53 +411,10 @@
|
|
| 331 |
settingsPanel.classList.toggle('open');
|
| 332 |
}
|
| 333 |
|
| 334 |
-
// Попытка загрузки персонажей из API
|
| 335 |
-
async function tryFetchCharacters() {
|
| 336 |
-
try {
|
| 337 |
-
// Используем setTimeout для имитации задержки загрузки, чтобы показать лоадер
|
| 338 |
-
setTimeout(async () => {
|
| 339 |
-
rouletteLoader.classList.remove('hidden');
|
| 340 |
-
roulettePlaceholder.classList.add('hidden');
|
| 341 |
-
spinButton.disabled = true;
|
| 342 |
-
|
| 343 |
-
// Имитация загрузки из API (реальный CORS запрос без прокси не пройдет)
|
| 344 |
-
// В реальном приложении этот fetch будет работать, если API разрешит CORS
|
| 345 |
-
// const response = await fetch('https://akasha.cv/api/filters/characters/?type=profile');
|
| 346 |
-
// if (!response.ok) throw new Error('Ошибка загрузки данных');
|
| 347 |
-
// const data = await response.json();
|
| 348 |
-
// if (data && data.data && data.data[0] && data.data[0].options) {
|
| 349 |
-
// allCharacters = data.data[0].options.map(char => ({
|
| 350 |
-
// name: char.name,
|
| 351 |
-
// icon: char.icon
|
| 352 |
-
// }));
|
| 353 |
-
// console.log('Загружено персонажей из API:', allCharacters.length);
|
| 354 |
-
// } else {
|
| 355 |
-
// throw new Error('Неверный формат данных');
|
| 356 |
-
// }
|
| 357 |
-
|
| 358 |
-
// В текущей реализации всегда используем локальные данные, т.к. CORS не позволяет
|
| 359 |
-
allCharacters = [...LOCAL_CHARACTERS];
|
| 360 |
-
console.log('Используем локальные данные персонажей:', allCharacters.length);
|
| 361 |
-
|
| 362 |
-
rouletteLoader.classList.add('hidden');
|
| 363 |
-
spinButton.disabled = false;
|
| 364 |
-
roulettePlaceholder.classList.remove('hidden'); // Показываем плейсхолдер после загрузки
|
| 365 |
-
}, 500); // Небольшая задержка для имитации загрузки
|
| 366 |
-
} catch (error) {
|
| 367 |
-
console.error('Ошибка загрузки персонажей:', error);
|
| 368 |
-
allCharacters = [...LOCAL_CHARACTERS];
|
| 369 |
-
console.log('Используем локальные данные персонажей (из-за ошибки загрузки):', allCharacters.length);
|
| 370 |
-
rouletteLoader.classList.add('hidden');
|
| 371 |
-
spinButton.disabled = false;
|
| 372 |
-
roulettePlaceholder.classList.remove('hidden'); // Показываем плейсхолдер после загрузки
|
| 373 |
-
}
|
| 374 |
-
}
|
| 375 |
-
|
| 376 |
// Создание карточки персонажа
|
| 377 |
-
function createCharacterCard(character
|
| 378 |
const card = document.createElement('div');
|
| 379 |
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]`;
|
| 380 |
-
if (isRoulette) card.classList.add('roulette-char-card');
|
| 381 |
|
| 382 |
card.innerHTML = `
|
| 383 |
<img src="${character.icon}" alt="${character.name}"
|
|
@@ -388,40 +425,140 @@
|
|
| 388 |
return card;
|
| 389 |
}
|
| 390 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 391 |
// Отображение результата рулетки с плавной анимацией
|
| 392 |
-
function renderRouletteResult(team) {
|
| 393 |
rouletteResult.innerHTML = '';
|
| 394 |
roulettePlaceholder.classList.add('hidden');
|
| 395 |
|
| 396 |
-
|
| 397 |
-
const
|
|
|
|
|
|
|
|
|
|
| 398 |
rouletteResult.appendChild(card);
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
}
|
| 405 |
}
|
| 406 |
|
| 407 |
-
// Отображение истории
|
| 408 |
function renderHistory() {
|
| 409 |
historyContent.innerHTML = '';
|
| 410 |
|
| 411 |
if (historyTeams.length === 0) {
|
| 412 |
historyPlaceholder.classList.remove('hidden');
|
| 413 |
clearHistoryButton.classList.add('hidden');
|
|
|
|
| 414 |
} else {
|
| 415 |
historyPlaceholder.classList.add('hidden');
|
| 416 |
clearHistoryButton.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 417 |
|
| 418 |
-
|
| 419 |
const teamElement = document.createElement('div');
|
| 420 |
teamElement.className = 'bg-[#FDF8F0] dark:bg-gray-800 rounded-xl p-4 shadow-sm border border-[#EFEAE4] dark:border-gray-600';
|
| 421 |
|
| 422 |
const title = document.createElement('h3');
|
| 423 |
title.className = 'text-lg font-semibold mb-3 dark:text-white';
|
| 424 |
-
|
|
|
|
| 425 |
teamElement.appendChild(title);
|
| 426 |
|
| 427 |
const charsContainer = document.createElement('div');
|
|
@@ -437,16 +574,17 @@
|
|
| 437 |
}
|
| 438 |
}
|
| 439 |
|
| 440 |
-
// Сохранение отряда в историю
|
| 441 |
-
function saveTeamToHistory(team) {
|
| 442 |
historyTeams.unshift({
|
| 443 |
date: new Date().toISOString(),
|
| 444 |
-
characters: team
|
|
|
|
| 445 |
});
|
| 446 |
|
| 447 |
-
// Ограничиваем историю последними
|
| 448 |
-
if (historyTeams.length >
|
| 449 |
-
historyTeams = historyTeams.slice(0,
|
| 450 |
}
|
| 451 |
|
| 452 |
localStorage.setItem(`${currentUserUID}_genshinTeamsHistory`, JSON.stringify(historyTeams));
|
|
@@ -463,63 +601,70 @@
|
|
| 463 |
|
| 464 |
// Запуск рулетки
|
| 465 |
async function spinRoulette() {
|
| 466 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
|
| 468 |
isSpinning = true;
|
| 469 |
spinButton.disabled = true;
|
| 470 |
spinButton.textContent = 'Крутим...';
|
| 471 |
-
rouletteResult.innerHTML = '';
|
| 472 |
roulettePlaceholder.classList.add('hidden');
|
| 473 |
rouletteLoader.classList.remove('hidden');
|
| 474 |
-
|
| 475 |
-
|
|
|
|
| 476 |
const teamSize = parseInt(teamSizeSelect.value);
|
| 477 |
-
let
|
| 478 |
const selectedTeam = [];
|
| 479 |
|
| 480 |
-
let
|
| 481 |
switch (animationSpeedSelect.value) {
|
| 482 |
-
case 'fast':
|
| 483 |
-
case 'slow':
|
| 484 |
-
default:
|
| 485 |
}
|
| 486 |
|
| 487 |
-
const
|
| 488 |
-
const totalSpinTimePerChar = spinDurationBase / teamSize; // Общее время "кручения" для каждого символа
|
| 489 |
|
| 490 |
for (let i = 0; i < teamSize; i++) {
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
const tempCard = createCharacterCard(
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
|
|
|
|
|
|
| 523 |
}
|
| 524 |
|
| 525 |
// Завершение рулетки
|
|
@@ -527,10 +672,9 @@
|
|
| 527 |
spinButton.disabled = false;
|
| 528 |
spinButton.textContent = 'Крутить ещё!';
|
| 529 |
isSpinning = false;
|
| 530 |
-
rouletteResult.classList.remove('roulette-spinning'); // Удаляем класс анимации вращения
|
| 531 |
|
| 532 |
-
// Сохраняем
|
| 533 |
-
|
| 534 |
}
|
| 535 |
|
| 536 |
// Модальное окно подтверждения
|
|
@@ -571,12 +715,35 @@
|
|
| 571 |
themeToggle.addEventListener('click', toggleTheme);
|
| 572 |
settingsToggle.addEventListener('click', toggleSettings);
|
| 573 |
clearHistoryButton.addEventListener('click', () => {
|
| 574 |
-
showConfirmModal(
|
|
|
|
|
|
|
|
|
|
| 575 |
});
|
| 576 |
confirmModal.addEventListener('click', (e) => {
|
| 577 |
if (e.target === confirmModal) hideConfirmModal();
|
| 578 |
});
|
| 579 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 580 |
// Сохранение настроек при изменении
|
| 581 |
teamSizeSelect.addEventListener('change', (e) => {
|
| 582 |
config.teamSize = parseInt(e.target.value);
|
|
@@ -594,7 +761,7 @@
|
|
| 594 |
|
| 595 |
// Инициализация приложения
|
| 596 |
initTheme();
|
| 597 |
-
|
| 598 |
renderHistory();
|
| 599 |
});
|
| 600 |
</script>
|
|
|
|
| 50 |
transition: max-height 0.3s ease-out;
|
| 51 |
}
|
| 52 |
.settings-panel.open {
|
| 53 |
+
max-height: 1000px; /* Увеличено для вместимости содержимого, особенно для "Моих персонажей" */
|
| 54 |
}
|
| 55 |
+
/* Улучшенная анимация для рулетки */
|
| 56 |
+
.roulette-item-spin {
|
| 57 |
+
animation: spin-in-place 0.2s ease-in-out infinite alternate;
|
| 58 |
}
|
| 59 |
+
@keyframes spin-in-place {
|
| 60 |
+
0% { transform: scale(0.9) rotate(-2deg); opacity: 0.7; }
|
| 61 |
+
100% { transform: scale(1.1) rotate(2deg); opacity: 1; }
|
|
|
|
| 62 |
}
|
| 63 |
+
|
| 64 |
+
/* Более объемная и живая анимация выпадения персонажа */
|
| 65 |
+
.roulette-item-final-enter {
|
| 66 |
opacity: 0;
|
| 67 |
+
transform: scale(0.7) translateY(50px) rotateX(-90deg);
|
| 68 |
+
filter: blur(5px);
|
| 69 |
+
box-shadow: 0 0 0 rgba(0,0,0,0);
|
| 70 |
}
|
| 71 |
+
.roulette-item-final-enter-active {
|
| 72 |
+
opacity: 1;
|
| 73 |
+
transform: scale(1) translateY(0) rotateX(0deg);
|
| 74 |
+
filter: blur(0);
|
| 75 |
+
transition: opacity 0.6s cubic-bezier(0.68, -0.55, 0.27, 1.55),
|
| 76 |
+
transform 0.6s cubic-bezier(0.68, -0.55, 0.27, 1.55),
|
| 77 |
+
filter 0.6s ease-out,
|
| 78 |
+
box-shadow 0.6s ease-out;
|
| 79 |
+
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.player-char-card {
|
| 83 |
+
position: relative;
|
| 84 |
+
cursor: pointer;
|
| 85 |
+
border: 2px solid transparent;
|
| 86 |
+
transition: border-color 0.2s;
|
| 87 |
+
}
|
| 88 |
+
.player-char-card.selected {
|
| 89 |
+
border-color: #DCA852;
|
| 90 |
+
}
|
| 91 |
+
.player-char-card .remove-btn {
|
| 92 |
+
position: absolute;
|
| 93 |
+
top: -5px;
|
| 94 |
+
right: -5px;
|
| 95 |
+
background-color: red;
|
| 96 |
+
color: white;
|
| 97 |
+
border-radius: 50%;
|
| 98 |
+
width: 20px;
|
| 99 |
+
height: 20px;
|
| 100 |
+
display: flex;
|
| 101 |
+
justify-content: center;
|
| 102 |
+
align-items: center;
|
| 103 |
+
font-size: 0.8rem;
|
| 104 |
+
font-weight: bold;
|
| 105 |
+
cursor: pointer;
|
| 106 |
+
opacity: 0;
|
| 107 |
+
transition: opacity 0.2s;
|
| 108 |
+
}
|
| 109 |
+
.player-char-card:hover .remove-btn {
|
| 110 |
opacity: 1;
|
|
|
|
|
|
|
| 111 |
}
|
| 112 |
</style>
|
| 113 |
</head>
|
|
|
|
| 127 |
</svg>
|
| 128 |
</button>
|
| 129 |
</div>
|
| 130 |
+
<h1 class="text-4xl md:text-5xl font-bold text-[#4A4A4A] dark:text-[#e2e8f0] mb-2">Random Team</h1>
|
| 131 |
<p class="text-lg text-[#6B6B6B] dark:text-[#a0aec0] max-w-2xl mx-auto">
|
| 132 |
Сгенерируйте случайный отряд из четырех персонажей Genshin Impact!
|
| 133 |
</p>
|
|
|
|
| 136 |
<main>
|
| 137 |
<div id="settings-panel" class="settings-panel bg-[#EFEAE4] dark:bg-gray-700 p-6 rounded-2xl shadow-lg mb-6">
|
| 138 |
<h3 class="text-xl font-bold mb-4 dark:text-white">Настройки рулетки</h3>
|
| 139 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
| 140 |
<div>
|
| 141 |
<label for="team-size" class="block text-sm font-medium mb-2 dark:text-gray-300">Количество персонажей в отряде</label>
|
| 142 |
<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">
|
|
|
|
| 155 |
</select>
|
| 156 |
</div>
|
| 157 |
</div>
|
| 158 |
+
|
| 159 |
+
<h3 class="text-xl font-bold mb-4 dark:text-white">Мои персонажи</h3>
|
| 160 |
+
<p class="text-sm text-[#6B6B6B] dark:text-[#a0aec0] mb-4">
|
| 161 |
+
Выберите персонажей, которыми вы владеете, чтобы рулетка использовала только их. Если ни один не выбран, будут использоваться все персонажи.
|
| 162 |
+
</p>
|
| 163 |
+
<div class="mb-4">
|
| 164 |
+
<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">
|
| 165 |
+
</div>
|
| 166 |
+
<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">
|
| 167 |
+
</div>
|
| 168 |
+
<div class="text-center">
|
| 169 |
+
<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">
|
| 170 |
+
Очистить выбранных персонажей
|
| 171 |
+
</button>
|
| 172 |
+
</div>
|
| 173 |
</div>
|
| 174 |
|
| 175 |
<section id="roulette-section" class="mb-16 bg-[#EFEAE4] dark:bg-gray-700 p-6 rounded-2xl shadow-lg">
|
|
|
|
| 183 |
<p id="roulette-placeholder" class="text-[#888] dark:text-gray-400">Ваш случайный отряд появится здесь.</p>
|
| 184 |
<div id="roulette-loader" class="loader hidden"></div>
|
| 185 |
</div>
|
| 186 |
+
|
| 187 |
+
<div id="save-team-section" class="mt-6 p-4 bg-[#FDF8F0] dark:bg-gray-800 rounded-xl shadow-sm hidden">
|
| 188 |
+
<h3 class="text-lg font-semibold mb-3 dark:text-white">Сохранить этот отряд</h3>
|
| 189 |
+
<div class="flex flex-col sm:flex-row gap-3">
|
| 190 |
+
<input type="text" id="team-name-input" placeholder="Название отряда (необязательно)" class="flex-grow p-2 rounded border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white">
|
| 191 |
+
<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">
|
| 192 |
+
Сохранить
|
| 193 |
+
</button>
|
| 194 |
+
</div>
|
| 195 |
+
</div>
|
| 196 |
+
|
| 197 |
</section>
|
| 198 |
|
| 199 |
<section id="history-section" class="bg-[#EFEAE4] dark:bg-gray-700 p-6 rounded-2xl shadow-lg">
|
| 200 |
<div class="flex justify-between items-center mb-8">
|
| 201 |
<h2 class="text-3xl font-bold text-[#4A4A4A] dark:text-white">История Отрядов</h2>
|
| 202 |
+
<div class="flex gap-2">
|
| 203 |
+
<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">
|
| 204 |
+
Очистить
|
| 205 |
+
</button>
|
| 206 |
+
<button id="view-full-history-button" class="bg-[#DCA852] text-white font-bold py-2 px-4 rounded-lg text-sm 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 hidden">
|
| 207 |
+
Вся история
|
| 208 |
+
</button>
|
| 209 |
+
</div>
|
| 210 |
</div>
|
| 211 |
<div id="history-content" class="flex flex-col gap-6">
|
| 212 |
<p id="history-placeholder" class="text-center text-[#888] dark:text-gray-400">История ваших отрядов будет отображаться здесь.</p>
|
|
|
|
| 224 |
<div id="confirm-modal" class="fixed inset-0 bg-black bg-opacity-70 z-50 flex justify-center items-center hidden p-4">
|
| 225 |
<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">
|
| 226 |
<h3 class="text-2xl font-bold mb-4 text-[#4A4A4A] dark:text-white">Подтверждение</h3>
|
| 227 |
+
<p id="confirm-modal-message" class="text-lg text-[#6B6B6B] dark:text-gray-300 mb-6"></p>
|
| 228 |
<div class="flex justify-around gap-4">
|
| 229 |
<button id="confirm-modal-cancel" class="bg-[#A089D0] text-white font-bold py-2 px-6 rounded-lg hover:bg-[#8e7abf]">Отмена</button>
|
| 230 |
<button id="confirm-modal-ok" class="bg-red-500 text-white font-bold py-2 px-6 rounded-lg hover:bg-red-600">Очистить</button>
|
|
|
|
| 248 |
{ "name": "Беннет", "icon": "https://game-cdn.appsample.com/gim/items/bennett.png" },
|
| 249 |
{ "name": "Кандакия", "icon": "https://game-cdn.appsample.com/gim/items/candace.png" },
|
| 250 |
{ "name": "Шарлотта", "icon": "https://game-cdn.appsample.com/gim/items/charlotte.png" },
|
| 251 |
+
{ "name": "Часка", "icon": "https://game-cdn.appsample.com/gim/items/chasca.png" },
|
| 252 |
+
{ "name": "Тиори", "icon": "https://game-cdn.appsample.com/gim/items/chiori.png" },
|
| 253 |
{ "name": "Чун Юнь", "icon": "https://game-cdn.appsample.com/gim/items/chongyun.png" },
|
| 254 |
+
{ "name": "Ситлали", "icon": "https://game-cdn.appsample.com/gim/items/citlali.png" },
|
| 255 |
{ "name": "Клоринда", "icon": "https://game-cdn.appsample.com/gim/items/clorinde.png" },
|
| 256 |
{ "name": "Коллеи", "icon": "https://game-cdn.appsample.com/gim/items/collei.png" },
|
| 257 |
{ "name": "Сайно", "icon": "https://game-cdn.appsample.com/gim/items/cyno.png" },
|
|
|
|
| 286 |
{ "name": "Сара", "icon": "https://game-cdn.appsample.com/gim/items/kujou_sara.png" },
|
| 287 |
{ "name": "Шинобу", "icon": "https://game-cdn.appsample.com/gim/items/kuki_shinobu.png" },
|
| 288 |
{ "name": "Лань Янь", "icon": "https://game-cdn.appsample.com/gim/items/lan_yan.png" },
|
| 289 |
+
{ "name": "Лейла", "icon": "https://game-cdn.appsample.com/gim/items/layla.png" },
|
| 290 |
{ "name": "Лиза", "icon": "https://game-cdn.appsample.com/gim/items/lisa.png" },
|
| 291 |
{ "name": "Линнет", "icon": "https://game-cdn.appsample.com/gim/items/lynette.png" },
|
| 292 |
{ "name": "Лини", "icon": "https://game-cdn.appsample.com/gim/items/lyney.png" },
|
|
|
|
| 302 |
{ "name": "Ноэлль", "icon": "https://game-cdn.appsample.com/gim/items/noelle.png" },
|
| 303 |
{ "name": "Оророн", "icon": "https://game-cdn.appsample.com/gim/items/ororon.png" },
|
| 304 |
{ "name": "Ци Ци", "icon": "https://game-cdn.appsample.com/gim/items/qiqi.png" },
|
| 305 |
+
{ "name": "Райдэн", "icon": "https://game-cdn.appsample.com/gim/items/raiden_shogun.png" },
|
| 306 |
{ "name": "Рэйзор", "icon": "https://game-cdn.appsample.com/gim/items/razor.png" },
|
| 307 |
+
{ "name": "Розалья", "icon": "https://game-cdn.appsample.com/gim/items/rosaria.png" },
|
| 308 |
+
{ "name": "Кокоми", "icon": "https://game-cdn.appsample.com/gim/items/sangonomiya_kokomi.png" },
|
| 309 |
{ "name": "Саю", "icon": "https://game-cdn.appsample.com/gim/items/sayu.png" },
|
| 310 |
+
{ "name": "Сетус", "icon": "https://game-cdn.appsample.com/gim/items/sethos.png" },
|
| 311 |
{ "name": "Шэнь Хэ", "icon": "https://game-cdn.appsample.com/gim/items/shenhe.png" },
|
| 312 |
+
{ "name": "Хэйдзо", "icon": "https://game-cdn.appsample.com/gim/items/shikanoin_heizou.png" },
|
| 313 |
+
{ "name": "Сиджвин", "icon": "https://game-cdn.appsample.com/gim/items/sigewinne.png" },
|
| 314 |
{ "name": "Сахароза", "icon": "https://game-cdn.appsample.com/gim/items/sucrose.png" },
|
| 315 |
{ "name": "Тарталья", "icon": "https://game-cdn.appsample.com/gim/items/tartaglia.png" },
|
| 316 |
{ "name": "Тома", "icon": "https://game-cdn.appsample.com/gim/items/thoma.png" },
|
|
|
|
| 318 |
{ "name": "Вареса", "icon": "https://game-cdn.appsample.com/gim/items/varesa.png" },
|
| 319 |
{ "name": "Венти", "icon": "https://game-cdn.appsample.com/gim/items/venti.png" },
|
| 320 |
{ "name": "Странник", "icon": "https://game-cdn.appsample.com/gim/items/wanderer.png" },
|
| 321 |
+
{ "name": "Ризли", "icon": "https://game-cdn.appsample.com/gim/items/wriothesley.png" },
|
| 322 |
{ "name": "Сяо", "icon": "https://game-cdn.appsample.com/gim/items/xiao.png" },
|
| 323 |
{ "name": "Сян Линь", "icon": "https://game-cdn.appsample.com/gim/items/xiangling.png" },
|
| 324 |
{ "name": "Сянь Юнь", "icon": "https://game-cdn.appsample.com/gim/items/xianyun.png" },
|
| 325 |
+
{ "name": "Шилонен", "icon": "https://game-cdn.appsample.com/gim/items/xilonen.png" },
|
| 326 |
{ "name": "Син Цю", "icon": "https://game-cdn.appsample.com/gim/items/xingqiu.png" },
|
| 327 |
{ "name": "Синь Янь", "icon": "https://game-cdn.appsample.com/gim/items/xinyan.png" },
|
| 328 |
{ "name": "Яэ Мико", "icon": "https://game-cdn.appsample.com/gim/items/yae_miko.png" },
|
|
|
|
| 331 |
{ "name": "Е Лань", "icon": "https://game-cdn.appsample.com/gim/items/yelan.png" },
|
| 332 |
{ "name": "Ёимия", "icon": "https://game-cdn.appsample.com/gim/items/yoimiya.png" },
|
| 333 |
{ "name": "Юнь Цзинь", "icon": "https://game-cdn.appsample.com/gim/items/yun_jin.png" },
|
| 334 |
+
{ "name": "Мидзуки", "icon": "https://game-cdn.appsample.com/gim/items/yumemizuki_mizuki.png" },
|
| 335 |
{ "name": "Чжун Ли", "icon": "https://game-cdn.appsample.com/gim/items/zhongli.png" }
|
| 336 |
];
|
| 337 |
|
|
|
|
| 357 |
// Состояние приложения
|
| 358 |
let allCharacters = [...LOCAL_CHARACTERS];
|
| 359 |
let historyTeams = JSON.parse(localStorage.getItem(`${currentUserUID}_genshinTeamsHistory`)) || [];
|
| 360 |
+
let playerCharacters = JSON.parse(localStorage.getItem(`${currentUserUID}_playerCharacters`)) || [];
|
| 361 |
let isSpinning = false;
|
| 362 |
+
let currentGeneratedTeam = []; // Для сохранения сгенерированного отряда перед именованием
|
| 363 |
|
| 364 |
// Элементы DOM
|
| 365 |
const spinButton = document.getElementById('spin-button');
|
|
|
|
| 379 |
const confirmModalMessage = document.getElementById('confirm-modal-message');
|
| 380 |
const confirmModalCancel = document.getElementById('confirm-modal-cancel');
|
| 381 |
const confirmModalOk = document.getElementById('confirm-modal-ok');
|
| 382 |
+
const playerCharactersSelection = document.getElementById('player-characters-selection');
|
| 383 |
+
const characterSearchInput = document.getElementById('character-search');
|
| 384 |
+
const clearPlayerCharactersButton = document.getElementById('clear-player-characters');
|
| 385 |
+
const saveTeamSection = document.getElementById('save-team-section');
|
| 386 |
+
const teamNameInput = document.getElementById('team-name-input');
|
| 387 |
+
const saveTeamButton = document.getElementById('save-team-button');
|
| 388 |
+
const viewFullHistoryButton = document.getElementById('view-full-history-button');
|
| 389 |
+
|
| 390 |
|
| 391 |
// Инициализация темы
|
| 392 |
function initTheme() {
|
|
|
|
| 411 |
settingsPanel.classList.toggle('open');
|
| 412 |
}
|
| 413 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
// Создание карточки персонажа
|
| 415 |
+
function createCharacterCard(character) {
|
| 416 |
const card = document.createElement('div');
|
| 417 |
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]`;
|
|
|
|
| 418 |
|
| 419 |
card.innerHTML = `
|
| 420 |
<img src="${character.icon}" alt="${character.name}"
|
|
|
|
| 425 |
return card;
|
| 426 |
}
|
| 427 |
|
| 428 |
+
// Создание карточки персонажа для выбора (в секции "Мои персонажи")
|
| 429 |
+
function createPlayerCharacterSelectionCard(character) {
|
| 430 |
+
const card = document.createElement('div');
|
| 431 |
+
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`;
|
| 432 |
+
card.setAttribute('data-name', character.name);
|
| 433 |
+
|
| 434 |
+
const isSelected = playerCharacters.some(c => c.name === character.name);
|
| 435 |
+
if (isSelected) {
|
| 436 |
+
card.classList.add('selected');
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
card.innerHTML = `
|
| 440 |
+
<img src="${character.icon}" alt="${character.name}"
|
| 441 |
+
class="w-12 h-12 rounded-full mb-1 object-cover border-2 border-transparent"
|
| 442 |
+
onerror="this.onerror=null;this.src='https://placehold.co/48x48/EFEAE4/4A4A4A?text=N/A'">
|
| 443 |
+
<p class="font-medium text-xs truncate w-full dark:text-white">${character.name}</p>
|
| 444 |
+
${isSelected ? '<button class="remove-btn" title="Удалить">×</button>' : ''}
|
| 445 |
+
`;
|
| 446 |
+
|
| 447 |
+
const img = card.querySelector('img');
|
| 448 |
+
if (isSelected) {
|
| 449 |
+
img.classList.remove('border-transparent');
|
| 450 |
+
img.classList.add('border-[#DCA852]');
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
card.addEventListener('click', (event) => {
|
| 454 |
+
if (event.target.classList.contains('remove-btn')) {
|
| 455 |
+
removePlayerCharacter(character.name);
|
| 456 |
+
} else {
|
| 457 |
+
togglePlayerCharacter(character);
|
| 458 |
+
}
|
| 459 |
+
});
|
| 460 |
+
|
| 461 |
+
return card;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
// Рендеринг списка персонажей для выбора
|
| 465 |
+
function renderPlayerCharactersSelection(filter = '') {
|
| 466 |
+
playerCharactersSelection.innerHTML = '';
|
| 467 |
+
const filteredCharacters = allCharacters.filter(char =>
|
| 468 |
+
char.name.toLowerCase().includes(filter.toLowerCase())
|
| 469 |
+
);
|
| 470 |
+
|
| 471 |
+
filteredCharacters.forEach(char => {
|
| 472 |
+
playerCharactersSelection.appendChild(createPlayerCharacterSelectionCard(char));
|
| 473 |
+
});
|
| 474 |
+
updateClearPlayerCharactersButtonVisibility();
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
// Добавление/удаление персонажа из списка игрока
|
| 478 |
+
function togglePlayerCharacter(character) {
|
| 479 |
+
const index = playerCharacters.findIndex(c => c.name === character.name);
|
| 480 |
+
if (index > -1) {
|
| 481 |
+
playerCharacters.splice(index, 1);
|
| 482 |
+
} else {
|
| 483 |
+
playerCharacters.push(character);
|
| 484 |
+
}
|
| 485 |
+
localStorage.setItem(`${currentUserUID}_playerCharacters`, JSON.stringify(playerCharacters));
|
| 486 |
+
renderPlayerCharactersSelection(characterSearchInput.value); // Перерисовываем с текущим фильтром
|
| 487 |
+
updateClearPlayerCharactersButtonVisibility();
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
// Удаление персонажа из списка игрока по имени
|
| 491 |
+
function removePlayerCharacter(characterName) {
|
| 492 |
+
playerCharacters = playerCharacters.filter(c => c.name !== characterName);
|
| 493 |
+
localStorage.setItem(`${currentUserUID}_playerCharacters`, JSON.stringify(playerCharacters));
|
| 494 |
+
renderPlayerCharactersSelection(characterSearchInput.value);
|
| 495 |
+
updateClearPlayerCharactersButtonVisibility();
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
// Очистка всех персонажей игрока
|
| 499 |
+
function clearPlayerCharacters() {
|
| 500 |
+
playerCharacters = [];
|
| 501 |
+
localStorage.removeItem(`${currentUserUID}_playerCharacters`);
|
| 502 |
+
renderPlayerCharactersSelection('');
|
| 503 |
+
hideConfirmModal();
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
// Обновление видимости кнопки "Очистить выбранных персонажей"
|
| 507 |
+
function updateClearPlayerCharactersButtonVisibility() {
|
| 508 |
+
if (playerCharacters.length > 0) {
|
| 509 |
+
clearPlayerCharactersButton.classList.remove('hidden');
|
| 510 |
+
} else {
|
| 511 |
+
clearPlayerCharactersButton.classList.add('hidden');
|
| 512 |
+
}
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
// Отображение результата рулетки с плавной анимацией
|
| 516 |
+
async function renderRouletteResult(team) {
|
| 517 |
rouletteResult.innerHTML = '';
|
| 518 |
roulettePlaceholder.classList.add('hidden');
|
| 519 |
|
| 520 |
+
for (let i = 0; i < team.length; i++) {
|
| 521 |
+
const char = team[i];
|
| 522 |
+
const card = createCharacterCard(char);
|
| 523 |
+
card.classList.add('roulette-item-final-enter');
|
| 524 |
+
|
| 525 |
rouletteResult.appendChild(card);
|
| 526 |
+
|
| 527 |
+
await new Promise(resolve => setTimeout(resolve, 50));
|
| 528 |
+
card.classList.add('roulette-item-final-enter-active');
|
| 529 |
+
|
| 530 |
+
await new Promise(resolve => setTimeout(resolve, 200));
|
| 531 |
+
}
|
| 532 |
}
|
| 533 |
|
| 534 |
+
// Отображение истории (только последние 3)
|
| 535 |
function renderHistory() {
|
| 536 |
historyContent.innerHTML = '';
|
| 537 |
|
| 538 |
if (historyTeams.length === 0) {
|
| 539 |
historyPlaceholder.classList.remove('hidden');
|
| 540 |
clearHistoryButton.classList.add('hidden');
|
| 541 |
+
viewFullHistoryButton.classList.add('hidden');
|
| 542 |
} else {
|
| 543 |
historyPlaceholder.classList.add('hidden');
|
| 544 |
clearHistoryButton.classList.remove('hidden');
|
| 545 |
+
if (historyTeams.length > 3) { // Показываем кнопку "Вся история", если команд больше 3
|
| 546 |
+
viewFullHistoryButton.classList.remove('hidden');
|
| 547 |
+
} else {
|
| 548 |
+
viewFullHistoryButton.classList.add('hidden');
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
// Показываем только последние 3 отряда на главной странице
|
| 552 |
+
const teamsToShow = historyTeams.slice(0, 3);
|
| 553 |
|
| 554 |
+
teamsToShow.forEach((team, index) => {
|
| 555 |
const teamElement = document.createElement('div');
|
| 556 |
teamElement.className = 'bg-[#FDF8F0] dark:bg-gray-800 rounded-xl p-4 shadow-sm border border-[#EFEAE4] dark:border-gray-600';
|
| 557 |
|
| 558 |
const title = document.createElement('h3');
|
| 559 |
title.className = 'text-lg font-semibold mb-3 dark:text-white';
|
| 560 |
+
// Испол��зуем сохраненное имя, если оно есть, иначе генерируем
|
| 561 |
+
title.textContent = team.name ? team.name : `Отряд ${historyTeams.length - index} (${new Date(team.date).toLocaleString()})`;
|
| 562 |
teamElement.appendChild(title);
|
| 563 |
|
| 564 |
const charsContainer = document.createElement('div');
|
|
|
|
| 574 |
}
|
| 575 |
}
|
| 576 |
|
| 577 |
+
// Сохранение отряда в историю с возможностью добавления имени
|
| 578 |
+
function saveTeamToHistory(team, teamName = '') {
|
| 579 |
historyTeams.unshift({
|
| 580 |
date: new Date().toISOString(),
|
| 581 |
+
characters: team,
|
| 582 |
+
name: teamName // Добавляем имя
|
| 583 |
});
|
| 584 |
|
| 585 |
+
// Ограничиваем историю последними 20 отрядами для общей истории (чтобы не было слишком много данных, но достаточно для просмотра)
|
| 586 |
+
if (historyTeams.length > 20) {
|
| 587 |
+
historyTeams = historyTeams.slice(0, 20);
|
| 588 |
}
|
| 589 |
|
| 590 |
localStorage.setItem(`${currentUserUID}_genshinTeamsHistory`, JSON.stringify(historyTeams));
|
|
|
|
| 601 |
|
| 602 |
// Запуск рулетки
|
| 603 |
async function spinRoulette() {
|
| 604 |
+
const availableCharsForSpin = playerCharacters.length > 0 ? playerCharacters : allCharacters;
|
| 605 |
+
|
| 606 |
+
if (isSpinning || availableCharsForSpin.length < config.teamSize) {
|
| 607 |
+
alert(`Недостаточно персонажей для формирования отряда из ${config.teamSize} человек. Доступно: ${availableCharsForSpin.length}`);
|
| 608 |
+
return;
|
| 609 |
+
}
|
| 610 |
|
| 611 |
isSpinning = true;
|
| 612 |
spinButton.disabled = true;
|
| 613 |
spinButton.textContent = 'Крутим...';
|
| 614 |
+
rouletteResult.innerHTML = '';
|
| 615 |
roulettePlaceholder.classList.add('hidden');
|
| 616 |
rouletteLoader.classList.remove('hidden');
|
| 617 |
+
saveTeamSection.classList.add('hidden'); // Скрываем секцию сохранения при новом вращении
|
| 618 |
+
teamNameInput.value = ''; // Очищаем поле ввода имени
|
| 619 |
+
|
| 620 |
const teamSize = parseInt(teamSizeSelect.value);
|
| 621 |
+
let charsPool = [...availableCharsForSpin];
|
| 622 |
const selectedTeam = [];
|
| 623 |
|
| 624 |
+
let spinDurationMs;
|
| 625 |
switch (animationSpeedSelect.value) {
|
| 626 |
+
case 'fast': spinDurationMs = 1500; break;
|
| 627 |
+
case 'slow': spinDurationMs = 4000; break;
|
| 628 |
+
default: spinDurationMs = 2500;
|
| 629 |
}
|
| 630 |
|
| 631 |
+
const individualSpinTime = spinDurationMs / teamSize;
|
|
|
|
| 632 |
|
| 633 |
for (let i = 0; i < teamSize; i++) {
|
| 634 |
+
const tempRouletteDiv = document.createElement('div');
|
| 635 |
+
tempRouletteDiv.className = 'flex flex-col items-center flex-shrink-0 w-[100px] sm:w-[120px] md:w-[130px] lg:w-[140px]';
|
| 636 |
+
rouletteResult.appendChild(tempRouletteDiv);
|
| 637 |
+
|
| 638 |
+
let startTime = Date.now();
|
| 639 |
+
let animationFrame;
|
| 640 |
+
|
| 641 |
+
const animateSpin = () => {
|
| 642 |
+
const elapsedTime = Date.now() - startTime;
|
| 643 |
+
if (elapsedTime < individualSpinTime) {
|
| 644 |
+
const randomChar = charsPool[Math.floor(Math.random() * charsPool.length)];
|
| 645 |
+
tempRouletteDiv.innerHTML = '';
|
| 646 |
+
const tempCard = createCharacterCard(randomChar);
|
| 647 |
+
tempCard.classList.add('roulette-item-spin');
|
| 648 |
+
tempRouletteDiv.appendChild(tempCard);
|
| 649 |
+
animationFrame = requestAnimationFrame(animateSpin);
|
| 650 |
+
} else {
|
| 651 |
+
cancelAnimationFrame(animationFrame);
|
| 652 |
+
const selectedIndex = Math.floor(Math.random() * charsPool.length);
|
| 653 |
+
const selectedChar = charsPool.splice(selectedIndex, 1)[0];
|
| 654 |
+
selectedTeam.push(selectedChar);
|
| 655 |
+
|
| 656 |
+
tempRouletteDiv.innerHTML = '';
|
| 657 |
+
const finalCard = createCharacterCard(selectedChar);
|
| 658 |
+
finalCard.classList.add('roulette-item-final-enter');
|
| 659 |
+
tempRouletteDiv.appendChild(finalCard);
|
| 660 |
+
setTimeout(() => {
|
| 661 |
+
finalCard.classList.add('roulette-item-final-enter-active');
|
| 662 |
+
}, 10);
|
| 663 |
+
}
|
| 664 |
+
};
|
| 665 |
+
animateSpin();
|
| 666 |
+
|
| 667 |
+
await new Promise(resolve => setTimeout(resolve, individualSpinTime + 100));
|
| 668 |
}
|
| 669 |
|
| 670 |
// Завершение рулетки
|
|
|
|
| 672 |
spinButton.disabled = false;
|
| 673 |
spinButton.textContent = 'Крутить ещё!';
|
| 674 |
isSpinning = false;
|
|
|
|
| 675 |
|
| 676 |
+
currentGeneratedTeam = selectedTeam; // Сохраняем сгенерированный отряд
|
| 677 |
+
saveTeamSection.classList.remove('hidden'); // Показываем секцию сохранения
|
| 678 |
}
|
| 679 |
|
| 680 |
// Модальное окно подтверждения
|
|
|
|
| 715 |
themeToggle.addEventListener('click', toggleTheme);
|
| 716 |
settingsToggle.addEventListener('click', toggleSettings);
|
| 717 |
clearHistoryButton.addEventListener('click', () => {
|
| 718 |
+
showConfirmModal(`UID:${currentUserUID} \r\rВы уверены, что хотите очистить всю историю отрядов?`, clearHistory);
|
| 719 |
+
});
|
| 720 |
+
clearPlayerCharactersButton.addEventListener('click', () => {
|
| 721 |
+
showConfirmModal('Вы уверены, что хотите очистить список ваших персонажей?', clearPlayerCharacters);
|
| 722 |
});
|
| 723 |
confirmModal.addEventListener('click', (e) => {
|
| 724 |
if (e.target === confirmModal) hideConfirmModal();
|
| 725 |
});
|
| 726 |
|
| 727 |
+
characterSearchInput.addEventListener('input', (e) => {
|
| 728 |
+
renderPlayerCharactersSelection(e.target.value);
|
| 729 |
+
});
|
| 730 |
+
|
| 731 |
+
saveTeamButton.addEventListener('click', () => {
|
| 732 |
+
const teamName = teamNameInput.value.trim();
|
| 733 |
+
if (currentGeneratedTeam.length > 0) {
|
| 734 |
+
saveTeamToHistory(currentGeneratedTeam, teamName);
|
| 735 |
+
saveTeamSection.classList.add('hidden'); // Скрываем секцию после сохранения
|
| 736 |
+
teamNameInput.value = ''; // Очищаем поле
|
| 737 |
+
currentGeneratedTeam = []; // Сбрасываем текущий отряд
|
| 738 |
+
}
|
| 739 |
+
});
|
| 740 |
+
|
| 741 |
+
viewFullHistoryButton.addEventListener('click', () => {
|
| 742 |
+
// Открываем новую вкладку для полной истории
|
| 743 |
+
showConfirmModal(`${currentUserUID} ${history-content}`);
|
| 744 |
+
});
|
| 745 |
+
|
| 746 |
+
|
| 747 |
// Сохранение настроек при изменении
|
| 748 |
teamSizeSelect.addEventListener('change', (e) => {
|
| 749 |
config.teamSize = parseInt(e.target.value);
|
|
|
|
| 761 |
|
| 762 |
// Инициализация приложения
|
| 763 |
initTheme();
|
| 764 |
+
renderPlayerCharactersSelection();
|
| 765 |
renderHistory();
|
| 766 |
});
|
| 767 |
</script>
|