| <!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="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <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@300;400;500;600;700;800;900&display=swap" rel="stylesheet"> |
| <style> |
| :root { |
| --primary: #667eea; |
| --secondary: #764ba2; |
| --accent: #f093fb; |
| --success: #10b981; |
| --warning: #f59e0b; |
| --danger: #ef4444; |
| --dark: #1e293b; |
| --light: #f8fafc; |
| } |
| |
| body { |
| font-family: 'Inter', sans-serif; |
| transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); |
| background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); |
| min-height: 100vh; |
| color: #e2e8f0; |
| } |
| |
| .glass { |
| background: rgba(30, 41, 59, 0.85); |
| backdrop-filter: blur(12px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); |
| } |
| |
| .card-gradient { |
| background: linear-gradient(145deg, #1e293b, #0f172a); |
| border-radius: 20px; |
| box-shadow: 10px 10px 30px #0a0f1a, -10px -10px 30px #2a3a52; |
| } |
| |
| .gradient-primary { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| } |
| |
| .gradient-secondary { |
| background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
| } |
| |
| .gradient-accent { |
| background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); |
| } |
| |
| .gradient-gold { |
| background: linear-gradient(135deg, #f6d365 0%, #fda085 100%); |
| } |
| |
| .gradient-purple { |
| background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); |
| } |
| |
| .gradient-success { |
| background: linear-gradient(135deg, #10b981 0%, #34d399 100%); |
| } |
| |
| .gradient-warning { |
| background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%); |
| } |
| |
| .gradient-danger { |
| background: linear-gradient(135deg, #ef4444 0%, #f87171 100%); |
| } |
| |
| .loader { |
| width: 60px; |
| height: 60px; |
| border: 4px solid rgba(102, 126, 234, 0.2); |
| border-radius: 50%; |
| border-top-color: var(--primary); |
| animation: spin 1s ease-in-out infinite; |
| } |
| |
| @keyframes spin { |
| to { transform: rotate(360deg); } |
| } |
| |
| .float-animation { |
| animation: float 6s ease-in-out infinite; |
| } |
| |
| @keyframes float { |
| 0%, 100% { transform: translateY(0px) rotate(0deg); } |
| 50% { transform: translateY(-15px) rotate(5deg); } |
| } |
| |
| .card-hover { |
| transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); |
| } |
| |
| .card-hover:hover { |
| transform: translateY(-10px) scale(1.02); |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4); |
| } |
| |
| .settings-panel { |
| max-height: 0; |
| overflow: hidden; |
| transition: max-height 0.8s cubic-bezier(0.4, 0, 0.2, 1); |
| } |
| |
| .settings-panel.open { |
| max-height: 3000px; |
| } |
| |
| .roulette-spin { |
| animation: roulette-spin 0.1s linear infinite; |
| } |
| |
| @keyframes roulette-spin { |
| 0% { transform: rotate(0deg) scale(1); } |
| 50% { transform: rotate(180deg) scale(1.1); } |
| 100% { transform: rotate(360deg) scale(1); } |
| } |
| |
| .reveal-animation { |
| animation: reveal 0.8s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards; |
| } |
| |
| @keyframes reveal { |
| 0% { |
| opacity: 0; |
| transform: scale(0.3) rotate(-15deg); |
| filter: blur(10px); |
| } |
| 70% { |
| opacity: 1; |
| transform: scale(1.1) rotate(5deg); |
| filter: blur(0); |
| } |
| 100% { |
| opacity: 1; |
| transform: scale(1) rotate(0); |
| filter: blur(0); |
| } |
| } |
| |
| .element-icon { |
| position: absolute; |
| bottom: -8px; |
| right: -8px; |
| width: 28px; |
| height: 28px; |
| border-radius: 50%; |
| background: #1e293b; |
| padding: 3px; |
| box-shadow: 0 4px 12px rgba(0,0,0,0.3); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| z-index: 10; |
| } |
| |
| .tab-active { |
| position: relative; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; |
| color: white !important; |
| box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3); |
| } |
| |
| .glow { |
| box-shadow: 0 0 35px rgba(102, 126, 234, 0.6); |
| } |
| |
| .particle { |
| position: absolute; |
| width: 6px; |
| height: 6px; |
| background: #667eea; |
| border-radius: 50%; |
| animation: particle-float 2s ease-out forwards; |
| z-index: 1; |
| } |
| |
| @keyframes particle-float { |
| 0% { |
| transform: translate(0, 0) scale(1); |
| opacity: 1; |
| } |
| 100% { |
| transform: translate(var(--tx), var(--ty)) scale(0); |
| opacity: 0; |
| } |
| } |
| |
| .pulse { |
| animation: pulse 2s infinite; |
| } |
| |
| @keyframes pulse { |
| 0% { transform: scale(1); } |
| 50% { transform: scale(1.05); } |
| 100% { transform: scale(1); } |
| } |
| |
| .slide-in { |
| animation: slide-in 0.5s ease-out forwards; |
| } |
| |
| @keyframes slide-in { |
| from { |
| opacity: 0; |
| transform: translateY(30px); |
| } |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| .progress-bar { |
| height: 6px; |
| border-radius: 3px; |
| background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); |
| width: 0%; |
| transition: width 0.3s ease; |
| } |
| |
| .custom-scrollbar::-webkit-scrollbar { |
| width: 8px; |
| height: 8px; |
| } |
| |
| .custom-scrollbar::-webkit-scrollbar-track { |
| background: rgba(255, 255, 255, 0.05); |
| border-radius: 10px; |
| } |
| |
| .custom-scrollbar::-webkit-scrollbar-thumb { |
| background: rgba(102, 126, 234, 0.5); |
| border-radius: 10px; |
| } |
| |
| .custom-scrollbar::-webkit-scrollbar-thumb:hover { |
| background: rgba(102, 126, 234, 0.8); |
| } |
| |
| .character-card { |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .character-card::before { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| height: 4px; |
| background: linear-gradient(90deg, #667eea, #764ba2); |
| transform: scaleX(0); |
| transition: transform 0.3s ease; |
| transform-origin: left; |
| } |
| |
| .character-card:hover::before { |
| transform: scaleX(1); |
| } |
| |
| .ripple { |
| position: absolute; |
| border-radius: 50%; |
| background: rgba(255, 255, 255, 0.6); |
| transform: scale(0); |
| animation: ripple 0.6s linear; |
| pointer-events: none; |
| } |
| |
| @keyframes ripple { |
| to { |
| transform: scale(4); |
| opacity: 0; |
| } |
| } |
| |
| .shine { |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .shine::after { |
| content: ''; |
| position: absolute; |
| top: -50%; |
| left: -50%; |
| width: 200%; |
| height: 200%; |
| background: linear-gradient( |
| to right, |
| rgba(255, 255, 255, 0) 0%, |
| rgba(255, 255, 255, 0.1) 50%, |
| rgba(255, 255, 255, 0) 100% |
| ); |
| transform: rotate(30deg); |
| transition: transform 0.6s; |
| } |
| |
| .shine:hover::after { |
| transform: rotate(30deg) translate(20%, 20%); |
| } |
| |
| .stats-card { |
| background: linear-gradient(135deg, rgba(102, 126, 234, 0.15) 0%, rgba(118, 75, 162, 0.15) 100%); |
| border-left: 4px solid #667eea; |
| } |
| |
| .badge { |
| display: inline-flex; |
| align-items: center; |
| justify-content: center; |
| padding: 4px 10px; |
| border-radius: 20px; |
| font-size: 0.75rem; |
| font-weight: 600; |
| text-transform: uppercase; |
| letter-spacing: 0.5px; |
| } |
| |
| .badge-team { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| color: white; |
| } |
| |
| .badge-boss { |
| background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); |
| color: white; |
| } |
| |
| .badge-legend { |
| background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); |
| color: white; |
| } |
| |
| .input-field { |
| transition: all 0.3s ease; |
| background: rgba(30, 41, 59, 0.9); |
| border: 2px solid transparent; |
| } |
| |
| .input-field:focus { |
| border-color: #667eea; |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2); |
| outline: none; |
| } |
| |
| .toggle-switch { |
| position: relative; |
| display: inline-block; |
| width: 60px; |
| height: 30px; |
| } |
| |
| .toggle-switch input { |
| opacity: 0; |
| width: 0; |
| height: 0; |
| } |
| |
| .toggle-slider { |
| position: absolute; |
| cursor: pointer; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background-color: #ccc; |
| transition: .4s; |
| border-radius: 34px; |
| } |
| |
| .toggle-slider:before { |
| position: absolute; |
| content: ""; |
| height: 22px; |
| width: 22px; |
| left: 4px; |
| bottom: 4px; |
| background-color: white; |
| transition: .4s; |
| border-radius: 50%; |
| } |
| |
| input:checked + .toggle-slider { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| } |
| |
| input:checked + .toggle-slider:before { |
| transform: translateX(30px); |
| } |
| |
| .character-avatar { |
| transition: all 0.3s ease; |
| position: relative; |
| } |
| |
| .character-avatar.selected { |
| transform: scale(1.05); |
| box-shadow: 0 0 0 3px #667eea, 0 10px 25px rgba(102, 126, 234, 0.3); |
| } |
| |
| .modal-backdrop { |
| animation: fadeIn 0.3s ease-out forwards; |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; } |
| to { opacity: 1; } |
| } |
| |
| .modal-content { |
| animation: slideUp 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; |
| } |
| |
| @keyframes slideUp { |
| from { |
| opacity: 0; |
| transform: translateY(50px) scale(0.9); |
| } |
| to { |
| opacity: 1; |
| transform: translateY(0) scale(1); |
| } |
| } |
| |
| .tooltip { |
| position: relative; |
| } |
| |
| .tooltip:hover::after { |
| content: attr(data-tooltip); |
| position: absolute; |
| bottom: 100%; |
| left: 50%; |
| transform: translateX(-50%); |
| background: rgba(255, 255, 255, 0.9); |
| color: #1e293b; |
| padding: 6px 12px; |
| border-radius: 6px; |
| font-size: 0.875rem; |
| white-space: nowrap; |
| z-index: 100; |
| margin-bottom: 5px; |
| } |
| |
| .tooltip:hover::before { |
| content: ''; |
| position: absolute; |
| bottom: 100%; |
| left: 50%; |
| transform: translateX(-50%); |
| border: 5px solid transparent; |
| border-top-color: rgba(255, 255, 255, 0.9); |
| margin-bottom: -5px; |
| z-index: 100; |
| } |
| |
| .grid-stack { |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); |
| gap: 1.25rem; |
| } |
| |
| @media (min-width: 640px) { |
| .grid-stack { |
| grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); |
| } |
| } |
| |
| @media (min-width: 768px) { |
| .grid-stack { |
| grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); |
| } |
| } |
| |
| @media (min-width: 1024px) { |
| .grid-stack { |
| grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); |
| } |
| } |
| |
| .result-container { |
| min-height: 350px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .result-container::before { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%); |
| border-radius: 20px; |
| } |
| |
| .history-item { |
| transition: all 0.3s ease; |
| border-left: 4px solid transparent; |
| } |
| |
| .history-item.team { |
| border-left-color: #667eea; |
| } |
| |
| .history-item.world-boss { |
| border-left-color: #4facfe; |
| } |
| |
| .history-item.weekly-boss { |
| border-left-color: #f6d365; |
| } |
| |
| .history-item.legend { |
| border-left-color: #a8edea; |
| } |
| |
| .history-item:hover { |
| transform: translateX(5px); |
| } |
| |
| .fade-in { |
| animation: fadeIn 0.5s ease-out forwards; |
| } |
| |
| .stagger-item { |
| opacity: 0; |
| transform: translateY(20px); |
| } |
| |
| .stagger-item.visible { |
| animation: slide-in 0.5s ease-out forwards; |
| } |
| |
| .confetti { |
| position: fixed; |
| width: 15px; |
| height: 15px; |
| background: var(--bg); |
| top: -10px; |
| opacity: 0; |
| z-index: 1000; |
| } |
| |
| @keyframes confetti-fall { |
| 0% { |
| opacity: 1; |
| transform: translateY(0) rotate(0deg); |
| } |
| 100% { |
| opacity: 0; |
| transform: translateY(100vh) rotate(720deg); |
| } |
| } |
| |
| .star-rating { |
| color: #fbbf24; |
| } |
| |
| .star-rating .empty { |
| color: #4b5563; |
| } |
| |
| .progress-ring { |
| transform: rotate(-90deg); |
| } |
| |
| .progress-ring-circle { |
| stroke-dasharray: 283; |
| stroke-dashoffset: 283; |
| transition: stroke-dashoffset 0.5s ease; |
| } |
| |
| |
| .card-padding { |
| padding: 1.75rem; |
| } |
| |
| .section-padding { |
| padding: 3rem; |
| } |
| |
| @media (max-width: 768px) { |
| .section-padding { |
| padding: 1.5rem; |
| } |
| |
| .card-padding { |
| padding: 1.25rem; |
| } |
| } |
| |
| .header-height { |
| min-height: auto; |
| padding: 2rem 0; |
| } |
| |
| .stat-card { |
| padding: 1.5rem; |
| border-radius: 1.25rem; |
| } |
| |
| .character-avatar-lg { |
| width: 110px; |
| height: 110px; |
| } |
| |
| .character-avatar-md { |
| width: 90px; |
| height: 90px; |
| } |
| |
| .boss-avatar { |
| width: 140px; |
| height: 140px; |
| } |
| |
| .button-padding { |
| padding: 1rem 2.5rem; |
| } |
| |
| .tab-button { |
| padding: 0.875rem 2rem; |
| border-radius: 1.25rem; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="app-container" class="container mx-auto px-4 py-6 md:py-8 max-w-7xl"> |
| |
| <header class="text-center mb-10"> |
| <div class="flex flex-col md:flex-row justify-between items-center mb-8 gap-6"> |
| |
| <div class="flex items-center space-x-4"> |
| <button id="language-toggle" class="p-3 rounded-2xl glass hover:scale-105 transition-transform duration-300 group tooltip" data-tooltip="Switch language"> |
| <div class="flex flex-col items-center"> |
| <i class="fas fa-language text-blue-300 text-xl"></i> |
| <span id="language-label" class="text-xs font-medium mt-1">EN</span> |
| </div> |
| </button> |
| |
| <div class="hidden md:flex items-center space-x-2 px-4 py-2 rounded-2xl glass"> |
| <i class="fas fa-user text-purple-400"></i> |
| <span class="text-sm font-medium text-gray-300">UID:</span> |
| <span id="user-uid" class="text-sm font-mono bg-gray-800 px-2 py-1 rounded-lg"></span> |
| </div> |
| </div> |
| |
| |
| <div class="text-center order-first md:order-none"> |
| <div class="flex items-center justify-center mb-3"> |
| <div class="w-12 h-12 rounded-full gradient-primary flex items-center justify-center mr-3"> |
| <i class="fas fa-dice text-white text-xl"></i> |
| </div> |
| <h1 class="text-4xl md:text-5xl lg:text-5xl font-bold bg-gradient-to-r from-purple-500 via-blue-400 to-purple-500 bg-clip-text text-transparent animate-pulse"> |
| <span data-i18n="title">Genshin Roulette</span> |
| </h1> |
| </div> |
| <p class="text-lg text-gray-300 max-w-2xl mx-auto" data-i18n="subtitle"> |
| Random characters, bosses & legends for your Genshin Impact adventure |
| </p> |
| </div> |
|
|
| |
| <div class="flex items-center space-x-4"> |
| <button id="stats-toggle" class="p-3 rounded-2xl glass hover:scale-105 transition-transform duration-300 group tooltip" data-tooltip="Show stats"> |
| <i class="fas fa-chart-bar text-green-300 text-xl"></i> |
| </button> |
| |
| <button id="settings-toggle" class="p-3 rounded-2xl glass hover:scale-105 transition-transform duration-300 group tooltip" data-tooltip="Settings"> |
| <i class="fas fa-cog text-gray-300 group-hover:rotate-90 transition-transform text-xl"></i> |
| </button> |
| |
| <button id="help-toggle" class="p-3 rounded-2xl glass hover:scale-105 transition-transform duration-300 group tooltip" data-tooltip="Help"> |
| <i class="fas fa-question text-yellow-300 text-xl"></i> |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="stats-panel" class="settings-panel glass rounded-3xl p-6 mb-8"> |
| <div class="grid grid-cols-2 md:grid-cols-4 gap-4"> |
| <div class="stats-card p-4 rounded-2xl"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <p class="text-sm text-gray-400" data-i18n="stats.characters">Characters</p> |
| <p class="text-2xl font-bold text-white" id="total-characters">120</p> |
| </div> |
| <i class="fas fa-users text-purple-400 text-2xl"></i> |
| </div> |
| <div class="mt-2 text-xs text-gray-400"> |
| <span data-i18n="stats.owned">Owned:</span> <span id="owned-characters">0</span> |
| </div> |
| </div> |
| |
| <div class="stats-card p-4 rounded-2xl"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <p class="text-sm text-gray-400" data-i18n="stats.worldBosses">World Bosses</p> |
| <p class="text-2xl font-bold text-white" id="total-world-bosses">40</p> |
| </div> |
| <i class="fas fa-dragon text-blue-400 text-2xl"></i> |
| </div> |
| <div class="mt-2 text-xs text-gray-400"> |
| <span data-i18n="stats.available">Available:</span> <span id="available-world-bosses">40</span> |
| </div> |
| </div> |
| |
| <div class="stats-card p-4 rounded-2xl"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <p class="text-sm text-gray-400" data-i18n="stats.weeklyBosses">Weekly Bosses</p> |
| <p class="text-2xl font-bold text-white" id="total-weekly-bosses">12</p> |
| </div> |
| <i class="fas fa-crown text-yellow-400 text-2xl"></i> |
| </div> |
| <div class="mt-2 text-xs text-gray-400"> |
| <span data-i18n="stats.available">Available:</span> <span id="available-weekly-bosses">12</span> |
| </div> |
| </div> |
| |
| <div class="stats-card p-4 rounded-2xl"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <p class="text-sm text-gray-400" data-i18n="stats.legends">Legends</p> |
| <p class="text-2xl font-bold text-white" id="total-legends">30</p> |
| </div> |
| <i class="fas fa-landmark text-pink-400 text-2xl"></i> |
| </div> |
| <div class="mt-2 text-xs text-gray-400"> |
| <span data-i18n="stats.available">Available:</span> <span id="available-legends">30</span> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="flex flex-wrap justify-center gap-3 mb-8"> |
| <button data-category="characters" class="tab-btn px-6 py-3 rounded-2xl glass font-semibold transition-all duration-300 hover:scale-105 flex items-center group tab-button"> |
| <i class="fas fa-users mr-2 text-purple-400 group-hover:text-white transition-colors"></i> |
| <span data-i18n="tab.characters">Character Team</span> |
| </button> |
| |
| <button data-category="world-bosses" class="tab-btn px-6 py-3 rounded-2xl glass font-semibold transition-all duration-300 hover:scale-105 flex items-center group tab-button"> |
| <i class="fas fa-dragon mr-2 text-blue-400 group-hover:text-white transition-colors"></i> |
| <span data-i18n="tab.worldBosses">World Bosses</span> |
| </button> |
| |
| <button data-category="weekly-bosses" class="tab-btn px-6 py-3 rounded-2xl glass font-semibold transition-all duration-300 hover:scale-105 flex items-center group tab-button"> |
| <i class="fas fa-crown mr-2 text-yellow-400 group-hover:text-white transition-colors"></i> |
| <span data-i18n="tab.weeklyBosses">Weekly Bosses</span> |
| </button> |
| |
| <button data-category="local-legends" class="tab-btn px-6 py-3 rounded-2xl glass font-semibold transition-all duration-300 hover:scale-105 flex items-center group tab-button"> |
| <i class="fas fa-landmark mr-2 text-pink-400 group-hover:text-white transition-colors"></i> |
| <span data-i18n="tab.localLegends">Local Legends</span> |
| </button> |
| </div> |
| </header> |
|
|
| <main> |
| |
| <div id="settings-panel" class="settings-panel glass rounded-3xl p-8 mb-10"> |
| <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"> |
| <h3 class="text-2xl font-bold text-white flex items-center"> |
| <i class="fas fa-sliders-h mr-3 text-purple-400"></i> |
| <span data-i18n="settings.title">Roulette Settings</span> |
| </h3> |
| |
| <div class="flex items-center space-x-4"> |
| <div class="flex items-center space-x-2"> |
| <span class="text-sm font-medium text-gray-300" data-i18n="settings.sound">Sound:</span> |
| <label class="toggle-switch"> |
| <input type="checkbox" id="sound-toggle" checked> |
| <span class="toggle-slider"></span> |
| </label> |
| </div> |
| |
| <div class="flex items-center space-x-2"> |
| <span class="text-sm font-medium text-gray-300" data-i18n="settings.animations">Animations:</span> |
| <label class="toggle-switch"> |
| <input type="checkbox" id="animations-toggle" checked> |
| <span class="toggle-slider"></span> |
| </label> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-10"> |
| <div class="card-gradient p-6 rounded-2xl"> |
| <label for="team-size" class="block text-sm font-semibold mb-3 text-gray-300 flex items-center"> |
| <i class="fas fa-user-group mr-2 text-purple-400"></i> |
| <span data-i18n="settings.teamSize">Team Size</span> |
| </label> |
| <select id="team-size" class="w-full p-3 rounded-xl input-field focus:ring-2 focus:ring-purple-500 transition-all"> |
| <option value="4">4</option> |
| <option value="3">3</option> |
| <option value="2">2</option> |
| <option value="1">1</option> |
| </select> |
| </div> |
| |
| <div class="card-gradient p-6 rounded-2xl"> |
| <label for="animation-speed" class="block text-sm font-semibold mb-3 text-gray-300 flex items-center"> |
| <i class="fas fa-tachometer-alt mr-2 text-blue-400"></i> |
| <span data-i18n="settings.animationSpeed">Animation Speed</span> |
| </label> |
| <select id="animation-speed" class="w-full p-3 rounded-xl input-field focus:ring-2 focus:ring-purple-500 transition-all"> |
| <option value="fast" data-i18n="settings.speed.fast">Fast</option> |
| <option value="medium" data-i18n="settings.speed.medium" selected>Medium</option> |
| <option value="slow" data-i18n="settings.speed.slow">Slow</option> |
| </select> |
| </div> |
| |
| <div class="card-gradient p-6 rounded-2xl"> |
| <label for="duplicates" class="block text-sm font-semibold mb-3 text-gray-300 flex items-center"> |
| <i class="fas fa-copy mr-2 text-green-400"></i> |
| <span data-i18n="settings.duplicates">Allow Duplicates</span> |
| </label> |
| <select id="duplicates" class="w-full p-3 rounded-xl input-field focus:ring-2 focus:ring-purple-500 transition-all"> |
| <option value="no" data-i18n="settings.duplicates.no">No Duplicates</option> |
| <option value="yes" data-i18n="settings.duplicates.yes">Allow Duplicates</option> |
| </select> |
| </div> |
| </div> |
|
|
| |
| <div class="mb-10"> |
| <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 gap-4"> |
| <h3 class="text-2xl font-bold text-white flex items-center"> |
| <i class="fas fa-user-check mr-3 text-green-400"></i> |
| <span data-i18n="settings.myCharacters">My Characters</span> |
| </h3> |
| |
| <div class="flex items-center space-x-3"> |
| <div class="px-4 py-2 rounded-xl glass"> |
| <span class="text-sm font-medium text-gray-300"> |
| <span data-i18n="settings.selected">Selected:</span> |
| <span id="selected-count">0</span>/<span id="total-characters-count">120</span> |
| </span> |
| </div> |
| |
| <button id="select-all-characters" class="px-4 py-2 rounded-xl gradient-success text-white font-semibold hover:scale-105 transition-transform"> |
| <span data-i18n="settings.selectAll">Select All</span> |
| </button> |
| |
| <button id="clear-player-characters" class="px-4 py-2 rounded-xl gradient-danger text-white font-semibold hover:scale-105 transition-transform hidden"> |
| <span data-i18n="settings.clearSelected">Clear All</span> |
| </button> |
| </div> |
| </div> |
| |
| <p class="text-gray-400 mb-6" data-i18n="settings.myCharactersDesc"> |
| Select characters you own. If none selected, all characters will be used. |
| </p> |
| |
| |
| <div class="mb-6 grid grid-cols-1 md:grid-cols-3 gap-4"> |
| <div class="md:col-span-2"> |
| <div class="relative"> |
| <i class="fas fa-search absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400"></i> |
| <input type="text" id="character-search" |
| placeholder="Search characters by name..." |
| data-i18n-placeholder="settings.searchPlaceholder" |
| class="w-full pl-12 pr-4 py-3 rounded-2xl input-field focus:ring-2 focus:ring-purple-500 transition-all"> |
| </div> |
| </div> |
| |
| <div> |
| <select id="element-filter" class="w-full p-3 rounded-xl input-field focus:ring-2 focus:ring-purple-500 transition-all"> |
| <option value="all" data-i18n="filters.allElements">All Elements</option> |
| <option value="Pyro" data-i18n="elements.pyro">Pyro</option> |
| <option value="Hydro" data-i18n="elements.hydro">Hydro</option> |
| <option value="Anemo" data-i18n="elements.anemo">Anemo</option> |
| <option value="Electro" data-i18n="elements.electro">Electro</option> |
| <option value="Dendro" data-i18n="elements.dendro">Dendro</option> |
| <option value="Cryo" data-i18n="elements.cryo">Cryo</option> |
| <option value="Geo" data-i18n="elements.geo">Geo</option> |
| </select> |
| </div> |
| </div> |
| |
| |
| <div id="player-characters-selection" class="grid-stack mb-6 max-h-[500px] overflow-y-auto p-2 custom-scrollbar"> |
| |
| </div> |
| |
| |
| <div class="mb-2"> |
| <div class="flex justify-between text-sm text-gray-400 mb-1"> |
| <span data-i18n="settings.progress">Selection Progress</span> |
| <span id="selection-percentage">0%</span> |
| </div> |
| <div class="w-full bg-gray-700 rounded-full h-2.5"> |
| <div id="selection-progress-bar" class="progress-bar h-2.5 rounded-full"></div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div> |
| <h3 class="text-2xl font-bold mb-6 text-white flex items-center"> |
| <i class="fas fa-dragon mr-3 text-red-400"></i> |
| <span data-i18n="settings.bossSettings">Boss & Legend Settings</span> |
| </h3> |
| |
| <p class="text-gray-400 mb-6" data-i18n="settings.bossSettingsDesc"> |
| Toggle which bosses and legends should be included in randomization |
| </p> |
| |
| |
| <div class="mb-6"> |
| <div class="card-gradient p-6 rounded-2xl"> |
| <label for="boss-category-select" class="block text-sm font-semibold mb-3 text-gray-300 flex items-center"> |
| <i class="fas fa-filter mr-2 text-blue-400"></i> |
| <span data-i18n="settings.category">Category:</span> |
| </label> |
| <div class="flex flex-wrap gap-3"> |
| <button data-boss-category="world-bosses" class="boss-category-btn px-4 py-2 rounded-xl glass font-medium transition-all hover:scale-105 flex items-center"> |
| <i class="fas fa-dragon mr-2 text-blue-400"></i> |
| <span data-i18n="settings.categories.worldBosses">World Bosses</span> |
| <span class="ml-2 px-2 py-1 text-xs bg-blue-900 text-blue-200 rounded-full" id="world-boss-count">40</span> |
| </button> |
| |
| <button data-boss-category="weekly-bosses" class="boss-category-btn px-4 py-2 rounded-xl glass font-medium transition-all hover:scale-105 flex items-center"> |
| <i class="fas fa-crown mr-2 text-yellow-400"></i> |
| <span data-i18n="settings.categories.weeklyBosses">Weekly Bosses</span> |
| <span class="ml-2 px-2 py-1 text-xs bg-yellow-900 text-yellow-200 rounded-full" id="weekly-boss-count">12</span> |
| </button> |
| |
| <button data-boss-category="local-legends" class="boss-category-btn px-4 py-2 rounded-xl glass font-medium transition-all hover:scale-105 flex items-center active"> |
| <i class="fas fa-landmark mr-2 text-pink-400"></i> |
| <span data-i18n="settings.categories.localLegends">Local Legends</span> |
| <span class="ml-2 px-2 py-1 text-xs bg-pink-900 text-pink-200 rounded-full" id="legend-count">30</span> |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div id="bosses-settings-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 max-h-[500px] overflow-y-auto p-2 custom-scrollbar"> |
| |
| </div> |
| |
| |
| <div class="flex flex-wrap gap-3 mt-6"> |
| <button id="enable-all-bosses" class="px-4 py-2 rounded-xl gradient-success text-white font-semibold hover:scale-105 transition-transform"> |
| <i class="fas fa-check-circle mr-2"></i> |
| <span data-i18n="settings.enableAll">Enable All</span> |
| </button> |
| |
| <button id="disable-all-bosses" class="px-4 py-2 rounded-xl gradient-danger text-white font-semibold hover:scale-105 transition-transform"> |
| <i class="fas fa-times-circle mr-2"></i> |
| <span data-i18n="settings.disableAll">Disable All</span> |
| </button> |
| |
| <button id="reset-bosses" class="px-4 py-2 rounded-xl glass font-semibold hover:scale-105 transition-transform"> |
| <i class="fas fa-undo mr-2"></i> |
| <span data-i18n="settings.reset">Reset to Default</span> |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <section id="characters-section" class="mb-20 glass rounded-3xl p-8 relative overflow-hidden section-padding"> |
| |
| <div class="absolute top-0 right-0 w-64 h-64 bg-gradient-to-br from-purple-500/10 to-pink-500/10 rounded-full -translate-y-32 translate-x-32"></div> |
| <div class="absolute bottom-0 left-0 w-96 h-96 bg-gradient-to-tr from-blue-500/10 to-cyan-500/10 rounded-full translate-y-48 -translate-x-48"></div> |
| |
| <div class="relative z-10"> |
| <div class="text-center mb-10"> |
| <div class="inline-flex items-center justify-center w-24 h-24 rounded-full gradient-primary mb-6"> |
| <i class="fas fa-users text-white text-4xl"></i> |
| </div> |
| <h2 class="text-5xl font-bold mb-4 text-white" data-i18n="sections.characters.title"> |
| Character Team Roulette |
| </h2> |
| <p class="text-gray-300 text-lg max-w-2xl mx-auto" data-i18n="sections.characters.subtitle"> |
| Spin to get a random team of characters for your next adventure |
| </p> |
| </div> |
| |
| |
| <div class="flex flex-col md:flex-row justify-between items-center mb-10 gap-6"> |
| <div class="flex-1 w-full"> |
| <div class="card-gradient p-6 rounded-2xl card-padding"> |
| <div class="flex flex-col md:flex-row justify-between items-center gap-4"> |
| <div> |
| <h4 class="font-bold text-lg text-white mb-2" data-i18n="sections.characters.config">Team Configuration</h4> |
| <p class="text-gray-400 text-sm" data-i18n="sections.characters.configDesc"> |
| Configure your team before spinning |
| </p> |
| </div> |
| |
| <div class="flex items-center space-x-6"> |
| <div class="text-center"> |
| <div class="text-3xl font-bold text-white" id="current-team-size">4</div> |
| <div class="text-xs text-gray-400" data-i18n="sections.characters.teamSize">Team Size</div> |
| </div> |
| |
| <div class="text-center"> |
| <div class="text-3xl font-bold text-white" id="available-chars">120</div> |
| <div class="text-xs text-gray-400" data-i18n="sections.characters.available">Available</div> |
| </div> |
| |
| <div class="text-center"> |
| <div class="text-3xl font-bold text-white" id="owned-chars">0</div> |
| <div class="text-xs text-gray-400" data-i18n="sections.characters.owned">Owned</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="flex-shrink-0 mt-4 md:mt-0"> |
| <button id="spin-characters-button" |
| class="px-14 py-6 rounded-2xl gradient-primary text-white font-bold text-xl hover:scale-105 transition-transform duration-300 shadow-lg hover:shadow-xl relative overflow-hidden group pulse button-padding"> |
| <div class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000"></div> |
| <div class="relative z-10 flex items-center justify-center"> |
| <i class="fas fa-dice-d20 mr-3 text-2xl"></i> |
| <span data-i18n="buttons.spinTeam">Spin Team!</span> |
| </div> |
| </button> |
| </div> |
| </div> |
| |
| |
| <div id="characters-result" class="result-container rounded-2xl p-8 mb-8"> |
| <div class="text-center max-w-md mx-auto"> |
| <div class="text-7xl mb-6 float-animation">🎮</div> |
| <p id="characters-placeholder" class="text-gray-400 text-xl mb-4" data-i18n="placeholders.characters"> |
| Your random team will appear here |
| </p> |
| <p class="text-gray-500 text-sm" data-i18n="placeholders.charactersHint"> |
| Click the "Spin Team!" button to generate a random team |
| </p> |
| </div> |
| <div id="characters-loader" class="loader hidden"></div> |
| </div> |
| |
| |
| <div id="save-characters-section" class="mt-8 flex flex-col md:flex-row justify-center items-center gap-4 hidden"> |
| <div class="relative flex-1 w-full md:w-auto"> |
| <i class="fas fa-tag absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400"></i> |
| <input type="text" id="team-name-input" |
| placeholder="Give your team a name (optional)" |
| data-i18n-placeholder="placeholders.teamName" |
| class="w-full pl-12 pr-4 py-4 rounded-2xl input-field focus:ring-2 focus:ring-purple-500"> |
| </div> |
| |
| <button id="save-team-button" |
| class="px-8 py-4 rounded-2xl gradient-secondary text-white font-bold hover:scale-105 transition-transform flex items-center"> |
| <i class="fas fa-save mr-3"></i> |
| <span data-i18n="buttons.saveTeam">Save Team</span> |
| </button> |
| |
| <button id="share-team-button" |
| class="px-8 py-4 rounded-2xl glass font-bold hover:scale-105 transition-transform flex items-center hidden"> |
| <i class="fas fa-share-alt mr-3"></i> |
| <span data-i18n="buttons.share">Share</span> |
| </button> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section id="world-bosses-section" class="mb-20 glass rounded-3xl p-8 hidden relative overflow-hidden section-padding"> |
| |
| <div class="absolute top-0 left-0 w-64 h-64 bg-gradient-to-br from-blue-500/10 to-cyan-500/10 rounded-full -translate-y-32 -translate-x-32"></div> |
| <div class="absolute bottom-0 right-0 w-96 h-96 bg-gradient-to-tr from-purple-500/10 to-pink-500/10 rounded-full translate-y-48 translate-x-48"></div> |
| |
| <div class="relative z-10"> |
| <div class="text-center mb-10"> |
| <div class="inline-flex items-center justify-center w-24 h-24 rounded-full gradient-accent mb-6"> |
| <i class="fas fa-dragon text-white text-4xl"></i> |
| </div> |
| <h2 class="text-5xl font-bold mb-4 text-white" data-i18n="sections.worldBosses.title"> |
| World Boss Roulette |
| </h2> |
| <p class="text-gray-300 text-lg max-w-2xl mx-auto" data-i18n="sections.worldBosses.subtitle"> |
| Challenge a random world boss and test your skills |
| </p> |
| </div> |
| |
| <div class="flex justify-center mb-10"> |
| <button id="spin-world-boss-button" |
| class="px-14 py-6 rounded-2xl gradient-accent text-white font-bold text-xl hover:scale-105 transition-transform duration-300 shadow-lg hover:shadow-xl relative overflow-hidden group button-padding"> |
| <div class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000"></div> |
| <div class="relative z-10 flex items-center justify-center"> |
| <i class="fas fa-dragon mr-3 text-2xl"></i> |
| <span data-i18n="buttons.spinBoss">Spin Boss!</span> |
| </div> |
| </button> |
| </div> |
| |
| <div id="world-boss-result" class="result-container rounded-2xl p-8 mb-8"> |
| <div class="text-center max-w-md mx-auto"> |
| <div class="text-7xl mb-6 float-animation">🐉</div> |
| <p id="world-boss-placeholder" class="text-gray-400 text-xl mb-4" data-i18n="placeholders.worldBoss"> |
| Random world boss will appear here |
| </p> |
| <p class="text-gray-500 text-sm" data-i18n="placeholders.worldBossHint"> |
| Click the "Spin Boss!" button to generate a random world boss |
| </p> |
| </div> |
| <div id="world-boss-loader" class="loader hidden"></div> |
| </div> |
| |
| <div id="save-world-boss-section" class="mt-8 flex justify-center hidden"> |
| <button id="save-world-boss-button" |
| class="px-8 py-4 rounded-2xl gradient-secondary text-white font-bold hover:scale-105 transition-transform flex items-center"> |
| <i class="fas fa-save mr-3"></i> |
| <span data-i18n="buttons.saveToHistory">Save to History</span> |
| </button> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section id="weekly-bosses-section" class="mb-20 glass rounded-3xl p-8 hidden relative overflow-hidden section-padding"> |
| |
| <div class="absolute top-0 right-0 w-64 h-64 bg-gradient-to-br from-yellow-500/10 to-orange-500/10 rounded-full -translate-y-32 translate-x-32"></div> |
| <div class="absolute bottom-0 left-0 w-96 h-96 bg-gradient-to-tr from-red-500/10 to-pink-500/10 rounded-full translate-y-48 -translate-x-48"></div> |
| |
| <div class="relative z-10"> |
| <div class="text-center mb-10"> |
| <div class="inline-flex items-center justify-center w-24 h-24 rounded-full gradient-gold mb-6"> |
| <i class="fas fa-crown text-white text-4xl"></i> |
| </div> |
| <h2 class="text-5xl font-bold mb-4 text-white" data-i18n="sections.weeklyBosses.title"> |
| Weekly Boss Roulette |
| </h2> |
| <p class="text-gray-300 text-lg max-w-2xl mx-auto" data-i18n="sections.weeklyBosses.subtitle"> |
| Face a random weekly boss for valuable rewards |
| </p> |
| </div> |
| |
| <div class="flex justify-center mb-10"> |
| <button id="spin-weekly-boss-button" |
| class="px-14 py-6 rounded-2xl gradient-gold text-white font-bold text-xl hover:scale-105 transition-transform duration-300 shadow-lg hover:shadow-xl relative overflow-hidden group button-padding"> |
| <div class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000"></div> |
| <div class="relative z-10 flex items-center justify-center"> |
| <i class="fas fa-crown mr-3 text-2xl"></i> |
| <span data-i18n="buttons.spinWeeklyBoss">Spin Weekly Boss!</span> |
| </div> |
| </button> |
| </div> |
| |
| <div id="weekly-boss-result" class="result-container rounded-2xl p-8 mb-8"> |
| <div class="text-center max-w-md mx-auto"> |
| <div class="text-7xl mb-6 float-animation">⚔️</div> |
| <p id="weekly-boss-placeholder" class="text-gray-400 text-xl mb-4" data-i18n="placeholders.weeklyBoss"> |
| Random weekly boss will appear here |
| </p> |
| <p class="text-gray-500 text-sm" data-i18n="placeholders.weeklyBossHint"> |
| Click the "Spin Weekly Boss!" button to generate a random weekly boss |
| </p> |
| </div> |
| <div id="weekly-boss-loader" class="loader hidden"></div> |
| </div> |
| |
| <div id="save-weekly-boss-section" class="mt-8 flex justify-center hidden"> |
| <button id="save-weekly-boss-button" |
| class="px-8 py-4 rounded-2xl gradient-secondary text-white font-bold hover:scale-105 transition-transform flex items-center"> |
| <i class="fas fa-save mr-3"></i> |
| <span data-i18n="buttons.saveToHistory">Save to History</span> |
| </button> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section id="local-legends-section" class="mb-20 glass rounded-3xl p-8 hidden relative overflow-hidden section-padding"> |
| |
| <div class="absolute top-0 left-0 w-64 h-64 bg-gradient-to-br from-pink-500/10 to-purple-500/10 rounded-full -translate-y-32 -translate-x-32"></div> |
| <div class="absolute bottom-0 right-0 w-96 h-96 bg-gradient-to-tr from-green-500/10 to-cyan-500/10 rounded-full translate-y-48 translate-x-48"></div> |
| |
| <div class="relative z-10"> |
| <div class="text-center mb-10"> |
| <div class="inline-flex items-center justify-center w-24 h-24 rounded-full gradient-purple mb-6"> |
| <i class="fas fa-landmark text-white text-4xl"></i> |
| </div> |
| <h2 class="text-5xl font-bold mb-4 text-white" data-i18n="sections.localLegends.title"> |
| Local Legends Roulette |
| </h2> |
| <p class="text-gray-300 text-lg max-w-2xl mx-auto" data-i18n="sections.localLegends.subtitle"> |
| Discover and challenge random local legends across Teyvat |
| </p> |
| </div> |
| |
| <div class="flex justify-center mb-10"> |
| <button id="spin-legend-button" |
| class="px-14 py-6 rounded-2xl gradient-purple text-white font-bold text-xl hover:scale-105 transition-transform duration-300 shadow-lg hover:shadow-xl relative overflow-hidden group button-padding"> |
| <div class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000"></div> |
| <div class="relative z-10 flex items-center justify-center"> |
| <i class="fas fa-landmark mr-3 text-2xl"></i> |
| <span data-i18n="buttons.spinLegend">Spin Legend!</span> |
| </div> |
| </button> |
| </div> |
| |
| <div id="legend-result" class="result-container rounded-2xl p-8 mb-8"> |
| <div class="text-center max-w-md mx-auto"> |
| <div class="text-7xl mb-6 float-animation">🏆</div> |
| <p id="legend-placeholder" class="text-gray-400 text-xl mb-4" data-i18n="placeholders.legend"> |
| Random local legend will appear here |
| </p> |
| <p class="text-gray-500 text-sm" data-i18n="placeholders.legendHint"> |
| Click the "Spin Legend!" button to discover a random local legend |
| </p> |
| </div> |
| <div id="legend-loader" class="loader hidden"></div> |
| </div> |
| |
| <div id="save-legend-section" class="mt-8 flex justify-center hidden"> |
| <button id="save-legend-button" |
| class="px-8 py-4 rounded-2xl gradient-secondary text-white font-bold hover:scale-105 transition-transform flex items-center"> |
| <i class="fas fa-save mr-3"></i> |
| <span data-i18n="buttons.saveToHistory">Save to History</span> |
| </button> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section id="history-section" class="glass rounded-3xl p-8 relative overflow-hidden section-padding"> |
| |
| <div class="absolute top-0 right-0 w-48 h-48 bg-gradient-to-br from-gray-500/10 to-gray-700/10 rounded-full -translate-y-24 translate-x-24"></div> |
| |
| <div class="relative z-10"> |
| <div class="flex flex-col md:flex-row justify-between items-center mb-10 gap-6"> |
| <div> |
| <h2 class="text-5xl font-bold text-white flex items-center"> |
| <i class="fas fa-history mr-4 text-purple-400"></i> |
| <span data-i18n="history.title">History</span> |
| </h2> |
| <p class="text-gray-400 mt-2" data-i18n="history.subtitle"> |
| Your recent random selections |
| </p> |
| </div> |
| |
| <div class="flex flex-wrap gap-3"> |
| <button id="show-full-history-button" |
| class="px-5 py-3 rounded-2xl glass font-semibold hover:scale-105 transition-transform flex items-center hidden"> |
| <i class="fas fa-list-ol mr-3"></i> |
| <span data-i18n="buttons.fullHistory">Full History</span> |
| </button> |
| |
| <button id="clear-history-button" |
| class="px-5 py-3 rounded-2xl gradient-danger text-white font-semibold hover:scale-105 transition-transform flex items-center hidden"> |
| <i class="fas fa-trash-alt mr-3"></i> |
| <span data-i18n="buttons.clearHistory">Clear History</span> |
| </button> |
| |
| <button id="export-history-button" |
| class="px-5 py-3 rounded-2xl glass font-semibold hover:scale-105 transition-transform flex items-center"> |
| <i class="fas fa-download mr-3"></i> |
| <span data-i18n="buttons.export">Export</span> |
| </button> |
| </div> |
| </div> |
| |
| |
| <div class="mb-8"> |
| <div class="flex flex-wrap gap-3"> |
| <button data-history-filter="all" class="history-filter-btn px-4 py-2 rounded-xl glass font-medium transition-all hover:scale-105 active"> |
| <i class="fas fa-layer-group mr-2"></i> |
| <span data-i18n="history.filters.all">All</span> |
| </button> |
| |
| <button data-history-filter="team" class="history-filter-btn px-4 py-2 rounded-xl glass font-medium transition-all hover:scale-105"> |
| <i class="fas fa-users mr-2 text-purple-400"></i> |
| <span data-i18n="history.filters.teams">Teams</span> |
| </button> |
| |
| <button data-history-filter="world-boss" class="history-filter-btn px-4 py-2 rounded-xl glass font-medium transition-all hover:scale-105"> |
| <i class="fas fa-dragon mr-2 text-blue-400"></i> |
| <span data-i18n="history.filters.worldBosses">World Bosses</span> |
| </button> |
| |
| <button data-history-filter="weekly-boss" class="history-filter-btn px-4 py-2 rounded-xl glass font-medium transition-all hover:scale-105"> |
| <i class="fas fa-crown mr-2 text-yellow-400"></i> |
| <span data-i18n="history.filters.weeklyBosses">Weekly Bosses</span> |
| </button> |
| |
| <button data-history-filter="legend" class="history-filter-btn px-4 py-2 rounded-xl glass font-medium transition-all hover:scale-105"> |
| <i class="fas fa-landmark mr-2 text-pink-400"></i> |
| <span data-i18n="history.filters.legends">Legends</span> |
| </button> |
| </div> |
| </div> |
| |
| |
| <div id="history-content" class="space-y-6"> |
| <p id="history-placeholder" class="text-center text-gray-400 text-lg py-12" data-i18n="placeholders.history"> |
| Your random selections history will appear here |
| </p> |
| </div> |
| |
| |
| <div class="mt-10 pt-8 border-t border-gray-700"> |
| <div class="grid grid-cols-2 md:grid-cols-4 gap-4"> |
| <div class="text-center p-4 rounded-2xl glass stat-card"> |
| <div class="text-2xl font-bold text-white" id="history-total">0</div> |
| <div class="text-sm text-gray-400" data-i18n="history.stats.total">Total Entries</div> |
| </div> |
| |
| <div class="text-center p-4 rounded-2xl glass stat-card"> |
| <div class="text-2xl font-bold text-white" id="history-teams">0</div> |
| <div class="text-sm text-gray-400" data-i18n="history.stats.teams">Teams</div> |
| </div> |
| |
| <div class="text-center p-4 rounded-2xl glass stat-card"> |
| <div class="text-2xl font-bold text-white" id="history-bosses">0</div> |
| <div class="text-sm text-gray-400" data-i18n="history.stats.bosses">Bosses</div> |
| </div> |
| |
| <div class="text-center p-4 rounded-2xl glass stat-card"> |
| <div class="text-2xl font-bold text-white" id="history-legends">0</div> |
| <div class="text-sm text-gray-400" data-i18n="history.stats.legends">Legends</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </section> |
| </main> |
| |
| |
| <footer class="text-center mt-20 pt-10 border-t border-gray-700"> |
| <div class="flex flex-col lg:flex-row justify-between items-center mb-8 gap-8"> |
| <div class="text-left"> |
| <div class="flex items-center mb-4"> |
| <div class="w-12 h-12 rounded-full gradient-primary flex items-center justify-center mr-3"> |
| <i class="fas fa-dice text-white"></i> |
| </div> |
| <h3 class="text-xl font-bold text-white">Genshin Roulette</h3> |
| </div> |
| <p class="text-sm text-gray-400 max-w-md" data-i18n="footer.disclaimer"> |
| This is a fan-made project. Not affiliated with or endorsed by HoYoverse. |
| </p> |
| </div> |
| |
| <div class="grid grid-cols-2 md:grid-cols-3 gap-6"> |
| </div> |
| </div> |
| |
| <div class="flex flex-col md:flex-row justify-between items-center pt-8 border-t border-gray-700 gap-4"> |
| <div class="flex items-center space-x-6"> |
| <span class="text-sm text-gray-400"> |
| <span data-i18n="footer.version">Version:</span> <span class="font-mono">2.1.0</span> |
| </span> |
| <span class="text-sm text-gray-400"> |
| <span data-i18n="footer.characters">Characters:</span> <span class="font-bold">120</span> |
| </span> |
| <span class="text-sm text-gray-400"> |
| <span data-i18n="footer.bosses">Bosses:</span> <span class="font-bold">82</span> |
| </span> |
| </div> |
| |
| <div class="flex items-center space-x-4"> |
| <button id="back-to-top" class="p-2 rounded-full glass hover:scale-105 transition-transform tooltip" data-tooltip="Back to top"> |
| <i class="fas fa-arrow-up text-gray-300"></i> |
| </button> |
| |
| <a href="#" class="p-2 rounded-full glass hover:scale-105 transition-transform tooltip" data-tooltip="GitHub"> |
| <i class="fab fa-github text-gray-300"></i> |
| </a> |
| |
| <a href="#" class="p-2 rounded-full glass hover:scale-105 transition-transform tooltip" data-tooltip="Discord"> |
| <i class="fab fa-discord text-gray-300"></i> |
| </a> |
| </div> |
| </div> |
| </footer> |
| </div> |
|
|
| |
| <div id="confirm-modal" class="fixed inset-0 bg-black/70 z-50 flex justify-center items-center hidden p-4 modal-backdrop"> |
| <div class="glass rounded-3xl w-full max-w-md shadow-2xl p-8 text-center modal-content"> |
| <div class="w-20 h-20 rounded-full gradient-danger flex items-center justify-center mx-auto mb-6"> |
| <i class="fas fa-exclamation text-white text-3xl"></i> |
| </div> |
| <h3 class="text-2xl font-bold mb-4 text-white" data-i18n="modals.confirm.title">Confirmation</h3> |
| <p id="confirm-modal-message" class="text-gray-300 mb-8 text-lg" data-i18n="modals.confirm.message"> |
| Are you sure you want to clear history? |
| </p> |
| <div class="flex justify-center gap-4"> |
| <button id="confirm-modal-cancel" class="px-8 py-3 rounded-2xl glass font-semibold hover:scale-105 transition-transform flex items-center"> |
| <i class="fas fa-times mr-3"></i> |
| <span data-i18n="buttons.cancel">Cancel</span> |
| </button> |
| <button id="confirm-modal-ok" class="px-8 py-3 rounded-2xl gradient-danger text-white font-semibold hover:scale-105 transition-transform flex items-center"> |
| <i class="fas fa-check mr-3"></i> |
| <span data-i18n="buttons.confirm">Confirm</span> |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| <div id="full-history-modal" class="fixed inset-0 bg-black/70 z-50 flex justify-center items-center hidden p-4 modal-backdrop"> |
| <div class="glass rounded-3xl w-full max-w-6xl h-[90vh] flex flex-col p-8 modal-content"> |
| <div class="flex justify-between items-center mb-8"> |
| <h2 class="text-4xl font-bold text-white flex items-center"> |
| <i class="fas fa-history mr-4 text-purple-400"></i> |
| <span data-i18n="modals.fullHistory.title">Full History</span> |
| </h2> |
| <button id="full-history-modal-close" class="p-3 rounded-2xl glass hover:scale-105 transition-transform"> |
| <i class="fas fa-times text-gray-300 text-xl"></i> |
| </button> |
| </div> |
| |
| <div id="full-history-modal-content-inner" class="flex-1 overflow-y-auto pr-4 space-y-6 custom-scrollbar"> |
| |
| </div> |
| |
| <div class="text-center mt-8 pt-6 border-t border-gray-700"> |
| <button id="full-history-modal-close-bottom" class="px-8 py-3 rounded-2xl glass font-semibold hover:scale-105 transition-transform flex items-center mx-auto"> |
| <i class="fas fa-times mr-3"></i> |
| <span data-i18n="buttons.close">Close</span> |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="help-modal" class="fixed inset-0 bg-black/70 z-50 flex justify-center items-center hidden p-4 modal-backdrop"> |
| <div class="glass rounded-3xl w-full max-w-4xl h-[80vh] flex flex-col p-8 modal-content"> |
| <div class="flex justify-between items-center mb-8"> |
| <h2 class="text-4xl font-bold text-white flex items-center"> |
| <i class="fas fa-question-circle mr-4 text-yellow-400"></i> |
| <span data-i18n="modals.help.title">Help & Guide</span> |
| </h2> |
| <button id="help-modal-close" class="p-3 rounded-2xl glass hover:scale-105 transition-transform"> |
| <i class="fas fa-times text-gray-300 text-xl"></i> |
| </button> |
| </div> |
| |
| <div class="flex-1 overflow-y-auto pr-4 space-y-8 custom-scrollbar"> |
| <div> |
| <h3 class="text-2xl font-bold text-white mb-4" data-i18n="help.gettingStarted">Getting Started</h3> |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
| <div class="card-gradient p-6 rounded-2xl"> |
| <div class="w-12 h-12 rounded-full gradient-primary flex items-center justify-center mb-4"> |
| <i class="fas fa-users text-white"></i> |
| </div> |
| <h4 class="font-bold text-lg text-white mb-2" data-i18n="help.selectCharacters">Select Your Characters</h4> |
| <p class="text-gray-400" data-i18n="help.selectCharactersDesc"> |
| Go to Settings → My Characters and select all characters you own. This ensures the roulette only uses characters you have. |
| </p> |
| </div> |
| |
| <div class="card-gradient p-6 rounded-2xl"> |
| <div class="w-12 h-12 rounded-full gradient-accent flex items-center justify-center mb-4"> |
| <i class="fas fa-sliders-h text-white"></i> |
| </div> |
| <h4 class="font-bold text-lg text-white mb-2" data-i18n="help.configureSettings">Configure Settings</h4> |
| <p class="text-gray-400" data-i18n="help.configureSettingsDesc"> |
| Adjust team size, animation speed, and boss preferences in the settings panel to customize your experience. |
| </p> |
| </div> |
| </div> |
| </div> |
| |
| <div> |
| <h3 class="text-2xl font-bold text-white mb-4" data-i18n="help.features">Features</h3> |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> |
| <div class="card-gradient p-6 rounded-2xl"> |
| <h4 class="font-bold text-lg text-white mb-2 flex items-center"> |
| <i class="fas fa-users mr-2 text-purple-400"></i> |
| <span data-i18n="help.teamRoulette">Team Roulette</span> |
| </h4> |
| <p class="text-gray-400 text-sm" data-i18n="help.teamRouletteDesc"> |
| Generate random teams of 1-4 characters for new gameplay challenges. |
| </p> |
| </div> |
| |
| <div class="card-gradient p-6 rounded-2xl"> |
| <h4 class="font-bold text-lg text-white mb-2 flex items-center"> |
| <i class="fas fa-dragon mr-2 text-blue-400"></i> |
| <span data-i18n="help.bossRoulette">Boss Roulette</span> |
| </h4> |
| <p class="text-gray-400 text-sm" data-i18n="help.bossRouletteDesc"> |
| Get random world bosses, weekly bosses, or local legends to challenge. |
| </p> |
| </div> |
| |
| <div class="card-gradient p-6 rounded-2xl"> |
| <h4 class="font-bold text-lg text-white mb-2 flex items-center"> |
| <i class="fas fa-history mr-2 text-green-400"></i> |
| <span data-i18n="help.history">History & Stats</span> |
| </h4> |
| <p class="text-gray-400 text-sm" data-i18n="help.historyDesc"> |
| Track all your generated teams and bosses with detailed statistics. |
| </p> |
| </div> |
| </div> |
| </div> |
| |
| <div> |
| <h3 class="text-2xl font-bold text-white mb-4" data-i18n="help.tips">Tips & Tricks</h3> |
| <div class="space-y-4"> |
| <div class="flex items-start"> |
| <div class="w-8 h-8 rounded-full bg-green-900 flex items-center justify-center mr-4 flex-shrink-0"> |
| <i class="fas fa-lightbulb text-green-400"></i> |
| </div> |
| <p class="text-gray-400" data-i18n="help.tip1"> |
| Use the "My Characters" feature to limit the roulette to only characters you own for more realistic teams. |
| </p> |
| </div> |
| |
| <div class="flex items-start"> |
| <div class="w-8 h-8 rounded-full bg-blue-900 flex items-center justify-center mr-4 flex-shrink-0"> |
| <i class="fas fa-lightbulb text-blue-400"></i> |
| </div> |
| <p class="text-gray-400" data-i18n="help.tip2"> |
| Save your favorite teams with custom names for quick reference later. |
| </p> |
| </div> |
| |
| <div class="flex items-start"> |
| <div class="w-8 h-8 rounded-full bg-purple-900 flex items-center justify-center mr-4 flex-shrink-0"> |
| <i class="fas fa-lightbulb text-purple-400"></i> |
| </div> |
| <p class="text-gray-400" data-i18n="help.tip3"> |
| Enable sound effects for a more immersive spinning experience. |
| </p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <audio id="spin-sound" src="STREAMING-casino-game-wheel-spin-bosnow-1-00-07.mp3" preload="auto"></audio> |
| <audio id="reveal-sound" src="STREAMING-cinematic-transition-boom-piano-hit-tomas-herudek-4-4-00-08.mp3" preload="auto"></audio> |
| <audio id="click-sound" src="STREAMING-futuristic-ui-positive-selection-davies-aguirre-2-2-00-00.mp3" preload="auto"></audio> |
| <audio id="success-sound" src="STREAMING-ui-high-button-beep-epic-stock-media-3-3-00-00.mp3" preload="auto"></audio> |
|
|
| |
| <script> |
| |
| const translations = { |
| en: { |
| |
| "title": "Genshin Roulette", |
| "subtitle": "Random characters, bosses & legends for your Genshin Impact adventure", |
| "footer.disclaimer": "This is a fan-made project. Not affiliated with or endorsed by HoYoverse.", |
| "footer.version": "Version:", |
| "footer.characters": "Characters:", |
| "footer.bosses": "Bosses:", |
| |
| |
| "stats.characters": "Characters", |
| "stats.worldBosses": "World Bosses", |
| "stats.weeklyBosses": "Weekly Bosses", |
| "stats.legends": "Legends", |
| "stats.owned": "Owned:", |
| "stats.available": "Available:", |
| |
| |
| "tab.characters": "Character Team", |
| "tab.worldBosses": "World Bosses", |
| "tab.weeklyBosses": "Weekly Bosses", |
| "tab.localLegends": "Local Legends", |
| |
| |
| "sections.characters.title": "Character Team Roulette", |
| "sections.characters.subtitle": "Spin to get a random team of characters for your next adventure", |
| "sections.characters.config": "Team Configuration", |
| "sections.characters.configDesc": "Configure your team before spinning", |
| "sections.characters.teamSize": "Team Size", |
| "sections.characters.available": "Available", |
| "sections.characters.owned": "Owned", |
| "sections.worldBosses.title": "World Boss Roulette", |
| "sections.worldBosses.subtitle": "Challenge a random world boss and test your skills", |
| "sections.weeklyBosses.title": "Weekly Boss Roulette", |
| "sections.weeklyBosses.subtitle": "Face a random weekly boss for valuable rewards", |
| "sections.localLegends.title": "Local Legends Roulette", |
| "sections.localLegends.subtitle": "Discover and challenge random local legends across Teyvat", |
| |
| |
| "buttons.spinTeam": "Spin Team!", |
| "buttons.spinBoss": "Spin Boss!", |
| "buttons.spinWeeklyBoss": "Spin Weekly Boss!", |
| "buttons.spinLegend": "Spin Legend!", |
| "buttons.saveTeam": "Save Team", |
| "buttons.saveToHistory": "Save to History", |
| "buttons.share": "Share", |
| "buttons.fullHistory": "Full History", |
| "buttons.clearHistory": "Clear History", |
| "buttons.export": "Export", |
| "buttons.cancel": "Cancel", |
| "buttons.confirm": "Confirm", |
| "buttons.close": "Close", |
| "buttons.viewGuide": "View Guide", |
| |
| |
| "settings.title": "Roulette Settings", |
| "settings.sound": "Sound:", |
| "settings.animations": "Animations:", |
| "settings.teamSize": "Team Size", |
| "settings.animationSpeed": "Animation Speed", |
| "settings.duplicates": "Allow Duplicates", |
| "settings.duplicates.no": "No Duplicates", |
| "settings.duplicates.yes": "Allow Duplicates", |
| "settings.myCharacters": "My Characters", |
| "settings.myCharactersDesc": "Select characters you own. If none selected, all characters will be used.", |
| "settings.searchPlaceholder": "Search characters by name...", |
| "settings.selected": "Selected:", |
| "settings.selectAll": "Select All", |
| "settings.clearSelected": "Clear All", |
| "settings.progress": "Selection Progress", |
| "settings.bossSettings": "Boss & Legend Settings", |
| "settings.bossSettingsDesc": "Toggle which bosses and legends should be included in randomization", |
| "settings.category": "Category:", |
| "settings.categories.worldBosses": "World Bosses", |
| "settings.categories.weeklyBosses": "Weekly Bosses", |
| "settings.categories.localLegends": "Local Legends", |
| "settings.enableAll": "Enable All", |
| "settings.disableAll": "Disable All", |
| "settings.reset": "Reset to Default", |
| "settings.speed.fast": "Fast", |
| "settings.speed.medium": "Medium", |
| "settings.speed.slow": "Slow", |
| |
| |
| "filters.allElements": "All Elements", |
| |
| |
| "placeholders.characters": "Your random team will appear here", |
| "placeholders.charactersHint": "Click the 'Spin Team!' button to generate a random team", |
| "placeholders.teamName": "Give your team a name (optional)", |
| "placeholders.worldBoss": "Random world boss will appear here", |
| "placeholders.worldBossHint": "Click the 'Spin Boss!' button to generate a random world boss", |
| "placeholders.weeklyBoss": "Random weekly boss will appear here", |
| "placeholders.weeklyBossHint": "Click the 'Spin Weekly Boss!' button to generate a random weekly boss", |
| "placeholders.legend": "Random local legend will appear here", |
| "placeholders.legendHint": "Click the 'Spin Legend!' button to discover a random local legend", |
| "placeholders.history": "Your random selections history will appear here", |
| |
| |
| "history.title": "History", |
| "history.subtitle": "Your recent random selections", |
| "history.filters.all": "All", |
| "history.filters.teams": "Teams", |
| "history.filters.worldBosses": "World Bosses", |
| "history.filters.weeklyBosses": "Weekly Bosses", |
| "history.filters.legends": "Legends", |
| "history.stats.total": "Total Entries", |
| "history.stats.teams": "Teams", |
| "history.stats.bosses": "Bosses", |
| "history.stats.legends": "Legends", |
| |
| |
| "modals.confirm.title": "Confirmation", |
| "modals.confirm.message": "Are you sure you want to clear history?", |
| "modals.fullHistory.title": "Full History", |
| "modals.help.title": "Help & Guide", |
| |
| |
| "help.gettingStarted": "Getting Started", |
| "help.selectCharacters": "Select Your Characters", |
| "help.selectCharactersDesc": "Go to Settings → My Characters and select all characters you own. This ensures the roulette only uses characters you have.", |
| "help.configureSettings": "Configure Settings", |
| "help.configureSettingsDesc": "Adjust team size, animation speed, and boss preferences in the settings panel to customize your experience.", |
| "help.features": "Features", |
| "help.teamRoulette": "Team Roulette", |
| "help.teamRouletteDesc": "Generate random teams of 1-4 characters for new gameplay challenges.", |
| "help.bossRoulette": "Boss Roulette", |
| "help.bossRouletteDesc": "Get random world bosses, weekly bosses, or local legends to challenge.", |
| "help.history": "History & Stats", |
| "help.historyDesc": "Track all your generated teams and bosses with detailed statistics.", |
| "help.tips": "Tips & Tricks", |
| "help.tip1": "Use the 'My Characters' feature to limit the roulette to only characters you own for more realistic teams.", |
| "help.tip2": "Save your favorite teams with custom names for quick reference later.", |
| "help.tip3": "Enable sound effects for a more immersive spinning experience.", |
| |
| |
| "elements.pyro": "Pyro", |
| "elements.hydro": "Hydro", |
| "elements.anemo": "Anemo", |
| "elements.electro": "Electro", |
| "elements.dendro": "Dendro", |
| "elements.cryo": "Cryo", |
| "elements.geo": "Geo" |
| }, |
| ru: { |
| |
| "title": "Рулетка Genshin", |
| "subtitle": "Случайные персонажи, боссы и легенды для вашего приключения в Genshin Impact", |
| "footer.disclaimer": "Это фанатский проект. Не аффилирован и не одобрен HoYoverse.", |
| "footer.version": "Версия:", |
| "footer.characters": "Персонажи:", |
| "footer.bosses": "Боссы:", |
| |
| |
| "stats.characters": "Персонажи", |
| "stats.worldBosses": "Мировые боссы", |
| "stats.weeklyBosses": "Еженедельные боссы", |
| "stats.legends": "Легенды", |
| "stats.owned": "Ваши:", |
| "stats.available": "Доступно:", |
| |
| |
| "tab.characters": "Отряд персонажей", |
| "tab.worldBosses": "Мировые боссы", |
| "tab.weeklyBosses": "Еженедельные боссы", |
| "tab.localLegends": "Местные легенды", |
| |
| |
| "sections.characters.title": "Рандом отряда персонажей", |
| "sections.characters.subtitle": "Случайный подбор команды персонажей для вашего следующего приключения", |
| "sections.characters.config": "Настройка отряда", |
| "sections.characters.configDesc": "Настройте отряд перед вращением", |
| "sections.characters.teamSize": "Размер отряда", |
| "sections.characters.available": "Доступно", |
| "sections.characters.owned": "Ваши", |
| "sections.worldBosses.title": "Рандом мирового босса", |
| "sections.worldBosses.subtitle": "Сразитесь со случайным мировым боссом и проверьте свои навыки", |
| "sections.weeklyBosses.title": "Рандом еженедельного босса", |
| "sections.weeklyBosses.subtitle": "Сразитесь со случайным еженедельным боссом за ценные награды", |
| "sections.localLegends.title": "Рандом местной легенды", |
| "sections.localLegends.subtitle": "Откройте и сразитесь со случайными местными легендами по всему Тейвату", |
| |
| |
| "buttons.spinTeam": "Крутить отряд!", |
| "buttons.spinBoss": "Выбрать босса!", |
| "buttons.spinWeeklyBoss": "Выбрать босса!", |
| "buttons.spinLegend": "Выбрать легенду!", |
| "buttons.saveTeam": "Сохранить отряд", |
| "buttons.saveToHistory": "Сохранить в историю", |
| "buttons.share": "Поделиться", |
| "buttons.fullHistory": "Вся история", |
| "buttons.clearHistory": "Очистить историю", |
| "buttons.export": "Экспорт", |
| "buttons.cancel": "Отмена", |
| "buttons.confirm": "Подтвердить", |
| "buttons.close": "Закрыть", |
| "buttons.viewGuide": "Открыть гайд", |
| |
| |
| "settings.title": "Настройки рулетки", |
| "settings.sound": "Звук:", |
| "settings.animations": "Анимации:", |
| "settings.teamSize": "Количество в отряде", |
| "settings.animationSpeed": "Скорость анимации", |
| "settings.duplicates": "Повторения", |
| "settings.duplicates.no": "Без повторений", |
| "settings.duplicates.yes": "Разрешить повторения", |
| "settings.myCharacters": "Мои персонажи", |
| "settings.myCharactersDesc": "Выберите персонажей, которыми вы владеете. Если ни один не выбран, будут использоваться все персонажи.", |
| "settings.searchPlaceholder": "Поиск персонажей по имени...", |
| "settings.selected": "Выбрано:", |
| "settings.selectAll": "Выбрать всех", |
| "settings.clearSelected": "Очистить всё", |
| "settings.progress": "Прогресс выбора", |
| "settings.bossSettings": "Настройки боссов и легенд", |
| "settings.bossSettingsDesc": "Выберите, какие боссы и легенды должны участвовать в рандомизации", |
| "settings.category": "Категория:", |
| "settings.categories.worldBosses": "Мировые боссы", |
| "settings.categories.weeklyBosses": "Еженедельные боссы", |
| "settings.categories.localLegends": "Местные легенды", |
| "settings.enableAll": "Включить все", |
| "settings.disableAll": "Отключить все", |
| "settings.reset": "Сбросить", |
| "settings.speed.fast": "Быстрая", |
| "settings.speed.medium": "Средняя", |
| "settings.speed.slow": "Медленная", |
| |
| |
| "filters.allElements": "Все элементы", |
| |
| |
| "placeholders.characters": "Ваш случайный отряд появится здесь", |
| "placeholders.charactersHint": "Нажмите кнопку 'Крутить отряд!' для создания случайного отряда", |
| "placeholders.teamName": "Дайте название отряду (необязательно)", |
| "placeholders.worldBoss": "Случайный мировой босс появится здесь", |
| "placeholders.worldBossHint": "Нажмите кнопку 'Выбрать босса!' для выбора случайного мирового босса", |
| "placeholders.weeklyBoss": "Случайный еженедельный босс появится здесь", |
| "placeholders.weeklyBossHint": "Нажмите кнопку 'Выбрать босса!' для выбора случайного еженедельного босса", |
| "placeholders.legend": "Случайная местная легенда появится здесь", |
| "placeholders.legendHint": "Нажмите кнопку 'Выбрать легенду!' для выбора случайной местной легенды", |
| "placeholders.history": "История ваших случайных выборов будет отображаться здесь", |
| |
| |
| "history.title": "История", |
| "history.subtitle": "Ваши последние случайные выборы", |
| "history.filters.all": "Все", |
| "history.filters.teams": "Отряды", |
| "history.filters.worldBosses": "Мировые боссы", |
| "history.filters.weeklyBosses": "Еженедельные боссы", |
| "history.filters.legends": "Легенды", |
| "history.stats.total": "Всего записей", |
| "history.stats.teams": "Отряды", |
| "history.stats.bosses": "Боссы", |
| "history.stats.legends": "Легенды", |
| |
| |
| "modals.confirm.title": "Подтверждение", |
| "modals.confirm.message": "Вы уверены, что хотите очистить историю?", |
| "modals.fullHistory.title": "Вся история", |
| "modals.help.title": "Помощь и руководство", |
| |
| |
| "help.gettingStarted": "Начало работы", |
| "help.selectCharacters": "Выберите ваших персонажей", |
| "help.selectCharactersDesc": "Перейдите в Настройки → Мои персонажи и выберите всех персонажей, которыми вы владеете. Это гарантирует, что рулетка будет использовать только ваших персонажей.", |
| "help.configureSettings": "Настройте параметры", |
| "help.configureSettingsDesc": "Настройте размер отряда, скорость анимации и предпочтения по боссам в панели настроек для персонализации опыта.", |
| "help.features": "Возможности", |
| "help.teamRoulette": "Рандом отряда", |
| "help.teamRouletteDesc": "Создавайте случайные отряды из 1-4 персонажей для новых игровых испытаний.", |
| "help.bossRoulette": "Рандом боссов", |
| "help.bossRouletteDesc": "Получайте случайных мировых боссов, еженедельных боссов или местных легенд для сражений.", |
| "help.history": "История и статистика", |
| "help.historyDesc": "Отслеживайте все созданные отряды и боссов с подробной статистикой.", |
| "help.tips": "Советы и хитрости", |
| "help.tip1": "Используйте функцию 'Мои персонажи', чтобы ограничить рулетку только персонажами, которыми вы владеете, для более реалистичных отрядов.", |
| "help.tip2": "Сохраняйте любимые отряды с собственными названиями для быстрого доступа позже.", |
| "help.tip3": "Включите звуковые эффекты для более захватывающего опыта вращения.", |
| |
| |
| "elements.pyro": "Пиро", |
| "elements.hydro": "Гидро", |
| "elements.anemo": "Анемо", |
| "elements.electro": "Электро", |
| "elements.dendro": "Дендро", |
| "elements.cryo": "Крио", |
| "elements.geo": "Гео" |
| } |
| }; |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| |
| let currentLanguage = localStorage.getItem('genshinLanguage') || 'en'; |
| const languageToggle = document.getElementById('language-toggle'); |
| const languageLabel = document.getElementById('language-label'); |
| |
| function setLanguage(lang) { |
| currentLanguage = lang; |
| localStorage.setItem('genshinLanguage', lang); |
| languageLabel.textContent = lang === 'en' ? 'RU' : 'EN'; |
| |
| |
| document.querySelectorAll('[data-i18n]').forEach(element => { |
| const key = element.getAttribute('data-i18n'); |
| if (translations[lang][key]) { |
| element.textContent = translations[lang][key]; |
| } |
| }); |
| |
| |
| document.querySelectorAll('[data-i18n-placeholder]').forEach(element => { |
| const key = element.getAttribute('data-i18n-placeholder'); |
| if (translations[lang][key]) { |
| element.placeholder = translations[lang][key]; |
| } |
| }); |
| |
| |
| document.querySelectorAll('select option').forEach(option => { |
| const key = option.getAttribute('data-i18n'); |
| if (key && translations[lang][key]) { |
| option.textContent = translations[lang][key]; |
| } |
| }); |
| } |
| |
| languageToggle.addEventListener('click', () => { |
| const newLang = currentLanguage === 'en' ? 'ru' : 'en'; |
| setLanguage(newLang); |
| playSound('click'); |
| }); |
| |
| |
| setLanguage(currentLanguage); |
| |
| |
| |
| 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-koren-yumkazavr.webp" }, |
| { "name": "Кукузавр-тиран золотого пламени", "icon": "pic/WorldBoss/Prozhorlivyi_-Gornyi_-koren_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/show.png" }, |
| { "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/" }, |
| ]; |
| |
| |
| document.getElementById('total-characters').textContent = LOCAL_CHARACTERS.length; |
| document.getElementById('total-world-bosses').textContent = WORLD_BOSSES.length; |
| document.getElementById('total-weekly-bosses').textContent = WEEKLY_BOSSES.length; |
| document.getElementById('total-legends').textContent = LOCAL_LEGENDS.length; |
| document.getElementById('world-boss-count').textContent = WORLD_BOSSES.length; |
| document.getElementById('weekly-boss-count').textContent = WEEKLY_BOSSES.length; |
| document.getElementById('legend-count').textContent = LOCAL_LEGENDS.length; |
| document.getElementById('total-characters-count').textContent = LOCAL_CHARACTERS.length; |
| document.getElementById('available-chars').textContent = LOCAL_CHARACTERS.length; |
| |
| |
| function getOrCreateUID() { |
| let uid = localStorage.getItem('genshinRouletteUID'); |
| if (!uid) { |
| uid = 'UID-' + Math.random().toString(36).substring(2, 10).toUpperCase(); |
| localStorage.setItem('genshinRouletteUID', uid); |
| } |
| return uid; |
| } |
| |
| const currentUserUID = getOrCreateUID(); |
| document.getElementById('user-uid').textContent = currentUserUID; |
| |
| const config = { |
| teamSize: parseInt(localStorage.getItem(`${currentUserUID}_teamSize`)) || 4, |
| animationSpeed: localStorage.getItem(`${currentUserUID}_animationSpeed`) || 'medium', |
| category: localStorage.getItem('genshinCategory') || 'characters', |
| soundEnabled: localStorage.getItem('soundEnabled') !== 'false', |
| animationsEnabled: localStorage.getItem('animationsEnabled') !== 'false', |
| allowDuplicates: localStorage.getItem('allowDuplicates') === 'yes', |
| 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 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 statsToggle = document.getElementById('stats-toggle'); |
| const statsPanel = document.getElementById('stats-panel'); |
| const settingsToggle = document.getElementById('settings-toggle'); |
| const helpToggle = document.getElementById('help-toggle'); |
| const helpModal = document.getElementById('help-modal'); |
| const helpModalClose = document.getElementById('help-modal-close'); |
| const settingsPanel = document.getElementById('settings-panel'); |
| const teamSizeSelect = document.getElementById('team-size'); |
| const animationSpeedSelect = document.getElementById('animation-speed'); |
| const duplicatesSelect = document.getElementById('duplicates'); |
| const soundToggle = document.getElementById('sound-toggle'); |
| const animationsToggle = document.getElementById('animations-toggle'); |
| const confirmModal = document.getElementById('confirm-modal'); |
| const confirmModalContent = confirmModal.querySelector('.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 elementFilterSelect = document.getElementById('element-filter'); |
| const clearPlayerCharactersButton = document.getElementById('clear-player-characters'); |
| const selectAllCharactersButton = document.getElementById('select-all-characters'); |
| const saveTeamSection = document.getElementById('save-characters-section'); |
| const teamNameInput = document.getElementById('team-name-input'); |
| const saveTeamButton = document.getElementById('save-team-button'); |
| const shareTeamButton = document.getElementById('share-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 spinSound = document.getElementById('spin-sound'); |
| const revealSound = document.getElementById('reveal-sound'); |
| const clickSound = document.getElementById('click-sound'); |
| const successSound = document.getElementById('success-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 bossesSettingsContainer = document.getElementById('bosses-settings-container'); |
| const enableAllBossesButton = document.getElementById('enable-all-bosses'); |
| const disableAllBossesButton = document.getElementById('disable-all-bosses'); |
| const resetBossesButton = document.getElementById('reset-bosses'); |
| const selectedCountElement = document.getElementById('selected-count'); |
| const selectionProgressBar = document.getElementById('selection-progress-bar'); |
| const selectionPercentage = document.getElementById('selection-percentage'); |
| const ownedCharactersElement = document.getElementById('owned-characters'); |
| const currentTeamSizeElement = document.getElementById('current-team-size'); |
| const availableWorldBossesElement = document.getElementById('available-world-bosses'); |
| const availableWeeklyBossesElement = document.getElementById('available-weekly-bosses'); |
| const availableLegendsElement = document.getElementById('available-legends'); |
| const historyTotalElement = document.getElementById('history-total'); |
| const historyTeamsElement = document.getElementById('history-teams'); |
| const historyBossesElement = document.getElementById('history-bosses'); |
| const historyLegendsElement = document.getElementById('history-legends'); |
| const backToTopButton = document.getElementById('back-to-top'); |
| const exportHistoryButton = document.getElementById('export-history-button'); |
| |
| |
| const fullHistoryModal = document.getElementById('full-history-modal'); |
| const fullHistoryModalContent = fullHistoryModal.querySelector('.modal-content'); |
| const fullHistoryModalContentInner = document.getElementById('full-history-modal-content-inner'); |
| const fullHistoryModalCloseButton = document.getElementById('full-history-modal-close'); |
| const fullHistoryModalCloseBottomButton = document.getElementById('full-history-modal-close-bottom'); |
| |
| |
| function playSound(soundName) { |
| if (!config.soundEnabled) return; |
| |
| const soundMap = { |
| 'spin': spinSound, |
| 'reveal': revealSound, |
| 'click': clickSound, |
| 'success': successSound |
| }; |
| |
| const sound = soundMap[soundName]; |
| if (sound) { |
| sound.currentTime = 0; |
| sound.play().catch(e => console.log("Audio play failed:", e)); |
| } |
| } |
| |
| function createRipple(event) { |
| if (!config.animationsEnabled) return; |
| |
| const button = event.currentTarget; |
| const circle = document.createElement('span'); |
| const diameter = Math.max(button.clientWidth, button.clientHeight); |
| const radius = diameter / 2; |
| |
| circle.style.width = circle.style.height = `${diameter}px`; |
| circle.style.left = `${event.clientX - button.getBoundingClientRect().left - radius}px`; |
| circle.style.top = `${event.clientY - button.getBoundingClientRect().top - radius}px`; |
| circle.classList.add('ripple'); |
| |
| const ripple = button.getElementsByClassName('ripple')[0]; |
| if (ripple) ripple.remove(); |
| |
| button.appendChild(circle); |
| } |
| |
| function createParticles(element, count = 20, color = '#667eea') { |
| if (!config.animationsEnabled) return; |
| |
| for (let i = 0; i < count; i++) { |
| const particle = document.createElement('div'); |
| particle.className = 'particle'; |
| particle.style.backgroundColor = color; |
| particle.style.setProperty('--tx', `${Math.random() * 100 - 50}px`); |
| particle.style.setProperty('--ty', `${Math.random() * 100 - 50}px`); |
| particle.style.left = `${Math.random() * 100}%`; |
| particle.style.top = `${Math.random() * 100}%`; |
| element.appendChild(particle); |
| |
| setTimeout(() => particle.remove(), 2000); |
| } |
| } |
| |
| function createConfetti(count = 100) { |
| if (!config.animationsEnabled) return; |
| |
| const colors = ['#667eea', '#764ba2', '#f093fb', '#f5576c', '#4facfe', '#00f2fe', '#f6d365', '#fda085']; |
| |
| for (let i = 0; i < count; i++) { |
| const confetti = document.createElement('div'); |
| confetti.className = 'confetti'; |
| confetti.style.setProperty('--bg', colors[Math.floor(Math.random() * colors.length)]); |
| confetti.style.left = `${Math.random() * 100}%`; |
| confetti.style.width = `${Math.random() * 10 + 5}px`; |
| confetti.style.height = `${Math.random() * 10 + 5}px`; |
| confetti.style.animation = `confetti-fall ${Math.random() * 3 + 2}s linear forwards`; |
| document.body.appendChild(confetti); |
| |
| setTimeout(() => confetti.remove(), 5000); |
| } |
| } |
| |
| function updateStats() { |
| |
| ownedCharactersElement.textContent = playerCharacters.length; |
| document.getElementById('owned-characters').textContent = playerCharacters.length; |
| |
| |
| const availableWorldBosses = WORLD_BOSSES.filter(boss => |
| !config.excludedBosses['world-bosses'].includes(boss.name) |
| ).length; |
| const availableWeeklyBosses = WEEKLY_BOSSES.filter(boss => |
| !config.excludedBosses['weekly-bosses'].includes(boss.name) |
| ).length; |
| const availableLegends = LOCAL_LEGENDS.filter(legend => |
| !config.excludedBosses['local-legends'].includes(legend.name) |
| ).length; |
| |
| availableWorldBossesElement.textContent = availableWorldBosses; |
| availableWeeklyBossesElement.textContent = availableWeeklyBosses; |
| availableLegendsElement.textContent = availableLegends; |
| |
| |
| const totalHistory = historyTeams.length + historyBosses.length; |
| const teamHistory = historyTeams.length; |
| const bossHistory = historyBosses.filter(item => item.type !== 'legend').length; |
| const legendHistory = historyBosses.filter(item => item.type === 'legend').length; |
| |
| historyTotalElement.textContent = totalHistory; |
| historyTeamsElement.textContent = teamHistory; |
| historyBossesElement.textContent = bossHistory; |
| historyLegendsElement.textContent = legendHistory; |
| } |
| |
| function updateSelectionProgress() { |
| const selectedCount = playerCharacters.length; |
| const totalCount = allCharacters.length; |
| const percentage = totalCount > 0 ? Math.round((selectedCount / totalCount) * 100) : 0; |
| |
| selectedCountElement.querySelector('span:last-child').textContent = `${selectedCount}/${totalCount}`; |
| selectionProgressBar.style.width = `${percentage}%`; |
| selectionPercentage.textContent = `${percentage}%`; |
| |
| if (selectedCount > 0) { |
| clearPlayerCharactersButton.classList.remove('hidden'); |
| } else { |
| clearPlayerCharactersButton.classList.add('hidden'); |
| } |
| } |
| |
| |
| function switchTab(category) { |
| |
| document.querySelectorAll('.tab-btn').forEach(btn => { |
| btn.classList.remove('tab-active', 'gradient-primary', 'text-white'); |
| btn.classList.add('glass'); |
| }); |
| |
| |
| const activeTab = document.querySelector(`.tab-btn[data-category="${category}"]`); |
| activeTab.classList.remove('glass'); |
| activeTab.classList.add('tab-active', 'gradient-primary', 'text-white'); |
| |
| |
| const sections = { |
| 'characters': charactersSection, |
| 'world-bosses': worldBossesSection, |
| 'weekly-bosses': weeklyBossesSection, |
| 'local-legends': localLegendsSection |
| }; |
| |
| |
| Object.values(sections).forEach(section => { |
| section.classList.add('hidden'); |
| }); |
| |
| |
| const selectedSection = sections[category]; |
| selectedSection.classList.remove('hidden'); |
| |
| if (config.animationsEnabled) { |
| selectedSection.style.opacity = '0'; |
| selectedSection.style.transform = 'translateY(20px)'; |
| |
| requestAnimationFrame(() => { |
| selectedSection.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; |
| selectedSection.style.opacity = '1'; |
| selectedSection.style.transform = 'translateY(0)'; |
| }); |
| } |
| |
| |
| config.category = category; |
| localStorage.setItem('genshinCategory', category); |
| |
| playSound('click'); |
| } |
| |
| |
| function createCharacterCard(character, isSelectable = false, isSelected = false) { |
| const card = document.createElement('div'); |
| card.className = `character-card glass rounded-2xl p-4 text-center relative overflow-hidden ${isSelectable ? 'cursor-pointer' : ''}`; |
| |
| if (isSelected) { |
| card.classList.add('selected'); |
| } |
| |
| const elementName = translations[currentLanguage][`elements.${character.element.toLowerCase()}`] || character.element; |
| |
| card.innerHTML = ` |
| <div class="relative mb-3 mx-auto character-avatar-lg"> |
| <img src="${character.icon}" alt="${character.name}" |
| class="w-full h-full rounded-full object-cover border-4 ${isSelected ? 'border-purple-500' : 'border-gray-700'}"> |
| ${character.element ? ` |
| <div class="element-icon tooltip" data-tooltip="${elementName}"> |
| <img src="pic/Elements/Element_${character.element}.webp" |
| alt="${character.element}" |
| class="w-full h-full rounded-full"> |
| </div> |
| ` : ''} |
| ${isSelectable && isSelected ? ` |
| <div class="absolute top-0 right-0 w-6 h-6 rounded-full bg-purple-500 flex items-center justify-center"> |
| <i class="fas fa-check text-white text-xs"></i> |
| </div> |
| ` : ''} |
| </div> |
| <p class="font-semibold text-sm text-white truncate">${character.name}</p> |
| `; |
| |
| if (isSelectable) { |
| card.addEventListener('click', function(e) { |
| if (!e.target.closest('.tooltip')) { |
| togglePlayerCharacter(character); |
| playSound('click'); |
| } |
| }); |
| } |
| |
| return card; |
| } |
| |
| function createBossCard(boss, isLegend = false) { |
| const card = document.createElement('div'); |
| card.className = `glass rounded-2xl p-6 text-center card-hover ${isLegend ? 'border-l-4 border-purple-500' : 'border-l-4 border-blue-500'}`; |
| |
| const isClickable = boss.guideUrl && boss.guideUrl !== "#"; |
| |
| card.innerHTML = ` |
| <img src="${boss.icon}" alt="${boss.name}" |
| class="boss-avatar rounded-full object-cover mx-auto mb-4 border-4 ${isLegend ? 'border-purple-500' : 'border-blue-500'}"> |
| <h3 class="font-bold text-lg mb-2 text-white">${boss.name}</h3> |
| ${isClickable ? ` |
| <a href="${boss.guideUrl}" target="_blank" |
| class="inline-block mt-3 px-4 py-2 rounded-xl gradient-secondary text-white text-sm font-semibold hover:scale-105 transition-transform"> |
| <i class="fas fa-external-link-alt mr-2"></i> |
| <span data-i18n="buttons.viewGuide">View Guide</span> |
| </a> |
| ` : ''} |
| `; |
| |
| return card; |
| } |
| |
| |
| function renderPlayerCharactersSelection(filter = '', elementFilter = 'all') { |
| playerCharactersSelection.innerHTML = ''; |
| |
| let filteredCharacters = allCharacters.filter(char => { |
| const nameMatch = char.name.toLowerCase().includes(filter.toLowerCase()); |
| const elementMatch = elementFilter === 'all' || char.element === elementFilter; |
| return nameMatch && elementMatch; |
| }); |
| |
| |
| filteredCharacters.sort((a, b) => { |
| const aSelected = playerCharacters.some(c => c.name === a.name); |
| const bSelected = playerCharacters.some(c => c.name === b.name); |
| |
| if (aSelected && !bSelected) return -1; |
| if (!aSelected && bSelected) return 1; |
| |
| return a.name.localeCompare(b.name); |
| }); |
| |
| filteredCharacters.forEach(char => { |
| const isSelected = playerCharacters.some(c => c.name === char.name); |
| playerCharactersSelection.appendChild(createCharacterCard(char, true, isSelected)); |
| }); |
| |
| updateSelectionProgress(); |
| } |
| |
| 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, elementFilterSelect.value); |
| updateStats(); |
| } |
| |
| function selectAllCharacters() { |
| playerCharacters = [...allCharacters]; |
| localStorage.setItem(`${currentUserUID}_playerCharacters`, JSON.stringify(playerCharacters)); |
| renderPlayerCharactersSelection(characterSearchInput.value, elementFilterSelect.value); |
| updateStats(); |
| playSound('success'); |
| } |
| |
| function clearPlayerCharacters() { |
| playerCharacters = []; |
| localStorage.removeItem(`${currentUserUID}_playerCharacters`); |
| renderPlayerCharactersSelection(characterSearchInput.value, elementFilterSelect.value); |
| updateStats(); |
| playSound('click'); |
| } |
| |
| |
| function renderBossSettings(category = 'world-bosses') { |
| bossesSettingsContainer.innerHTML = ''; |
| |
| let bosses = []; |
| let categoryName = ''; |
| |
| switch(category) { |
| case 'world-bosses': |
| bosses = WORLD_BOSSES; |
| categoryName = translations[currentLanguage]['settings.categories.worldBosses']; |
| break; |
| case 'weekly-bosses': |
| bosses = WEEKLY_BOSSES; |
| categoryName = translations[currentLanguage]['settings.categories.weeklyBosses']; |
| break; |
| case 'local-legends': |
| bosses = LOCAL_LEGENDS; |
| categoryName = translations[currentLanguage]['settings.categories.localLegends']; |
| break; |
| } |
| |
| |
| document.querySelectorAll('.boss-category-btn').forEach(btn => { |
| btn.classList.remove('active', 'gradient-primary', 'text-white'); |
| btn.classList.add('glass'); |
| }); |
| |
| const activeBtn = document.querySelector(`.boss-category-btn[data-boss-category="${category}"]`); |
| activeBtn.classList.remove('glass'); |
| activeBtn.classList.add('active', 'gradient-primary', 'text-white'); |
| |
| |
| bosses.forEach(boss => { |
| const isExcluded = config.excludedBosses[category].includes(boss.name); |
| |
| const bossToggle = document.createElement('div'); |
| bossToggle.className = 'card-gradient p-4 rounded-2xl flex items-center justify-between'; |
| |
| bossToggle.innerHTML = ` |
| <div class="flex items-center"> |
| <div class="w-16 h-16 rounded-full overflow-hidden mr-4 flex-shrink-0"> |
| <img src="${boss.icon}" alt="${boss.name}" class="w-full h-full object-cover"> |
| </div> |
| <div> |
| <h4 class="font-semibold text-white text-sm">${boss.name}</h4> |
| <p class="text-xs text-gray-400">${categoryName}</p> |
| </div> |
| </div> |
| <label class="toggle-switch"> |
| <input type="checkbox" class="boss-toggle-checkbox" ${isExcluded ? '' : 'checked'}> |
| <span class="toggle-slider"></span> |
| </label> |
| `; |
| |
| 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)); |
| updateStats(); |
| playSound('click'); |
| }); |
| |
| bossesSettingsContainer.appendChild(bossToggle); |
| }); |
| } |
| |
| function enableAllBosses() { |
| const activeCategory = document.querySelector('.boss-category-btn.active').dataset.bossCategory; |
| config.excludedBosses[activeCategory] = []; |
| localStorage.setItem(`${currentUserUID}_excludedBosses`, JSON.stringify(config.excludedBosses)); |
| renderBossSettings(activeCategory); |
| updateStats(); |
| playSound('success'); |
| } |
| |
| function disableAllBosses() { |
| const activeCategory = document.querySelector('.boss-category-btn.active').dataset.bossCategory; |
| let bosses = []; |
| |
| switch(activeCategory) { |
| case 'world-bosses': bosses = WORLD_BOSSES; break; |
| case 'weekly-bosses': bosses = WEEKLY_BOSSES; break; |
| case 'local-legends': bosses = LOCAL_LEGENDS; break; |
| } |
| |
| config.excludedBosses[activeCategory] = bosses.map(boss => boss.name); |
| localStorage.setItem(`${currentUserUID}_excludedBosses`, JSON.stringify(config.excludedBosses)); |
| renderBossSettings(activeCategory); |
| updateStats(); |
| playSound('click'); |
| } |
| |
| function resetBosses() { |
| config.excludedBosses = { |
| 'world-bosses': [], |
| 'weekly-bosses': [], |
| 'local-legends': [] |
| }; |
| localStorage.removeItem(`${currentUserUID}_excludedBosses`); |
| renderBossSettings('world-bosses'); |
| updateStats(); |
| playSound('click'); |
| } |
| |
| |
| async function spinCharacters() { |
| if (isSpinning) return; |
| |
| const availableCharsForSpin = playerCharacters.length > 0 ? playerCharacters : allCharacters; |
| const teamSize = parseInt(teamSizeSelect.value); |
| |
| if (availableCharsForSpin.length < teamSize) { |
| alert(translations[currentLanguage]['sections.characters.notEnough'] || `Not enough characters to form a team of ${teamSize}. Available: ${availableCharsForSpin.length}`); |
| return; |
| } |
| |
| isSpinning = true; |
| spinCharactersButton.disabled = true; |
| spinCharactersButton.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Spinning...'; |
| |
| charactersResult.innerHTML = ''; |
| charactersPlaceholder.classList.add('hidden'); |
| charactersLoader.classList.remove('hidden'); |
| saveTeamSection.classList.add('hidden'); |
| |
| playSound('spin'); |
| |
| let charsPool = config.allowDuplicates ? [...availableCharsForSpin] : [...availableCharsForSpin]; |
| const selectedTeam = []; |
| |
| |
| let spinDurationMs; |
| switch (animationSpeedSelect.value) { |
| case 'fast': spinDurationMs = 1000; break; |
| case 'slow': spinDurationMs = 4000; break; |
| default: spinDurationMs = 2000; break; |
| } |
| |
| const individualSpinTime = spinDurationMs / teamSize; |
| |
| |
| for (let i = 0; i < teamSize; i++) { |
| const placeholder = document.createElement('div'); |
| placeholder.className = 'flex flex-col items-center p-4'; |
| placeholder.innerHTML = ` |
| <div class="w-24 h-24 rounded-full bg-gradient-to-r from-purple-500 to-pink-500 flex items-center justify-center mb-3"> |
| <i class="fas fa-question text-white text-3xl"></i> |
| </div> |
| <div class="w-32 h-4 bg-gradient-to-r from-gray-600 to-gray-700 rounded-full"></div> |
| `; |
| charactersResult.appendChild(placeholder); |
| } |
| |
| |
| for (let i = 0; i < teamSize; i++) { |
| await new Promise(resolve => setTimeout(resolve, individualSpinTime)); |
| |
| let selectedChar; |
| if (config.allowDuplicates) { |
| selectedChar = charsPool[Math.floor(Math.random() * charsPool.length)]; |
| } else { |
| const selectedIndex = Math.floor(Math.random() * charsPool.length); |
| selectedChar = charsPool.splice(selectedIndex, 1)[0]; |
| } |
| |
| selectedTeam.push(selectedChar); |
| } |
| |
| |
| charactersLoader.classList.add('hidden'); |
| charactersResult.innerHTML = ''; |
| |
| |
| const gridContainer = document.createElement('div'); |
| gridContainer.className = 'grid grid-cols-2 md:grid-cols-4 gap-6 w-full'; |
| |
| for (let i = 0; i < selectedTeam.length; i++) { |
| const char = selectedTeam[i]; |
| const card = createCharacterCard(char); |
| card.classList.add('reveal-animation'); |
| card.style.animationDelay = `${i * 0.1}s`; |
| gridContainer.appendChild(card); |
| |
| createParticles(card, 10, '#667eea'); |
| } |
| |
| charactersResult.appendChild(gridContainer); |
| playSound('reveal'); |
| createConfetti(50); |
| |
| |
| spinCharactersButton.disabled = false; |
| spinCharactersButton.innerHTML = '<i class="fas fa-dice-d20 mr-3"></i> Spin Team!'; |
| isSpinning = false; |
| |
| saveTeamSection.classList.remove('hidden'); |
| shareTeamButton.classList.remove('hidden'); |
| teamNameInput.value = ''; |
| currentGeneratedTeam = selectedTeam; |
| |
| |
| currentTeamSizeElement.textContent = teamSize; |
| } |
| |
| async function spinWorldBoss() { |
| if (isSpinning) return; |
| |
| isSpinning = true; |
| spinWorldBossButton.disabled = true; |
| spinWorldBossButton.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Spinning...'; |
| |
| worldBossResult.innerHTML = ''; |
| worldBossPlaceholder.classList.add('hidden'); |
| worldBossLoader.classList.remove('hidden'); |
| saveWorldBossSection.classList.add('hidden'); |
| |
| playSound('spin'); |
| |
| const availableBosses = WORLD_BOSSES.filter(boss => |
| !config.excludedBosses['world-bosses'].includes(boss.name) |
| ); |
| |
| if (availableBosses.length === 0) { |
| worldBossResult.innerHTML = ` |
| <div class="text-center p-8"> |
| <div class="text-5xl mb-4">⚠️</div> |
| <h3 class="text-xl font-bold text-white mb-2">No Bosses Available</h3> |
| <p class="text-gray-400">Enable some bosses in the settings to spin!</p> |
| </div> |
| `; |
| worldBossLoader.classList.add('hidden'); |
| spinWorldBossButton.disabled = false; |
| spinWorldBossButton.innerHTML = '<i class="fas fa-dragon mr-3"></i> Spin Boss!'; |
| isSpinning = false; |
| return; |
| } |
| |
| await new Promise(resolve => setTimeout(resolve, 1500)); |
| |
| const randomBoss = availableBosses[Math.floor(Math.random() * availableBosses.length)]; |
| |
| worldBossLoader.classList.add('hidden'); |
| worldBossResult.innerHTML = ''; |
| |
| const card = createBossCard(randomBoss, false); |
| card.classList.add('reveal-animation'); |
| worldBossResult.appendChild(card); |
| |
| playSound('reveal'); |
| createParticles(card, 15, '#4facfe'); |
| |
| spinWorldBossButton.disabled = false; |
| spinWorldBossButton.innerHTML = '<i class="fas fa-dragon mr-3"></i> Spin Boss!'; |
| isSpinning = false; |
| |
| saveWorldBossSection.classList.remove('hidden'); |
| currentGeneratedBoss = randomBoss; |
| isWorldBoss = true; |
| isLegend = false; |
| } |
| |
| async function spinWeeklyBoss() { |
| if (isSpinning) return; |
| |
| isSpinning = true; |
| spinWeeklyBossButton.disabled = true; |
| spinWeeklyBossButton.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Spinning...'; |
| |
| weeklyBossResult.innerHTML = ''; |
| weeklyBossPlaceholder.classList.add('hidden'); |
| weeklyBossLoader.classList.remove('hidden'); |
| saveWeeklyBossSection.classList.add('hidden'); |
| |
| playSound('spin'); |
| |
| const availableBosses = WEEKLY_BOSSES.filter(boss => |
| !config.excludedBosses['weekly-bosses'].includes(boss.name) |
| ); |
| |
| if (availableBosses.length === 0) { |
| weeklyBossResult.innerHTML = ` |
| <div class="text-center p-8"> |
| <div class="text-5xl mb-4">⚠️</div> |
| <h3 class="text-xl font-bold text-white mb-2">No Bosses Available</h3> |
| <p class="text-gray-400">Enable some bosses in the settings to spin!</p> |
| </div> |
| `; |
| weeklyBossLoader.classList.add('hidden'); |
| spinWeeklyBossButton.disabled = false; |
| spinWeeklyBossButton.innerHTML = '<i class="fas fa-crown mr-3"></i> Spin Weekly Boss!'; |
| isSpinning = false; |
| return; |
| } |
| |
| await new Promise(resolve => setTimeout(resolve, 1500)); |
| |
| const randomBoss = availableBosses[Math.floor(Math.random() * availableBosses.length)]; |
| |
| weeklyBossLoader.classList.add('hidden'); |
| weeklyBossResult.innerHTML = ''; |
| |
| const card = createBossCard(randomBoss, false); |
| card.classList.add('reveal-animation'); |
| weeklyBossResult.appendChild(card); |
| |
| playSound('reveal'); |
| createParticles(card, 15, '#f6d365'); |
| |
| spinWeeklyBossButton.disabled = false; |
| spinWeeklyBossButton.innerHTML = '<i class="fas fa-crown mr-3"></i> Spin Weekly Boss!'; |
| isSpinning = false; |
| |
| saveWeeklyBossSection.classList.remove('hidden'); |
| currentGeneratedBoss = randomBoss; |
| isWorldBoss = false; |
| isLegend = false; |
| } |
| |
| async function spinLocalLegend() { |
| if (isSpinning) return; |
| |
| isSpinning = true; |
| spinLegendButton.disabled = true; |
| spinLegendButton.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Spinning...'; |
| |
| legendResult.innerHTML = ''; |
| legendPlaceholder.classList.add('hidden'); |
| legendLoader.classList.remove('hidden'); |
| saveLegendSection.classList.add('hidden'); |
| |
| playSound('spin'); |
| |
| const availableLegends = LOCAL_LEGENDS.filter(legend => |
| !config.excludedBosses['local-legends'].includes(legend.name) |
| ); |
| |
| if (availableLegends.length === 0) { |
| legendResult.innerHTML = ` |
| <div class="text-center p-8"> |
| <div class="text-5xl mb-4">⚠️</div> |
| <h3 class="text-xl font-bold text-white mb-2">No Legends Available</h3> |
| <p class="text-gray-400">Enable some legends in the settings to spin!</p> |
| </div> |
| `; |
| legendLoader.classList.add('hidden'); |
| spinLegendButton.disabled = false; |
| spinLegendButton.innerHTML = '<i class="fas fa-landmark mr-3"></i> Spin Legend!'; |
| isSpinning = false; |
| return; |
| } |
| |
| await new Promise(resolve => setTimeout(resolve, 1500)); |
| |
| const randomLegend = availableLegends[Math.floor(Math.random() * availableLegends.length)]; |
| |
| legendLoader.classList.add('hidden'); |
| legendResult.innerHTML = ''; |
| |
| const card = createBossCard(randomLegend, true); |
| card.classList.add('reveal-animation'); |
| legendResult.appendChild(card); |
| |
| playSound('reveal'); |
| createParticles(card, 15, '#a8edea'); |
| |
| spinLegendButton.disabled = false; |
| spinLegendButton.innerHTML = '<i class="fas fa-landmark mr-3"></i> Spin Legend!'; |
| isSpinning = false; |
| |
| saveLegendSection.classList.remove('hidden'); |
| currentGeneratedBoss = randomLegend; |
| isWorldBoss = false; |
| isLegend = true; |
| } |
| |
| |
| function saveTeamToHistory(team, name = '') { |
| const teamData = { |
| date: new Date().toISOString(), |
| characters: team, |
| name: name, |
| type: 'team' |
| }; |
| |
| historyTeams.unshift(teamData); |
| localStorage.setItem(`${currentUserUID}_genshinTeamsHistory`, JSON.stringify(historyTeams)); |
| renderHistory(); |
| updateStats(); |
| playSound('success'); |
| } |
| |
| function saveBossToHistory(boss, isWorld, isLegendType = false) { |
| const bossData = { |
| date: new Date().toISOString(), |
| name: boss.name, |
| icon: boss.icon, |
| type: isLegendType ? 'legend' : (isWorld ? 'world-boss' : 'weekly-boss'), |
| guideUrl: boss.guideUrl || null |
| }; |
| |
| historyBosses.unshift(bossData); |
| localStorage.setItem(`${currentUserUID}_genshinBossesHistory`, JSON.stringify(historyBosses)); |
| renderHistory(); |
| updateStats(); |
| playSound('success'); |
| } |
| |
| function renderHistory(filter = 'all') { |
| historyContent.innerHTML = ''; |
| |
| const allHistory = [...historyTeams, ...historyBosses] |
| .sort((a, b) => new Date(b.date) - new Date(a.date)) |
| .filter(item => { |
| if (filter === 'all') return true; |
| if (filter === 'team') return item.type === 'team'; |
| if (filter === 'world-boss') return item.type === 'world-boss'; |
| if (filter === 'weekly-boss') return item.type === 'weekly-boss'; |
| if (filter === 'legend') return item.type === 'legend'; |
| return true; |
| }) |
| .slice(0, 6); |
| |
| 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'); |
| |
| allHistory.forEach((item, index) => { |
| const historyItem = document.createElement('div'); |
| historyItem.className = `history-item glass rounded-2xl p-6 ${item.type}`; |
| |
| let title = ''; |
| let content = ''; |
| let icon = ''; |
| let badgeClass = ''; |
| let badgeText = ''; |
| |
| if (item.type === 'team') { |
| const teamName = item.name ? `"${item.name}"` : 'Unnamed Team'; |
| title = `${teamName} - ${new Date(item.date).toLocaleString()}`; |
| badgeClass = 'badge-team'; |
| badgeText = translations[currentLanguage]['history.filters.teams']; |
| icon = '<i class="fas fa-users mr-2"></i>'; |
| |
| content = ` |
| <div class="flex flex-wrap gap-3 mt-4"> |
| ${item.characters.map(char => ` |
| <div class="flex flex-col items-center"> |
| <img src="${char.icon}" alt="${char.name}" |
| class="w-16 h-16 rounded-full object-cover border-2 border-gray-700"> |
| <span class="text-xs mt-1 text-gray-300">${char.name}</span> |
| </div> |
| `).join('')} |
| </div> |
| `; |
| } else { |
| const typeText = item.type === 'legend' ? |
| translations[currentLanguage]['history.filters.legends'] : |
| (item.type === 'world-boss' ? |
| translations[currentLanguage]['history.filters.worldBosses'] : |
| translations[currentLanguage]['history.filters.weeklyBosses']); |
| |
| title = `${item.name} - ${new Date(item.date).toLocaleString()}`; |
| badgeClass = item.type === 'legend' ? 'badge-legend' : |
| (item.type === 'world-boss' ? 'badge-boss' : 'badge-team'); |
| badgeText = typeText; |
| icon = item.type === 'legend' ? '<i class="fas fa-landmark mr-2"></i>' : |
| (item.type === 'world-boss' ? '<i class="fas fa-dragon mr-2"></i>' : '<i class="fas fa-crown mr-2"></i>'); |
| |
| content = ` |
| <div class="flex items-center justify-center mt-4"> |
| <img src="${item.icon}" alt="${item.name}" |
| class="w-24 h-24 rounded-full object-cover border-4 ${item.type === 'legend' ? 'border-purple-500' : 'border-blue-500'}"> |
| </div> |
| `; |
| } |
| |
| historyItem.innerHTML = ` |
| <div class="flex justify-between items-start mb-4"> |
| <div> |
| <div class="flex items-center mb-2"> |
| <span class="${badgeClass} badge flex items-center text-xs"> |
| ${icon}${badgeText} |
| </span> |
| </div> |
| <h3 class="font-bold text-white">${title}</h3> |
| </div> |
| <button class="delete-history-item p-2 rounded-lg glass hover:bg-red-500 hover:text-white transition-colors" data-index="${index}" data-type="${item.type}"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| ${content} |
| `; |
| |
| historyContent.appendChild(historyItem); |
| |
| |
| const deleteBtn = historyItem.querySelector('.delete-history-item'); |
| deleteBtn.addEventListener('click', function() { |
| const idx = parseInt(this.dataset.index); |
| const type = this.dataset.type; |
| |
| if (type === 'team') { |
| historyTeams.splice(idx, 1); |
| localStorage.setItem(`${currentUserUID}_genshinTeamsHistory`, JSON.stringify(historyTeams)); |
| } else { |
| historyBosses.splice(idx, 1); |
| localStorage.setItem(`${currentUserUID}_genshinBossesHistory`, JSON.stringify(historyBosses)); |
| } |
| |
| renderHistory(filter); |
| updateStats(); |
| playSound('click'); |
| }); |
| }); |
| } |
| } |
| |
| function clearHistory() { |
| historyTeams = []; |
| historyBosses = []; |
| localStorage.removeItem(`${currentUserUID}_genshinTeamsHistory`); |
| localStorage.removeItem(`${currentUserUID}_genshinBossesHistory`); |
| renderHistory(); |
| updateStats(); |
| hideConfirmModal(); |
| playSound('success'); |
| } |
| |
| 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 = ` |
| <div class="text-center py-12"> |
| <div class="text-5xl mb-4">📜</div> |
| <h3 class="text-xl font-bold text-white mb-2">No History</h3> |
| <p class="text-gray-400">Your history will appear here after you save some results.</p> |
| </div> |
| `; |
| } else { |
| allHistory.forEach((item, index) => { |
| const historyItem = document.createElement('div'); |
| historyItem.className = `glass rounded-2xl p-6 mb-4 ${item.type}`; |
| |
| let title = ''; |
| let content = ''; |
| let icon = ''; |
| let badgeClass = ''; |
| let badgeText = ''; |
| |
| if (item.type === 'team') { |
| const teamName = item.name ? `"${item.name}"` : 'Unnamed Team'; |
| title = `${teamName}`; |
| badgeClass = 'badge-team'; |
| badgeText = translations[currentLanguage]['history.filters.teams']; |
| icon = '<i class="fas fa-users mr-2"></i>'; |
| |
| content = ` |
| <div class="flex overflow-x-auto gap-3 mt-4 pb-2 custom-scrollbar"> |
| ${item.characters.map(char => ` |
| <div class="flex-shrink-0 flex flex-col items-center"> |
| <img src="${char.icon}" alt="${char.name}" |
| class="w-20 h-20 rounded-full object-cover border-2 border-gray-700"> |
| <span class="text-xs mt-1 text-gray-300 text-center">${char.name}</span> |
| </div> |
| `).join('')} |
| </div> |
| `; |
| } else { |
| const typeText = item.type === 'legend' ? |
| translations[currentLanguage]['history.filters.legends'] : |
| (item.type === 'world-boss' ? |
| translations[currentLanguage]['history.filters.worldBosses'] : |
| translations[currentLanguage]['history.filters.weeklyBosses']); |
| |
| title = `${item.name}`; |
| badgeClass = item.type === 'legend' ? 'badge-legend' : |
| (item.type === 'world-boss' ? 'badge-boss' : 'badge-team'); |
| badgeText = typeText; |
| icon = item.type === 'legend' ? '<i class="fas fa-landmark mr-2"></i>' : |
| (item.type === 'world-boss' ? '<i class="fas fa-dragon mr-2"></i>' : '<i class="fas fa-crown mr-2"></i>'); |
| |
| content = ` |
| <div class="flex items-center justify-center mt-4"> |
| <img src="${item.icon}" alt="${item.name}" |
| class="w-28 h-28 rounded-full object-cover border-4 ${item.type === 'legend' ? 'border-purple-500' : 'border-blue-500'}"> |
| </div> |
| `; |
| } |
| |
| const date = new Date(item.date).toLocaleString(); |
| |
| historyItem.innerHTML = ` |
| <div class="flex justify-between items-start mb-4"> |
| <div> |
| <div class="flex items-center mb-2"> |
| <span class="${badgeClass} badge flex items-center text-xs"> |
| ${icon}${badgeText} |
| </span> |
| </div> |
| <h3 class="font-bold text-white">${title}</h3> |
| <p class="text-sm text-gray-400 mt-1">${date}</p> |
| </div> |
| </div> |
| ${content} |
| `; |
| |
| fullHistoryModalContentInner.appendChild(historyItem); |
| }); |
| } |
| |
| fullHistoryModal.classList.remove('hidden'); |
| setTimeout(() => { |
| fullHistoryModalContent.classList.add('modal-content'); |
| }, 10); |
| } |
| |
| function hideFullHistoryModal() { |
| fullHistoryModal.classList.add('hidden'); |
| fullHistoryModalContent.classList.remove('modal-content'); |
| } |
| |
| function exportHistory() { |
| const allHistory = [...historyTeams, ...historyBosses] |
| .sort((a, b) => new Date(b.date) - new Date(a.date)); |
| |
| const exportData = { |
| exportedAt: new Date().toISOString(), |
| uid: currentUserUID, |
| history: allHistory |
| }; |
| |
| const dataStr = JSON.stringify(exportData, null, 2); |
| const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); |
| |
| const exportFileDefaultName = `genshin-roulette-history-${new Date().toISOString().split('T')[0]}.json`; |
| |
| const linkElement = document.createElement('a'); |
| linkElement.setAttribute('href', dataUri); |
| linkElement.setAttribute('download', exportFileDefaultName); |
| linkElement.click(); |
| |
| playSound('success'); |
| } |
| |
| |
| function showConfirmModal(message, callback) { |
| confirmModalMessage.textContent = message; |
| confirmModal.classList.remove('hidden'); |
| |
| 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); |
| |
| playSound('click'); |
| } |
| |
| function hideConfirmModal() { |
| confirmModal.classList.add('hidden'); |
| } |
| |
| function showHelpModal() { |
| helpModal.classList.remove('hidden'); |
| playSound('click'); |
| } |
| |
| function hideHelpModal() { |
| helpModal.classList.add('hidden'); |
| playSound('click'); |
| } |
| |
| |
| |
| statsToggle.addEventListener('click', () => { |
| statsPanel.classList.toggle('open'); |
| playSound('click'); |
| }); |
| statsToggle.addEventListener('mousedown', createRipple); |
| |
| |
| settingsToggle.addEventListener('click', () => { |
| settingsPanel.classList.toggle('open'); |
| playSound('click'); |
| }); |
| settingsToggle.addEventListener('mousedown', createRipple); |
| |
| |
| helpToggle.addEventListener('click', showHelpModal); |
| helpToggle.addEventListener('mousedown', createRipple); |
| helpModalClose.addEventListener('click', hideHelpModal); |
| helpModal.addEventListener('click', (e) => { |
| if (e.target === helpModal) hideHelpModal(); |
| }); |
| |
| |
| document.querySelectorAll('.tab-btn').forEach(btn => { |
| btn.addEventListener('click', function() { |
| switchTab(this.dataset.category); |
| }); |
| btn.addEventListener('mousedown', createRipple); |
| }); |
| |
| |
| document.querySelectorAll('.boss-category-btn').forEach(btn => { |
| btn.addEventListener('click', function() { |
| renderBossSettings(this.dataset.bossCategory); |
| }); |
| btn.addEventListener('mousedown', createRipple); |
| }); |
| |
| |
| document.querySelectorAll('.history-filter-btn').forEach(btn => { |
| btn.addEventListener('click', function() { |
| document.querySelectorAll('.history-filter-btn').forEach(b => b.classList.remove('active')); |
| this.classList.add('active'); |
| renderHistory(this.dataset.historyFilter); |
| playSound('click'); |
| }); |
| }); |
| |
| |
| spinCharactersButton.addEventListener('click', spinCharacters); |
| spinCharactersButton.addEventListener('mousedown', createRipple); |
| |
| spinWorldBossButton.addEventListener('click', spinWorldBoss); |
| spinWorldBossButton.addEventListener('mousedown', createRipple); |
| |
| spinWeeklyBossButton.addEventListener('click', spinWeeklyBoss); |
| spinWeeklyBossButton.addEventListener('mousedown', createRipple); |
| |
| spinLegendButton.addEventListener('click', spinLocalLegend); |
| spinLegendButton.addEventListener('mousedown', createRipple); |
| |
| |
| saveTeamButton.addEventListener('click', () => { |
| const teamName = teamNameInput.value.trim(); |
| if (currentGeneratedTeam.length > 0) { |
| saveTeamToHistory(currentGeneratedTeam, teamName); |
| saveTeamSection.classList.add('hidden'); |
| shareTeamButton.classList.add('hidden'); |
| } |
| }); |
| saveTeamButton.addEventListener('mousedown', createRipple); |
| |
| saveWorldBossButton.addEventListener('click', () => { |
| if (currentGeneratedBoss) { |
| saveBossToHistory(currentGeneratedBoss, true); |
| saveWorldBossSection.classList.add('hidden'); |
| } |
| }); |
| saveWorldBossButton.addEventListener('mousedown', createRipple); |
| |
| saveWeeklyBossButton.addEventListener('click', () => { |
| if (currentGeneratedBoss) { |
| saveBossToHistory(currentGeneratedBoss, false); |
| saveWeeklyBossSection.classList.add('hidden'); |
| } |
| }); |
| saveWeeklyBossButton.addEventListener('mousedown', createRipple); |
| |
| saveLegendButton.addEventListener('click', () => { |
| if (currentGeneratedBoss) { |
| saveBossToHistory(currentGeneratedBoss, false, true); |
| saveLegendSection.classList.add('hidden'); |
| } |
| }); |
| saveLegendButton.addEventListener('mousedown', createRipple); |
| |
| |
| clearHistoryButton.addEventListener('click', () => { |
| showConfirmModal( |
| translations[currentLanguage]['modals.confirm.message'], |
| clearHistory |
| ); |
| }); |
| clearHistoryButton.addEventListener('mousedown', createRipple); |
| |
| showFullHistoryButton.addEventListener('click', showFullHistoryModal); |
| showFullHistoryButton.addEventListener('mousedown', createRipple); |
| |
| exportHistoryButton.addEventListener('click', exportHistory); |
| exportHistoryButton.addEventListener('mousedown', createRipple); |
| |
| fullHistoryModalCloseButton.addEventListener('click', hideFullHistoryModal); |
| fullHistoryModalCloseBottomButton.addEventListener('click', hideFullHistoryModal); |
| fullHistoryModal.addEventListener('click', (e) => { |
| if (e.target === fullHistoryModal) hideFullHistoryModal(); |
| }); |
| |
| |
| characterSearchInput.addEventListener('input', (e) => { |
| renderPlayerCharactersSelection(e.target.value, elementFilterSelect.value); |
| }); |
| |
| elementFilterSelect.addEventListener('change', (e) => { |
| renderPlayerCharactersSelection(characterSearchInput.value, e.target.value); |
| }); |
| |
| clearPlayerCharactersButton.addEventListener('click', () => { |
| showConfirmModal( |
| translations[currentLanguage]['settings.clearSelected'] + '?', |
| clearPlayerCharacters |
| ); |
| }); |
| clearPlayerCharactersButton.addEventListener('mousedown', createRipple); |
| |
| selectAllCharactersButton.addEventListener('click', selectAllCharacters); |
| selectAllCharactersButton.addEventListener('mousedown', createRipple); |
| |
| |
| enableAllBossesButton.addEventListener('click', enableAllBosses); |
| enableAllBossesButton.addEventListener('mousedown', createRipple); |
| |
| disableAllBossesButton.addEventListener('click', disableAllBosses); |
| disableAllBossesButton.addEventListener('mousedown', createRipple); |
| |
| resetBossesButton.addEventListener('click', resetBosses); |
| resetBossesButton.addEventListener('mousedown', createRipple); |
| |
| |
| teamSizeSelect.addEventListener('change', (e) => { |
| config.teamSize = parseInt(e.target.value); |
| localStorage.setItem(`${currentUserUID}_teamSize`, config.teamSize); |
| currentTeamSizeElement.textContent = e.target.value; |
| playSound('click'); |
| }); |
| |
| animationSpeedSelect.addEventListener('change', (e) => { |
| config.animationSpeed = e.target.value; |
| localStorage.setItem(`${currentUserUID}_animationSpeed`, config.animationSpeed); |
| playSound('click'); |
| }); |
| |
| duplicatesSelect.addEventListener('change', (e) => { |
| config.allowDuplicates = e.target.value === 'yes'; |
| localStorage.setItem('allowDuplicates', e.target.value); |
| playSound('click'); |
| }); |
| |
| soundToggle.addEventListener('change', (e) => { |
| config.soundEnabled = e.target.checked; |
| localStorage.setItem('soundEnabled', config.soundEnabled); |
| playSound('click'); |
| }); |
| |
| animationsToggle.addEventListener('change', (e) => { |
| config.animationsEnabled = e.target.checked; |
| localStorage.setItem('animationsEnabled', config.animationsEnabled); |
| playSound('click'); |
| }); |
| |
| |
| confirmModal.addEventListener('click', (e) => { |
| if (e.target === confirmModal) hideConfirmModal(); |
| }); |
| |
| confirmModalCancel.addEventListener('click', hideConfirmModal); |
| confirmModalOk.addEventListener('click', hideConfirmModal); |
| |
| |
| backToTopButton.addEventListener('click', () => { |
| window.scrollTo({ top: 0, behavior: 'smooth' }); |
| playSound('click'); |
| }); |
| backToTopButton.addEventListener('mousedown', createRipple); |
| |
| |
| teamSizeSelect.value = config.teamSize; |
| animationSpeedSelect.value = config.animationSpeed; |
| duplicatesSelect.value = config.allowDuplicates ? 'yes' : 'no'; |
| soundToggle.checked = config.soundEnabled; |
| animationsToggle.checked = config.animationsEnabled; |
| |
| |
| renderPlayerCharactersSelection('', 'all'); |
| renderBossSettings('world-bosses'); |
| renderHistory('all'); |
| updateStats(); |
| updateSelectionProgress(); |
| |
| |
| switchTab(config.category); |
| |
| |
| document.querySelectorAll('button').forEach(button => { |
| if (!button.classList.contains('tooltip')) { |
| button.addEventListener('mousedown', createRipple); |
| } |
| }); |
| |
| |
| document.querySelectorAll('button, select, input[type="checkbox"]').forEach(element => { |
| element.addEventListener('click', () => playSound('click')); |
| }); |
| |
| console.log('Genshin Impact Roulette v2.1.0 initialized successfully!'); |
| }); |
| </script> |
| </body> |
| </html> |