Download index.html from Mycc/GG: direct link, hf CLI and curl.
- Browser
- Download file 118 kB
-
https://huggingface.co/spaces/Mycc/GG/resolve/main/index.html
- Command line
-
hf download hf://spaces/Mycc/GG/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Mycc/GG/resolve/main/index.html
118 kB
| <html lang="ru"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Святилище Роновы — Genshin Roulette</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/cormorant@latest/cyrillic-500-normal.css"> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/cormorant@latest/cyrillic-700-normal.css"> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/cormorant@latest/latin-500-normal.css"> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/cormorant@latest/latin-700-normal.css"> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/cormorant-unicase@latest/cyrillic-700-normal.css"> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/cormorant-unicase@latest/latin-700-normal.css"> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/cyrillic-400-normal.css"> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/cyrillic-500-normal.css"> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/cyrillic-600-normal.css"> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-400-normal.css"> | |
| <style> | |
| :root{ | |
| --void:#000000; --obsidian:#0D0D0D; --obsidian2:#121214; | |
| --crimson:#A30000; --ember:#C41E2F; --flame:#FF3040; --flame-soft:rgba(255,48,64,.55); | |
| --platinum:#E5E5E5; --silver:#B3B3B3; --dim:#7d7d82; --faint:#4a4a50; | |
| --line:rgba(160,160,170,.13); --line-red:rgba(163,0,0,.5); | |
| } | |
| *{box-sizing:border-box} | |
| html{scroll-behavior:smooth} | |
| body{margin:0;background:var(--void);color:var(--platinum);font-family:'Inter',ui-sans-serif,system-ui,sans-serif;min-height:100vh;overflow-x:hidden;} | |
| body.dimmed #app-root{opacity:.35;filter:saturate(.6);transition:all .3s;} | |
| .f-display{font-family:'Cormorant Unicase','Cormorant',serif;letter-spacing:.14em;text-transform:uppercase;} | |
| .f-serif{font-family:'Cormorant',serif;} | |
| ::selection{background:rgba(163,0,0,.55);color:#fff;} | |
| /* ==================== БЕЗДНА: ФОН ==================== */ | |
| .abyss{position:fixed;inset:0;z-index:0;overflow:hidden;pointer-events:none;background:radial-gradient(ellipse at 50% 55%,#0a0507 0%,#000 72%);} | |
| .abyss-glow{position:absolute;inset:0;background:radial-gradient(900px 620px at 50% 58%,rgba(163,0,0,.16),transparent 62%),radial-gradient(500px 340px at 50% 58%,rgba(196,30,47,.09),transparent 70%);animation:abyssBreath 9s ease-in-out infinite;} | |
| @keyframes abyssBreath{0%,100%{opacity:.75;transform:scale(1);}50%{opacity:1;transform:scale(1.06);}} | |
| .abyss-rings{position:absolute;left:50%;top:58%;width:0;height:0;will-change:transform;} | |
| .abyss-rings svg{position:absolute;left:50%;top:50%;} | |
| .ring-a{width:min(92vmin,880px);height:min(92vmin,880px);margin:calc(min(92vmin,880px)/-2) 0 0 calc(min(92vmin,880px)/-2);animation:spin 160s linear infinite;opacity:.10;} | |
| .ring-b{width:min(64vmin,620px);height:min(64vmin,620px);margin:calc(min(64vmin,620px)/-2) 0 0 calc(min(64vmin,620px)/-2);animation:spinRev 110s linear infinite;opacity:.12;} | |
| .ring-c{width:min(38vmin,380px);height:min(38vmin,380px);margin:calc(min(38vmin,380px)/-2) 0 0 calc(min(38vmin,380px)/-2);animation:spin 70s linear infinite;opacity:.08;} | |
| @keyframes spin{to{transform:rotate(360deg);}} | |
| @keyframes spinRev{to{transform:rotate(-360deg);}} | |
| #ember-canvas{position:fixed;inset:0;z-index:1;pointer-events:none;} | |
| #eyes-layer{position:fixed;inset:0;z-index:3;pointer-events:none;} | |
| /* ==================== ОЧИ БЕЗДНЫ ==================== */ | |
| .abyss-eye{position:absolute;pointer-events:none;opacity:.22;transition:opacity .3s;will-change:transform,opacity;} | |
| /* ==================== ЗЕРНО ==================== */ | |
| .grain{position:fixed;inset:-100%;z-index:2000;pointer-events:none;opacity:.05;background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)' opacity='0.6'/%3E%3C/svg%3E");animation:grainMove 7s steps(10) infinite;} | |
| @keyframes grainMove{0%{transform:translate(0,0)}10%{transform:translate(-5%,-8%)}20%{transform:translate(-12%,3%)}30%{transform:translate(6%,-14%)}40%{transform:translate(-4%,12%)}50%{transform:translate(-10%,6%)}60%{transform:translate(12%,0)}70%{transform:translate(0,10%)}80%{transform:translate(3%,14%)}90%{transform:translate(-8%,8%)}100%{transform:translate(0,0)}} | |
| /* ==================== ВУАЛЬ ПРИЗЫВА ==================== */ | |
| #summon-veil{position:fixed;inset:0;z-index:1500;pointer-events:none;opacity:0;transition:opacity .3s ease;background:radial-gradient(circle at 50% 50%,rgba(120,0,8,.35) 0%,rgba(0,0,0,.97) 62%);display:flex;align-items:center;justify-content:center;} | |
| #summon-veil.active{opacity:1;} | |
| .veil-pupil{width:170px;height:170px;border-radius:50%;background:#000;box-shadow:0 0 60px 30px rgba(163,0,0,.55),0 0 140px 90px rgba(0,0,0,.9);} | |
| #summon-veil.active .veil-pupil{animation:pupilClose .62s cubic-bezier(.6,0,.4,1) forwards;} | |
| @keyframes pupilClose{0%{transform:scale(.15);}45%{transform:scale(1);}100%{transform:scale(.04);opacity:.9;}} | |
| /* ==================== ТРИКВЕТР ==================== */ | |
| .tq{display:inline-block;color:var(--silver);} | |
| .divider{display:flex;align-items:center;gap:12px;margin:14px auto 10px;max-width:380px;} | |
| .divider .d-line{flex:1;height:1px;background:linear-gradient(90deg,transparent,rgba(160,160,170,.4),transparent);} | |
| .divider .tq{width:18px;height:18px;color:var(--ember);filter:drop-shadow(0 0 5px rgba(196,30,47,.6));animation:tqPulse 3.2s ease-in-out infinite;} | |
| @keyframes tqPulse{0%,100%{opacity:.6;transform:scale(1) rotate(0deg);}50%{opacity:1;transform:scale(1.12) rotate(8deg);}} | |
| /* ==================== ПАНЕЛИ ==================== */ | |
| .panel{background:rgba(13,13,13,.78);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid var(--line);border-radius:6px;position:relative;box-shadow:0 18px 50px rgba(0,0,0,.65),inset 0 1px 0 rgba(255,255,255,.04);} | |
| .panel::before{content:"";position:absolute;inset:0;pointer-events:none;border-radius:6px;background-image:linear-gradient(var(--ember),var(--ember)),linear-gradient(var(--ember),var(--ember)),linear-gradient(var(--ember),var(--ember)),linear-gradient(var(--ember),var(--ember)),linear-gradient(var(--ember),var(--ember)),linear-gradient(var(--ember),var(--ember)),linear-gradient(var(--ember),var(--ember)),linear-gradient(var(--ember),var(--ember));background-repeat:no-repeat;background-position:top left,top left,top right,top right,bottom left,bottom left,bottom right,bottom right;background-size:20px 1px,1px 20px,20px 1px,1px 20px,20px 1px,1px 20px,20px 1px,1px 20px;opacity:.55;} | |
| .modal-panel{background:rgba(13,13,13,.72);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);border:1px solid rgba(163,0,0,.35);border-radius:6px;box-shadow:0 24px 80px rgba(0,0,0,.8),0 0 60px rgba(163,0,0,.12),inset 0 1px 0 rgba(255,255,255,.04);} | |
| /* ==================== SIDEBAR ==================== */ | |
| .sidebar{position:fixed;left:0;top:0;bottom:0;width:282px;z-index:50;background:linear-gradient(180deg,rgba(10,10,11,.94),rgba(6,6,7,.96));backdrop-filter:blur(12px);border-right:1px solid var(--line);display:flex;flex-direction:column;transition:transform .3s ease;} | |
| .sidebar::after{content:"";position:absolute;top:0;right:-1px;bottom:0;width:1px;background:linear-gradient(180deg,transparent,rgba(163,0,0,.55) 30%,rgba(163,0,0,.55) 70%,transparent);} | |
| .sidebar-nav{flex-grow:1;overflow-y:auto;padding:20px 16px;} | |
| .sidebar-nav::-webkit-scrollbar{width:4px} | |
| .sidebar-nav::-webkit-scrollbar-thumb{background:#3a0a0e;border-radius:2px} | |
| /* --- ЖИВОЙ ЛОГОТИП (БОЛЬШОЙ) --- */ | |
| .brand{padding:30px 20px 26px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:18px;cursor:default;position:relative;overflow:hidden;} | |
| .brand::after{content:"";position:absolute;bottom:-40px;left:50%;transform:translateX(-50%);width:300px;height:80px;background:radial-gradient(ellipse,rgba(163,0,0,.3),transparent 70%);pointer-events:none;} | |
| .brand-sigil{width:104px;height:104px;flex-shrink:0;color:var(--ember);filter:drop-shadow(0 0 16px rgba(196,30,47,.6));transition:filter .4s,transform .5s;} | |
| .brand:hover .brand-sigil{filter:drop-shadow(0 0 28px rgba(255,48,64,.95));transform:scale(1.04);} | |
| .brand-sigil svg{width:100%;height:100%;overflow:visible;} | |
| .sg-ringA{animation:spin 40s linear infinite;transform-origin:60px 60px;} | |
| .sg-ringB{animation:spinRev 26s linear infinite;transform-origin:60px 60px;} | |
| .sg-tq{animation:tqGlow 3.5s ease-in-out infinite, spin 60s linear infinite;transform-origin:60px 60px;} | |
| @keyframes tqGlow{0%,100%{opacity:.7;}50%{opacity:1;}} | |
| .sg-eye{animation:sgBlink 7s infinite;transform-origin:60px 60px;} | |
| @keyframes sgBlink{0%,90%,100%{transform:scaleY(1);}93%{transform:scaleY(.05);}96%{transform:scaleY(1);}} | |
| .sg-iris{animation:irisPulse 3s ease-in-out infinite;} | |
| @keyframes irisPulse{0%,100%{opacity:.85;}50%{opacity:1;}} | |
| .brand-text h1{margin:0;font-family:'Cormorant Unicase',serif;font-weight:700;font-size:1.5rem;letter-spacing:.3em;color:var(--platinum);text-shadow:0 0 18px rgba(196,30,47,.5);} | |
| .brand-text p{margin:4px 0 0;font-family:'Cormorant Unicase',serif;font-size:.95rem;letter-spacing:.5em;color:var(--flame);text-shadow:0 0 10px rgba(255,48,64,.6);animation:tqGlow 3s ease-in-out infinite;} | |
| .sidebar-section{margin-bottom:26px;} | |
| .sidebar-section-title{font-family:'Cormorant Unicase',serif;font-size:.62rem;letter-spacing:.3em;color:var(--dim);text-transform:uppercase;margin-bottom:12px;padding:0 6px;display:flex;align-items:center;gap:10px;} | |
| .sidebar-section-title::before,.sidebar-section-title::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,transparent,var(--line),transparent);} | |
| /* ==================== КНОПКИ ==================== */ | |
| .btn-metal{position:relative;display:flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:11px 14px;margin-bottom:6px;background:linear-gradient(180deg,#17171a,#0b0b0d);border:1px solid #2a2a2e;border-radius:3px;color:#c9c9cf;font-family:'Inter',sans-serif;font-size:.85rem;font-weight:500;letter-spacing:.04em;cursor:pointer;transition:all .22s ease;text-align:center;box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 2px 8px rgba(0,0,0,.5);} | |
| .btn-metal svg{width:17px;height:17px;flex-shrink:0;opacity:.8;} | |
| .btn-metal:hover{border-color:rgba(196,30,47,.55);color:#fff;box-shadow:inset 0 0 14px rgba(163,0,0,.22),0 0 14px rgba(163,0,0,.2);} | |
| .btn-metal:active{transform:translateY(1px);} | |
| .btn-metal.active{background:linear-gradient(180deg,#1d060a,#0c0204);border-color:rgba(255,48,64,.45);color:#ffb3b8;box-shadow:inset 0 0 16px rgba(163,0,0,.35),0 0 16px rgba(163,0,0,.22);} | |
| .btn-metal.active::after{content:"";position:absolute;bottom:-1px;left:14%;right:14%;height:1px;background:linear-gradient(90deg,transparent,var(--flame),transparent);box-shadow:0 0 8px var(--flame-soft);animation:tqGlow 2s ease-in-out infinite;} | |
| .btn-metal.flash{animation:btnFlash .4s ease;} | |
| @keyframes btnFlash{0%{box-shadow:0 0 0 rgba(255,48,64,0);}30%{box-shadow:0 0 26px rgba(255,48,64,.8),inset 0 0 20px rgba(255,48,64,.4);}100%{box-shadow:0 0 0 rgba(255,48,64,0);}} | |
| .mode-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;} | |
| .mode-grid .btn-metal,.type-grid .btn-metal{padding:10px 4px;font-size:.72rem;letter-spacing:.06em;margin-bottom:0;font-family:'Cormorant Unicase',serif;font-weight:700;} | |
| .type-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;} | |
| .rv-btn{position:relative;display:inline-flex;align-items:center;justify-content:center;gap:8px;font-family:'Cormorant Unicase',serif;text-transform:uppercase;letter-spacing:.2em;font-weight:700;background:linear-gradient(180deg,#17171a,#0a0a0c);color:#d5d5da;border:1px solid #2c2c30;border-radius:3px;padding:11px 22px;font-size:.8rem;cursor:pointer;transition:all .25s ease;overflow:hidden;box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 3px 10px rgba(0,0,0,.55);} | |
| .rv-btn:hover{border-color:rgba(196,30,47,.6);color:#fff;box-shadow:inset 0 0 16px rgba(163,0,0,.25),0 0 18px rgba(163,0,0,.28);transform:translateY(-1px);} | |
| .rv-btn-danger{border-color:#3a0a0e;color:#e08a90;background:linear-gradient(180deg,#1a0508,#0d0204);} | |
| .rv-btn-danger:hover{color:#ff8b93;border-color:var(--flame);box-shadow:0 0 18px rgba(255,48,64,.35);} | |
| .rv-btn-sm{padding:8px 16px;font-size:.68rem;} | |
| .rv-btn:disabled{opacity:.5;cursor:not-allowed;transform:none;} | |
| .rv-iconbtn{width:38px;height:38px;border-radius:3px;display:inline-flex;align-items:center;justify-content:center;background:linear-gradient(180deg,#161618,#0b0b0d);border:1px solid #2a2a2e;color:var(--silver);cursor:pointer;transition:all .25s;font-size:1.1rem;} | |
| .rv-iconbtn:hover{border-color:rgba(196,30,47,.6);color:var(--flame);box-shadow:0 0 14px rgba(163,0,0,.3);} | |
| .rv-iconbtn svg{width:17px;height:17px;} | |
| .rv-input{width:100%;background:rgba(10,10,12,.8);border:1px solid #2a2a2e;border-radius:3px;padding:10px 14px;color:var(--platinum);font-family:'Inter',sans-serif;font-size:.85rem;transition:all .25s;} | |
| .rv-input:focus{outline:none;border-color:rgba(255,48,64,.5);box-shadow:0 0 14px rgba(163,0,0,.25);} | |
| select.rv-input option{background:#0d0d0f;color:var(--platinum);} | |
| .rv-label{font-family:'Cormorant Unicase',serif;letter-spacing:.14em;text-transform:uppercase;color:var(--ember);font-weight:700;} | |
| .rv-sub{color:var(--dim);} | |
| /* ==================== ГЛАВНАЯ КНОПКА ==================== */ | |
| .summon-btn{position:relative;font-family:'Cormorant Unicase',serif;font-weight:700;font-size:1.25rem;letter-spacing:.34em;text-indent:.34em;color:#cfcfd4;padding:20px 64px;border-radius:4px;cursor:pointer;background:linear-gradient(180deg,#1c1c1f 0%,#0c0c0e 55%,#141416 100%);border:1px solid #333338;box-shadow:inset 0 1px 0 rgba(255,255,255,.07),inset 0 -6px 14px rgba(0,0,0,.7),0 10px 30px rgba(0,0,0,.75);text-shadow:0 1px 0 #000,0 0 8px rgba(180,180,190,.15);transition:color .3s,border-color .3s;} | |
| .summon-btn .sx{display:inline-block;color:#6d6d74;transition:color .3s,text-shadow .3s;margin:0 6px;} | |
| .summon-btn:hover{color:#fff;border-color:#5c0f14;animation:summonPulse 1.7s ease-in-out infinite;} | |
| .summon-btn:hover .sx{color:var(--flame);text-shadow:0 0 14px rgba(255,48,64,.9);animation:sxSpin 1.2s linear infinite;} | |
| @keyframes sxSpin{to{transform:rotate(180deg);}} | |
| @keyframes summonPulse{0%,100%{box-shadow:inset 0 1px 0 rgba(255,255,255,.07),inset 0 0 18px rgba(163,0,0,.28),0 10px 30px rgba(0,0,0,.75),0 0 22px rgba(163,0,0,.22);}50%{box-shadow:inset 0 1px 0 rgba(255,255,255,.07),inset 0 0 30px rgba(163,0,0,.45),0 10px 30px rgba(0,0,0,.75),0 0 42px rgba(255,48,64,.4);}} | |
| .summon-btn:active{transform:translateY(2px) scale(.99);} | |
| .summon-btn:disabled{opacity:.55;cursor:wait;animation:none;} | |
| .warn-msg{color:var(--flame);font-size:.8rem;text-align:center;margin-top:12px;min-height:1.2em;text-shadow:0 0 10px rgba(255,48,64,.4);} | |
| /* ==================== MAIN ==================== */ | |
| .main-wrap{margin-left:282px;min-height:100vh;display:flex;flex-direction:column;position:relative;z-index:10;} | |
| .topbar{display:none;padding:12px 18px;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);background:rgba(8,8,9,.85);backdrop-filter:blur(10px);position:sticky;top:0;z-index:45;} | |
| .main-content{flex-grow:1;padding:36px 40px;max-width:1120px;width:100%;margin:0 auto;} | |
| .stage-head{text-align:center;margin-bottom:26px;} | |
| .stage-title{font-family:'Cormorant Unicase',serif;font-weight:700;font-size:2rem;letter-spacing:.22em;text-transform:uppercase;color:var(--platinum);margin:0;text-shadow:0 0 26px rgba(163,0,0,.55);} | |
| .stage-desc{color:var(--dim);font-size:.82rem;letter-spacing:.08em;margin:0;} | |
| .pane{min-height:340px;display:flex;flex-direction:column;align-items:center;justify-content:center;} | |
| .pane-empty{text-align:center;color:var(--faint);} | |
| .pane-empty .tq{width:44px;height:44px;opacity:.4;animation:tqPulse 4s ease-in-out infinite;} | |
| .pane-empty p{margin-top:14px;font-size:.85rem;letter-spacing:.1em;} | |
| .stage-actions{display:flex;flex-direction:column;align-items:center;margin-top:30px;} | |
| .pane-actions{display:flex;gap:12px;margin-top:22px;flex-wrap:wrap;justify-content:center;} | |
| /* ==================== КАРТЫ ==================== */ | |
| .cards-row{display:flex;flex-wrap:wrap;justify-content:center;gap:16px;perspective:1200px;} | |
| .gcard-wrap{perspective:1000px;transition:transform .25s ease;} | |
| .gcard{position:relative;width:132px;height:188px;transform-style:preserve-3d;transition:transform .75s cubic-bezier(.35,.1,.3,1.1);} | |
| .gcard-wrap.wide .gcard{width:min(600px,86vw);height:auto;min-height:320px;transition-duration:.9s;} | |
| .gcard.flipped{transform:rotateY(180deg);} | |
| .gcard.instant{transition:none;} | |
| .gface{backface-visibility:hidden;-webkit-backface-visibility:hidden;border-radius:5px;overflow:hidden;} | |
| .gface-rune{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;background:linear-gradient(155deg,#141416 0%,#08080a 45%,#101013 100%);border:1px solid #2c2c31;box-shadow:inset 0 0 30px rgba(0,0,0,.8),inset 0 1px 0 rgba(255,255,255,.06),0 8px 20px rgba(0,0,0,.6);} | |
| .gface-rune::after{content:"";position:absolute;inset:0;background:linear-gradient(120deg,transparent 30%,rgba(255,255,255,.045) 46%,transparent 60%);pointer-events:none;} | |
| .gface-rune .tq{width:56px;height:56px;color:#8a8e94;opacity:.9;filter:drop-shadow(0 0 7px rgba(150,150,160,.3));animation:tqPulse 3.5s ease-in-out infinite;} | |
| .gface-rune .rune-x{font-family:'Cormorant Unicase',serif;color:#3d3d42;font-size:.85rem;letter-spacing:.5em;} | |
| .gface-content{position:absolute;inset:0;transform:rotateY(180deg);background:linear-gradient(180deg,#101012,#0a0a0c);border:1px solid rgba(163,0,0,.4);box-shadow:inset 0 0 26px rgba(163,0,0,.12),0 8px 24px rgba(0,0,0,.6);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:12px;text-align:center;} | |
| .gcard-wrap.wide .gface-rune{position:relative;} | |
| .gcard-wrap.wide .gface-content{overflow-y:auto;} | |
| .gcard-wrap.wide .gface-content::-webkit-scrollbar{width:5px} | |
| .gcard-wrap.wide .gface-content::-webkit-scrollbar-thumb{background:#3a0a0e} | |
| .gcard-wrap.pop-in{animation:cardPop .4s cubic-bezier(.3,1.4,.5,1) both;} | |
| @keyframes cardPop{0%{opacity:0;transform:translateY(18px) scale(.85);}100%{opacity:1;transform:none;}} | |
| .char-face-img{width:86px;height:86px;border-radius:50%;object-fit:cover;border:2px solid var(--ember);box-shadow:0 0 16px rgba(163,0,0,.5),inset 0 0 8px rgba(0,0,0,.6);margin-bottom:8px;} | |
| .char-face-name{font-size:.72rem;font-weight:600;color:var(--platinum);width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:.03em;} | |
| .char-face-el{display:flex;align-items:center;gap:5px;margin-top:5px;font-size:.6rem;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);} | |
| .char-face-el img{width:13px;height:13px;border-radius:50%;} | |
| .boss-face-img{width:112px;height:112px;border-radius:50%;object-fit:cover;border:2px solid var(--ember);box-shadow:0 0 22px rgba(163,0,0,.55);margin-bottom:10px;} | |
| .boss-face-name{font-family:'Cormorant',serif;font-weight:700;font-size:.95rem;color:var(--platinum);line-height:1.3;} | |
| .boss-face-hint{font-size:.62rem;color:var(--dim);letter-spacing:.14em;text-transform:uppercase;margin-top:7px;} | |
| .player-group{width:100%;margin-bottom:22px;padding:18px 16px 20px;border:1px solid var(--line);border-radius:5px;background:linear-gradient(180deg,rgba(163,0,0,.05),rgba(163,0,0,.015));position:relative;} | |
| .player-group::before{content:"";position:absolute;top:-1px;left:16px;right:16px;height:1px;background:linear-gradient(90deg,transparent,rgba(196,30,47,.7),transparent);} | |
| .player-group-title{font-family:'Cormorant Unicase',serif;text-align:center;letter-spacing:.3em;text-transform:uppercase;font-size:.72rem;color:var(--ember);margin-bottom:14px;font-weight:700;} | |
| .player-group-title.host{color:#d4af37;text-shadow:0 0 12px rgba(212,175,55,.4);} | |
| /* ==================== КОНТРАКТ ==================== */ | |
| .contract{width:100%;padding:10px 6px;text-align:center;} | |
| .contract-level{display:inline-flex;align-items:center;gap:10px;font-family:'Cormorant Unicase',serif;font-size:.68rem;letter-spacing:.3em;text-transform:uppercase;color:var(--silver);margin-bottom:14px;} | |
| .contract-level .lvl{padding:3px 12px;border:1px solid var(--line-red);border-radius:2px;color:var(--flame);background:rgba(163,0,0,.12);} | |
| .contract-name{font-family:'Cormorant',serif;font-weight:700;font-size:1.7rem;color:var(--platinum);margin:0 0 10px;text-shadow:0 0 22px rgba(163,0,0,.5);letter-spacing:.04em;} | |
| .contract-text{font-family:'Cormorant',serif;font-size:1.06rem;line-height:1.65;color:#cfcfd4;max-width:520px;margin:0 auto;} | |
| .contract-block{margin-top:20px;text-align:left;} | |
| .contract-block-title{display:flex;align-items:center;gap:10px;font-family:'Cormorant Unicase',serif;font-size:.62rem;letter-spacing:.28em;text-transform:uppercase;color:var(--ember);margin-bottom:10px;} | |
| .contract-block-title::before,.contract-block-title::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,transparent,rgba(163,0,0,.5),transparent);} | |
| .task-row{display:flex;gap:10px;align-items:flex-start;padding:9px 12px;margin-bottom:6px;background:rgba(163,0,0,.06);border-left:2px solid var(--ember);font-size:.82rem;color:#d8d8dc;line-height:1.45;} | |
| .task-row.host{border-left-color:#d4af37;} | |
| .task-row b{font-family:'Cormorant Unicase',serif;font-size:.68rem;letter-spacing:.14em;text-transform:uppercase;color:var(--flame);white-space:nowrap;padding-top:1px;} | |
| .task-row.host b{color:#d4af37;} | |
| .handicap-row{display:flex;gap:10px;padding:9px 12px;margin-bottom:6px;background:rgba(0,0,0,.4);border:1px dashed rgba(160,160,170,.25);font-size:.8rem;color:var(--silver);align-items:center;} | |
| .handicap-row .tq{width:14px;height:14px;color:var(--faint);flex-shrink:0;} | |
| .curse-pts{display:inline-flex;align-items:center;gap:6px;font-family:'Cormorant Unicase',serif;font-size:.72rem;letter-spacing:.16em;padding:4px 14px;border-radius:2px;border:1px solid;margin-top:16px;} | |
| .pts-1{color:#9a9aa0;border-color:#3a3a40;background:rgba(80,80,88,.1);} | |
| .pts-3{color:#ff9a6a;border-color:#6e2a10;background:rgba(163,60,0,.12);text-shadow:0 0 10px rgba(255,120,60,.4);} | |
| .pts-5{color:#ff5560;border-color:#7a0d14;background:rgba(163,0,0,.18);text-shadow:0 0 14px rgba(255,48,64,.7);animation:flameFlicker 1.8s ease-in-out infinite;} | |
| @keyframes flameFlicker{0%,100%{box-shadow:0 0 8px rgba(163,0,0,.3);}50%{box-shadow:0 0 22px rgba(255,48,64,.55);}} | |
| /* чёрный туман для макс. уровня */ | |
| .smoke-wrap{position:relative;} | |
| .smoke-wrap::before,.smoke-wrap::after{content:"";position:absolute;inset:-14px;border-radius:8px;pointer-events:none;background:radial-gradient(circle at 30% 70%,rgba(0,0,0,.55),transparent 60%),radial-gradient(circle at 70% 30%,rgba(0,0,0,.45),transparent 60%);animation:smokeMove 6s ease-in-out infinite;z-index:0;} | |
| .smoke-wrap::after{animation-delay:-3s;opacity:.7;} | |
| @keyframes smokeMove{0%,100%{transform:translate(0,0) scale(1);}50%{transform:translate(7px,-9px) scale(1.06);}} | |
| /* ==================== ЛЕТОПИСЬ ==================== */ | |
| .chron-item{border:1px solid var(--line);border-radius:5px;padding:16px;background:rgba(10,10,12,.6);transition:all .3s;} | |
| .chron-item .chron-head{display:flex;align-items:center;gap:12px;margin-bottom:10px;} | |
| .chron-title{font-family:'Cormorant Unicase',serif;font-size:.8rem;letter-spacing:.14em;text-transform:uppercase;color:var(--silver);flex-grow:1;} | |
| .chron-date{font-size:.62rem;color:var(--faint);letter-spacing:.06em;white-space:nowrap;} | |
| .ch-status{width:32px;height:32px;flex-shrink:0;position:relative;display:flex;align-items:center;justify-content:center;} | |
| .ch-status .tq{width:26px;height:26px;transition:all .4s;} | |
| .st-active .ch-status .tq{color:var(--flame);animation:tqPulse 1.4s ease-in-out infinite;} | |
| .st-success{border-color:rgba(180,190,205,.4)!important;box-shadow:0 0 18px rgba(163,0,0,.15);} | |
| .st-success .ch-status .tq{color:#ff2030;filter:drop-shadow(0 0 7px rgba(255,32,48,.8));} | |
| .st-fail{opacity:.72;} | |
| .st-fail .ch-status .tq{color:#3f3f44;filter:grayscale(1);} | |
| .st-fail .ch-status::after{content:"";position:absolute;left:2px;right:2px;top:50%;height:2px;background:linear-gradient(90deg,transparent,#000 20%,#1a1a1a 50%,#000 80%,transparent);transform:rotate(-38deg);box-shadow:0 0 4px rgba(0,0,0,.9);} | |
| .status-btns{display:flex;gap:6px;} | |
| .status-btns button{padding:5px 10px;font-size:.62rem;letter-spacing:.1em;font-family:'Cormorant Unicase',serif;text-transform:uppercase;background:#101012;border:1px solid #2c2c31;color:var(--dim);border-radius:2px;cursor:pointer;transition:all .2s;} | |
| .status-btns button:hover{color:#fff;border-color:var(--ember);} | |
| .status-btns button.sel-ok{color:#7dff9b;border-color:rgba(80,200,120,.5);} | |
| .status-btns button.sel-fail{color:#ff7580;border-color:rgba(255,48,64,.5);} | |
| .chron-cards{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;} | |
| .mini-char{width:74px;text-align:center;} | |
| .mini-char img{width:56px;height:56px;border-radius:50%;object-fit:cover;border:2px solid var(--ember);box-shadow:0 0 10px rgba(163,0,0,.4);} | |
| .mini-char p{font-size:.6rem;margin-top:4px;color:var(--silver);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;} | |
| .flame-high{border-color:rgba(255,48,64,.45)!important;box-shadow:0 0 24px rgba(163,0,0,.25),inset 0 0 20px rgba(163,0,0,.06);} | |
| .flame-high .chron-title{color:#ffb3b8;} | |
| /* ==================== НАСТРОЙКИ ==================== */ | |
| .settings-tab{padding:9px 18px;border-radius:3px;background:transparent;border:1px solid transparent;color:var(--dim);font-family:'Cormorant Unicase',serif;font-weight:700;font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;cursor:pointer;transition:all .2s;white-space:nowrap;} | |
| .settings-tab.active{background:#141416;border-color:var(--line-red);color:var(--flame);} | |
| .settings-tab:hover:not(.active){color:var(--silver);} | |
| .player-char-card{position:relative;cursor:pointer;border:1px solid var(--line);border-radius:4px;background:rgba(14,14,16,.8);padding:8px 4px;text-align:center;transition:all .22s;} | |
| .player-char-card.selected{border-color:rgba(255,48,64,.55);box-shadow:0 0 14px rgba(163,0,0,.3),inset 0 0 12px rgba(163,0,0,.15);} | |
| .player-char-card:hover{border-color:rgba(196,30,47,.4);transform:translateY(-2px);} | |
| .player-char-card img.avatar{width:44px;height:44px;border-radius:50%;object-fit:cover;border:2px solid transparent;transition:border-color .2s;} | |
| .player-char-card.selected img.avatar{border-color:var(--flame);} | |
| .player-char-card p{font-size:.58rem;margin-top:5px;color:var(--silver);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;} | |
| .player-char-card .remove-btn{position:absolute;top:-6px;right:-6px;background:linear-gradient(135deg,#c22,#5c0a12);color:#fff;border-radius:50%;width:18px;height:18px;display:flex;align-items:center;justify-content:center;font-size:.8rem;opacity:0;transition:opacity .2s;box-shadow:0 2px 8px rgba(0,0,0,.5);} | |
| .player-char-card:hover .remove-btn{opacity:1;} | |
| .boss-toggle{display:flex;align-items:center;padding:9px 12px;margin-bottom:6px;border:1px solid var(--line);border-radius:3px;transition:all .2s;font-size:.8rem;} | |
| .boss-toggle:hover{background:rgba(163,0,0,.07);border-color:rgba(196,30,47,.35);} | |
| .boss-toggle input{margin-right:10px;width:15px;height:15px;accent-color:var(--crimson);cursor:pointer;} | |
| .boss-toggle label{cursor:pointer;flex:1;color:#c8c8ce;} | |
| /* ==================== МУЗЫКА / ФУТЕР ==================== */ | |
| .music-control{position:fixed;bottom:20px;right:20px;z-index:90;display:flex;align-items:center;gap:10px;} | |
| .music-btn{width:42px;height:42px;border-radius:3px;background:rgba(13,13,15,.85);backdrop-filter:blur(10px);border:1px solid #2c2c31;color:var(--ember);display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .25s;} | |
| .music-btn:hover{border-color:var(--flame);box-shadow:0 0 16px rgba(163,0,0,.4);color:var(--flame);} | |
| .music-btn svg{width:18px;height:18px;} | |
| .volume-slider{width:96px;height:3px;-webkit-appearance:none;appearance:none;background:rgba(163,0,0,.3);border-radius:2px;outline:none;opacity:0;transition:opacity .3s;} | |
| .music-control:hover .volume-slider{opacity:1;} | |
| .volume-slider::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:2px;background:var(--flame);cursor:pointer;box-shadow:0 0 8px rgba(255,48,64,.6);} | |
| .volume-slider::-moz-range-thumb{width:12px;height:12px;border-radius:2px;background:var(--flame);border:none;cursor:pointer;} | |
| .app-footer{margin-top:auto;padding:30px 24px 34px;border-top:1px solid var(--line);text-align:center;position:relative;} | |
| .app-footer::before{content:"";position:absolute;top:-1px;left:22%;right:22%;height:1px;background:linear-gradient(90deg,transparent,rgba(196,30,47,.7),transparent);} | |
| .footer-author{font-family:'Cormorant Unicase',serif;font-size:1rem;letter-spacing:.4em;text-indent:.4em;color:var(--ember);text-transform:uppercase;margin:14px 0 10px;cursor:pointer;transition:all .25s;} | |
| .footer-author:hover{color:var(--platinum);text-shadow:0 0 16px rgba(255,48,64,.9);} | |
| .footer-disclaimer{font-size:.68rem;color:var(--faint);max-width:480px;margin:0 auto;line-height:1.7;letter-spacing:.04em;} | |
| .loader{width:46px;height:46px;border:2px solid rgba(163,0,0,.25);border-top-color:var(--flame);border-radius:50%;animation:rot 1s linear infinite;} | |
| @keyframes rot{to{transform:rotate(360deg);}} | |
| .hidden{display:none!important;} | |
| .custom-scrollbar::-webkit-scrollbar{height:6px;width:6px;} | |
| .custom-scrollbar::-webkit-scrollbar-track{background:rgba(0,0,0,.3);} | |
| .custom-scrollbar::-webkit-scrollbar-thumb{background:#3a0a0e;border-radius:3px;} | |
| .custom-scrollbar::-webkit-scrollbar-thumb:hover{background:#6e1218;} | |
| .mobile-menu-btn{display:none;} | |
| @media(max-width:1023px){ | |
| .sidebar{transform:translateX(-100%);width:266px;} | |
| .sidebar.open{transform:translateX(0);} | |
| .main-wrap{margin-left:0;} | |
| .topbar{display:flex;} | |
| .mobile-menu-btn{display:inline-flex;} | |
| .main-content{padding:22px 16px;} | |
| .summon-btn{padding:16px 30px;font-size:1rem;} | |
| .stage-title{font-size:1.4rem;} | |
| } | |
| @media(prefers-reduced-motion:reduce){ | |
| .abyss-eye,.grain{display:none;} | |
| *{animation-duration:.01s!important;} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- ==================== БЕЗДНА ==================== --> | |
| <div class="abyss" aria-hidden="true"> | |
| <div class="abyss-glow"></div> | |
| <div class="abyss-rings" id="abyss-rings"> | |
| <svg class="ring-a" viewBox="0 0 200 200"><circle cx="100" cy="100" r="96" fill="none" stroke="#a30000" stroke-width=".6" stroke-dasharray="2 9"/><circle cx="100" cy="100" r="88" fill="none" stroke="#666" stroke-width=".3" stroke-dasharray="30 14"/></svg> | |
| <svg class="ring-b" viewBox="0 0 200 200"><circle cx="100" cy="100" r="95" fill="none" stroke="#8a8a92" stroke-width=".4" stroke-dasharray="1 6"/><circle cx="100" cy="100" r="70" fill="none" stroke="#a30000" stroke-width=".5" stroke-dasharray="14 20"/></svg> | |
| <svg class="ring-c" viewBox="0 0 200 200"><circle cx="100" cy="100" r="92" fill="none" stroke="#a30000" stroke-width=".5" stroke-dasharray="3 5"/></svg> | |
| </div> | |
| </div> | |
| <canvas id="ember-canvas" aria-hidden="true"></canvas> | |
| <div id="eyes-layer" aria-hidden="true"></div> | |
| <div class="grain" aria-hidden="true"></div> | |
| <div id="summon-veil" aria-hidden="true"><div class="veil-pupil"></div></div> | |
| <!-- Промпт музыки --> | |
| <div id="audio-prompt" class="fixed inset-0 z-[1600] bg-black/95 backdrop-blur-md flex flex-col items-center justify-center cursor-pointer transition-opacity duration-500"> | |
| <div id="prompt-sigil" style="width:130px;height:130px;color:var(--ember);margin-bottom:26px;filter:drop-shadow(0 0 20px rgba(196,30,47,.75));"></div> | |
| <h2 class="f-display" style="font-size:1.6rem;font-weight:700;margin:0 0 8px;text-shadow:0 0 24px rgba(163,0,0,.7);">Святилище Роновы</h2> | |
| <p style="color:var(--dim);font-size:.9rem;letter-spacing:.1em;">Коснитесь пустоты — и она ответит звуком</p> | |
| </div> | |
| <!-- Музыка --> | |
| <div class="music-control"> | |
| <input type="range" class="volume-slider" id="volume-slider" min="0" max="100" value="50"> | |
| <button class="music-btn" id="music-toggle" title="Музыка"> | |
| <svg id="music-icon-on" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"/></svg> | |
| <svg id="music-icon-off" fill="none" viewBox="0 0 24 24" stroke="currentColor" style="display:none;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2"/></svg> | |
| </button> | |
| </div> | |
| <audio id="bg-music" src="hoyo_.mp3" loop preload="auto"></audio> | |
| <audio id="boom-sound" src="boom.mp3" preload="auto"></audio> | |
| <div id="app-root"> | |
| <div class="main-wrap"> | |
| <!-- ==================== SIDEBAR ==================== --> | |
| <aside class="sidebar" id="sidebar"> | |
| <div class="brand"> | |
| <div class="brand-sigil" id="brand-sigil"></div> | |
| <div class="brand-text"> | |
| <h1>Святилище</h1> | |
| <p>Ро·но·вы</p> | |
| </div> | |
| </div> | |
| <nav class="sidebar-nav"> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-section-title">Режим сеанса</div> | |
| <div class="mode-grid" id="player-count-selector"> | |
| <button class="btn-metal active" data-val="1">Соло</button> | |
| <button class="btn-metal" data-val="2">Дуо</button> | |
| <button class="btn-metal" data-val="3">Трио</button> | |
| <button class="btn-metal" data-val="4">Кооп</button> | |
| </div> | |
| <div class="mt-3 text-center" style="font-size:.68rem;letter-spacing:.12em;color:var(--faint);"> | |
| ПЕРСОНАЖЕЙ В РИТУАЛЕ: <span id="total-chars" style="color:var(--flame);font-weight:600;">4</span> | |
| </div> | |
| </div> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-section-title">Ритуал</div> | |
| <div id="category-selector"> | |
| <button class="btn-metal active" data-cat="team"> | |
| <svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg> | |
| Призыв Отряда | |
| </button> | |
| <button class="btn-metal" data-cat="boss"> | |
| <svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg> | |
| Призыв Босса | |
| </button> | |
| <button class="btn-metal" data-cat="challenge"> | |
| <svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/></svg> | |
| Контракт Роновы | |
| </button> | |
| </div> | |
| </div> | |
| <div class="sidebar-section hidden" id="boss-sub-settings"> | |
| <div class="sidebar-section-title">Лик Жертвы</div> | |
| <div class="type-grid" id="boss-type-selector"> | |
| <button class="btn-metal active" data-type="world">Мир</button> | |
| <button class="btn-metal" data-type="weekly">Неделя</button> | |
| <button class="btn-metal" data-type="legend">Легенда</button> | |
| </div> | |
| </div> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-section-title">Святилище</div> | |
| <button class="btn-metal" id="history-toggle"> | |
| <svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg> | |
| Летопись Контрактов | |
| </button> | |
| <button class="btn-metal" id="settings-toggle"> | |
| <svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg> | |
| Настройки | |
| </button> | |
| <button class="btn-metal" id="about-toggle"> | |
| <svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg> | |
| О проекте | |
| </button> | |
| </div> | |
| </nav> | |
| <div style="padding:14px 18px;border-top:1px solid var(--line);font-size:.58rem;letter-spacing:.2em;color:var(--faint);text-align:center;font-family:'Cormorant Unicase',serif;"> | |
| ТЕНИ НАБЛЮДАЮТ | |
| </div> | |
| </aside> | |
| <!-- TOPBAR mobile --> | |
| <div class="topbar"> | |
| <button class="rv-iconbtn mobile-menu-btn" id="mobile-menu-btn"> | |
| <svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg> | |
| </button> | |
| <span class="f-display" style="font-size:.8rem;color:var(--ember);" id="topbar-title">Призыв Отряда</span> | |
| <span style="width:38px;"></span> | |
| </div> | |
| <!-- ==================== MAIN ==================== --> | |
| <main class="main-content"> | |
| <div class="panel" style="padding:40px 34px 44px;"> | |
| <div class="stage-head"> | |
| <h2 class="stage-title" id="stage-title">Призыв Отряда</h2> | |
| <div class="divider"><span class="d-line"></span><span class="tq tq-sep"></span><span class="d-line"></span></div> | |
| <p class="stage-desc" id="stage-desc"></p> | |
| </div> | |
| <div id="stage-body" style="min-height:380px;"> | |
| <div class="pane" id="pane-team"><div id="team-area" style="width:100%;"></div> | |
| <div class="pane-actions hidden" id="actions-team"> | |
| <button class="rv-btn" id="save-team-btn">✕ Записать в контракт ✕</button> | |
| <button class="rv-btn rv-btn-sm" id="reroll-team-btn">Перебросить</button> | |
| </div> | |
| </div> | |
| <div class="pane hidden" id="pane-boss"><div id="boss-area" style="width:100%;display:flex;justify-content:center;"></div> | |
| <div class="pane-actions hidden" id="actions-boss"> | |
| <button class="rv-btn" id="save-boss-btn">✕ Записать в контракт ✕</button> | |
| <button class="rv-btn rv-btn-sm" id="reroll-boss-btn">Перебросить</button> | |
| </div> | |
| </div> | |
| <div class="pane hidden" id="pane-challenge"><div id="challenge-area" style="width:100%;display:flex;justify-content:center;"></div> | |
| <div class="pane-actions hidden" id="actions-challenge"> | |
| <button class="rv-btn" id="save-challenge-btn">✕ Записать в контракт ✕</button> | |
| <button class="rv-btn rv-btn-sm" id="reroll-challenge-btn">Перебросить</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="stage-actions"> | |
| <button id="spin-btn" class="summon-btn"><span class="sx">✕</span>ПРИЗВАТЬ<span class="sx">✕</span></button> | |
| <p class="warn-msg" id="warn-msg"></p> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- ==================== FOOTER ==================== --> | |
| <footer class="app-footer"> | |
| <div class="divider" style="margin-bottom:0;"><span class="d-line"></span><span class="tq tq-sep"></span><span class="d-line"></span></div> | |
| <p class="footer-author" id="author-link">Mycc</p> | |
| <p class="footer-disclaimer"> | |
| Это фанатский проект. Не аффилирован и не одобрен HoYoverse.<br> | |
| Genshin Impact — товарный знак miHoYo / HoYoverse. Развлекательный контент. | |
| </p> | |
| </footer> | |
| </div> | |
| </div> | |
| <!-- ==================== МОДАЛКА: НАСТРОЙКИ ==================== --> | |
| <div id="settings-modal" class="fixed inset-0 z-[100] flex justify-center items-center hidden p-4" style="background:rgba(0,0,0,.55);backdrop-filter:blur(6px);"> | |
| <div class="modal-panel w-full max-w-4xl shadow-2xl relative flex flex-col p-6 md:p-8 transform transition-all duration-300 scale-95 opacity-0" id="settings-modal-content" style="height:86vh;"> | |
| <div class="flex justify-between items-center mb-5"> | |
| <h2 class="f-display" style="font-size:1.3rem;font-weight:700;color:var(--platinum);margin:0;">Настройки Святилища</h2> | |
| <button id="settings-close" class="rv-iconbtn">×</button> | |
| </div> | |
| <div class="flex gap-2 mb-5 pb-4 overflow-x-auto custom-scrollbar" style="border-bottom:1px solid var(--line);"> | |
| <button class="settings-tab active" data-tab="chars">Персонажи</button> | |
| <button class="settings-tab" data-tab="bosses">Боссы</button> | |
| <button class="settings-tab" data-tab="general">Общие</button> | |
| </div> | |
| <div class="flex-grow overflow-y-auto custom-scrollbar pr-2"> | |
| <div id="tab-general" class="settings-tab-result hidden"> | |
| <label class="block rv-label" style="font-size:.75rem;margin-bottom:10px;">Скорость ритуала</label> | |
| <select id="animation-speed" class="rv-input" style="max-width:300px;"> | |
| <option value="fast">Быстрая</option> | |
| <option value="medium" selected>Средняя</option> | |
| <option value="slow">Медленная</option> | |
| </select> | |
| <p class="rv-sub" style="font-size:.75rem;margin-top:18px;line-height:1.7;">Все настройки и результаты ритуалов сохраняются в вашем браузере. Алтарь запомнит, на чём вы остановились, даже если вы закроете вкладку.</p> | |
| </div> | |
| <div id="tab-chars" class="settings-tab-result"> | |
| <p class="rv-sub" style="font-size:.78rem;margin:0 0 14px;">Отметьте персонажей, которыми владеете. Если никто не отмечен — ритуал использует всех.</p> | |
| <input type="text" id="character-search" placeholder="Поиск по имени..." class="rv-input mb-4"> | |
| <div id="player-characters-selection" class="grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 gap-2"></div> | |
| <div class="text-center mt-6"> | |
| <button id="clear-player-characters" class="rv-btn rv-btn-danger rv-btn-sm hidden">Снять все отметки</button> | |
| </div> | |
| </div> | |
| <div id="tab-bosses" class="settings-tab-result hidden"> | |
| <select id="boss-category-select" class="rv-input mb-4"> | |
| <option value="world-bosses">Мировые боссы</option> | |
| <option value="weekly-bosses">Еженедельные боссы</option> | |
| <option value="local-legends">Местные легенды</option> | |
| </select> | |
| <div id="bosses-settings-container" class="grid grid-cols-1 md:grid-cols-2 gap-x-3"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ==================== МОДАЛКА: ЛЕТОПИСЬ ==================== --> | |
| <div id="history-modal" class="fixed inset-0 z-[100] flex justify-center items-center hidden p-4" style="background:rgba(0,0,0,.55);backdrop-filter:blur(6px);"> | |
| <div class="modal-panel w-full max-w-5xl shadow-2xl relative flex flex-col p-6 md:p-8 transform transition-all duration-300 scale-95 opacity-0" id="history-modal-content" style="height:86vh;"> | |
| <div class="flex justify-between items-center mb-5"> | |
| <h2 class="f-display" style="font-size:1.3rem;font-weight:700;color:var(--platinum);margin:0;">Летопись Контрактов</h2> | |
| <div class="flex gap-2"> | |
| <button id="clear-history-button" class="rv-btn rv-btn-danger rv-btn-sm hidden">Сжечь летопись</button> | |
| <button id="history-close" class="rv-iconbtn">×</button> | |
| </div> | |
| </div> | |
| <div id="history-content" class="flex-grow overflow-y-auto custom-scrollbar pr-2 flex flex-col gap-4"> | |
| <p id="history-placeholder" class="text-center rv-sub" style="font-style:italic;">Летопись пуста... заключите свой первый контракт.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ==================== МОДАЛКА: О ПРОЕКТЕ ==================== --> | |
| <div id="about-modal" class="fixed inset-0 z-[100] flex justify-center items-center hidden p-4" style="background:rgba(0,0,0,.55);backdrop-filter:blur(6px);"> | |
| <div class="modal-panel w-full max-w-2xl shadow-2xl relative p-6 md:p-10 transform transition-all duration-300 scale-95 opacity-0" id="about-modal-content"> | |
| <div class="text-center mb-6"> | |
| <div id="about-sigil" style="width:80px;height:80px;margin:0 auto 16px;color:var(--ember);filter:drop-shadow(0 0 14px rgba(196,30,47,.6));"></div> | |
| <h2 class="f-display" style="font-size:1.5rem;font-weight:700;margin:0 0 4px;">О проекте</h2> | |
| <div class="divider"><span class="d-line"></span><span class="tq tq-sep"></span><span class="d-line"></span></div> | |
| </div> | |
| <div class="space-y-4 rv-sub" style="line-height:1.75;font-size:.88rem;"> | |
| <p><strong style="color:var(--platinum);">Святилище Роновы</strong> — развлекательный фанатский инструмент для сообщества Genshin Impact: случайные отряды, боссы и контракты-испытания трёх уровней Проклятия, чтобы вдохнуть новую жизнь в привычный геймплей.</p> | |
| <p>Создано <strong style="color:var(--flame);">Mycc</strong> — с любовью к мрачной мифологии Тейвата. Ронова, Тень Смерти, чей взор следит за вами из углов этого алтаря, стала духовным покровителем проекта.</p> | |
| <p style="font-size:.72rem;color:var(--faint);">Это развлекательный контент. Весь прогресс хранится локально в вашем браузере и никуда не передаётся.</p> | |
| </div> | |
| <div class="text-center mt-8"> | |
| <button id="about-close" class="rv-btn">Закрыть</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| /* ==================== ДАННЫЕ ==================== */ | |
| const LOCAL_CHARACTERS=[ | |
| {name:"Альбедо",icon:"pic/albedo.png",element:"Geo"},{name:"Аль-Хайтам",icon:"pic/alhaitham.png",element:"Dendro"},{name:"Алой",icon:"pic/aloy.png",element:"Cryo"},{name:"Эмбер",icon:"pic/amber.png",element:"Pyro"},{name:"Аратаки Итто",icon:"pic/arataki_itto.png",element:"Geo"},{name:"Арлекино",icon:"pic/arlecchino.png",element:"Pyro"},{name:"Бай Чжу",icon:"pic/baizhu.png",element:"Dendro"},{name:"Барбара",icon:"pic/barbara.png",element:"Hydro"},{name:"Бэй Доу",icon:"pic/beidou.png",element:"Electro"},{name:"Беннет",icon:"pic/bennett.png",element:"Pyro"},{name:"Кандакия",icon:"pic/candace.png",element:"Hydro"},{name:"Шарлотта",icon:"pic/charlotte.png",element:"Cryo"},{name:"Часка",icon:"pic/chasca.png",element:"Anemo"},{name:"Тиори",icon:"pic/chiori.png",element:"Geo"},{name:"Чун Юнь",icon:"pic/chongyun.png",element:"Cryo"},{name:"Ситлали",icon:"pic/citlali.png",element:"Cryo"},{name:"Клоринда",icon:"pic/clorinde.png",element:"Electro"},{name:"Коллеи",icon:"pic/collei.png",element:"Dendro"},{name:"Сайно",icon:"pic/cyno.png",element:"Electro"},{name:"Дэхья",icon:"pic/dehya.png",element:"Pyro"},{name:"Дилюк",icon:"pic/diluc.png",element:"Pyro"},{name:"Диона",icon:"pic/diona.png",element:"Cryo"},{name:"Дори",icon:"pic/dori.png",element:"Electro"},{name:"Эмилия",icon:"pic/emilie.png",element:"Dendro"},{name:"Эскофье",icon:"pic/escoffier.png",element:"Cryo"},{name:"Эола",icon:"pic/eula.png",element:"Cryo"},{name:"Фарузан",icon:"pic/faruzan.png",element:"Anemo"},{name:"Фишль",icon:"pic/fischl.png",element:"Electro"},{name:"Фремине",icon:"pic/freminet.png",element:"Cryo"},{name:"Фурина",icon:"pic/furina.png",element:"Hydro"},{name:"Га Мин",icon:"pic/gaming.png",element:"Pyro"},{name:"Гань Юй",icon:"pic/ganyu.png",element:"Cryo"},{name:"Горо",icon:"pic/gorou.png",element:"Geo"},{name:"Ху Тао",icon:"pic/hu_tao.png",element:"Pyro"},{name:"Ифу",icon:"pic/ifa.png",element:"Anemo"},{name:"Иансан",icon:"pic/iansan.png",element:"Electro"},{name:"Джинн",icon:"pic/jean.png",element:"Anemo"},{name:"Кадзуха",icon:"pic/kaedehara_kazuha.png",element:"Anemo"},{name:"Кэйа",icon:"pic/kaeya.png",element:"Cryo"},{name:"Качина",icon:"pic/Kachina.webp",element:"Geo"},{name:"Аяка",icon:"pic/kamisato_ayaka.png",element:"Cryo"},{name:"Аято",icon:"pic/kamisato_ayato.png",element:"Hydro"},{name:"Кавэх",icon:"pic/kaveh.png",element:"Dendro"},{name:"Кэ Цин",icon:"pic/keqing.png",element:"Electro"},{name:"Кинич",icon:"pic/kinich.png",element:"Dendro"},{name:"Кирара",icon:"pic/kirara.png",element:"Dendro"},{name:"Кли",icon:"pic/klee.png",element:"Pyro"},{name:"Сара",icon:"pic/kujou_sara.png",element:"Electro"},{name:"Шинобу",icon:"pic/kuki_shinobu.png",element:"Electro"},{name:"Лань Янь",icon:"pic/lan_yan.png",element:"Anemo"},{name:"Лейла",icon:"pic/layla.png",element:"Cryo"},{name:"Лиза",icon:"pic/lisa.png",element:"Electro"},{name:"Линнет",icon:"pic/lynette.png",element:"Anemo"},{name:"Лини",icon:"pic/lyney.png",element:"Pyro"},{name:"Мавуика",icon:"pic/mavuika.png",element:"Pyro"},{name:"Мика",icon:"pic/mika.png",element:"Cryo"},{name:"Мона",icon:"pic/mona.png",element:"Hydro"},{name:"Муалани",icon:"pic/mualani.png",element:"Hydro"},{name:"Нахида",icon:"pic/nahida.png",element:"Dendro"},{name:"Навия",icon:"pic/navia.png",element:"Geo"},{name:"Нёвиллет",icon:"pic/neuvillette.png",element:"Hydro"},{name:"Нилу",icon:"pic/nilou.png",element:"Hydro"},{name:"Нин Гуан",icon:"pic/ningguang.png",element:"Geo"},{name:"Ноэлль",icon:"pic/noelle.png",element:"Geo"},{name:"Оророн",icon:"pic/ororon.png",element:"Electro"},{name:"Ци Ци",icon:"pic/qiqi.png",element:"Cryo"},{name:"Райдэн",icon:"pic/raiden_shogun.png",element:"Electro"},{name:"Рэйзор",icon:"pic/razor.png",element:"Electro"},{name:"Розалья",icon:"pic/rosaria.png",element:"Cryo"},{name:"Кокоми",icon:"pic/sangonomiya_kokomi.png",element:"Hydro"},{name:"Саю",icon:"pic/sayu.png",element:"Anemo"},{name:"Сетос",icon:"pic/sethos.png",element:"Electro"},{name:"Шэнь Хэ",icon:"pic/shenhe.png",element:"Cryo"},{name:"Хэйдзо",icon:"pic/shikanoin_heizo.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"},{name:"Варка",icon:"pic/varka.png",element:"Anemo"},{name:"Прюн",icon:"pic/prune.png",element:"Anemo"},{name:"Линнея",icon:"pic/linneya.png",element:"Geo"},{name:"Николь",icon:"pic/nikol.png",element:"Pyro"},{name:"Лохэн",icon:"pic/lohen.png",element:"Cryo"},{name:"Сандроне",icon:"pic/sandrone.png",element:"Cryo"},{name:"Алёша",icon:"pic/alyesha.png",element:"Electro"},{name:"ГГ (Крио)",icon:"pic/traveler.webp",element:"Cryo"},{name:"Одетта",icon:"pic/odetta.png",element:"Cryo"},{name:"Весна",icon:"pic/vesna.png",element:"Anemo"},{name:"Водяница",icon:"pic/vodyanitsa.png",element:"Hydro"}]; | |
| const WORLD_BOSSES=[ | |
| {name:"Сверхтяжёлый сухопутный крейсер",icon:"pic/WorldBoss/Sverkhtyazhyelyy-sukhoputnyy-kreyser.webp"},{name:"Повелитель морозной ночи",icon:"pic/WorldBoss/Povelitel-moroznoy-nochi.webp"},{name:"Лучезарный мотылёк-призрак",icon:"pic/WorldBoss/Luchezarnyy-motylyek_prizrak.webp"},{name:"Железный утёнок",icon:"pic/WorldBoss/ZHeleznyy-utyenok.webp"},{name:"Автоматон: Надсмотрщик",icon:"pic/WorldBoss/Secret-Source-Automaton-Overseer-Device.webp"},{name:"Статуя лавового дракона",icon:"pic/WorldBoss/Statuya-lavovogo-drakona.webp"},{name:"Говорящая с духами отшельница",icon:"pic/WorldBoss/Govoryashchaya-s-dukhami-otshelnitsa.webp"},{name:"Сумрачная папилла",icon:"pic/WorldBoss/Sumrachnaya-papilla.webp"},{name:"Автоматон: Конфигуратор",icon:"pic/WorldBoss/Avtomaton-tainstvennogo-istochnika.webp"},{name:"Прожорливый Горный король",icon:"pic/WorldBoss/Prozhorlivyy-gornyy-korol-yumkazavr.webp"},{name:"Кукузавр-тиран",icon:"pic/WorldBoss/Prozhorlivyi_-Gornyi_-korol_yumkazavr.webp"},{name:"Легат големов",icon:"pic/WorldBoss/Kapitan-golemov.webp"},{name:"Суаньни-отшельник",icon:"pic/WorldBoss/Suanni_otshelnik.webp"},{name:"Гидротульпа",icon:"pic/WorldBoss/Probuzhdyennaya-zhizn.webp"},{name:"Экспериментальный генератор",icon:"pic/WorldBoss/Eksperimentalnyy-generator-polya.webp"},{name:"Морской конёк жемчужины",icon:"pic/WorldBoss/Morskoy-konyek-tysyacheletney-zhemchuzhiny.webp"},{name:"Император огня и железа",icon:"pic/WorldBoss/Imperator-ognya-i-zheleza.webp"},{name:"Сюита ледяного ветра",icon:"pic/WorldBoss/Syuita-ledyanogo-vetra.webp"},{name:"Чудовищный креститель",icon:"pic/WorldBoss/CHudovishchnyy-krestitel.webp"},{name:"Унут Сетеха",icon:"pic/WorldBoss/Unut-Setekha.webp"},{name:"Дендро Гипостазис",icon:"pic/WorldBoss/Dendro-Gipostazis.png"},{name:"Алгоритм полуизвечной матрицы",icon:"pic/WorldBoss/Poluvechnaya-matritsa-upravleniya.webp"},{name:"Дракон вечных бедствий",icon:"pic/WorldBoss/Drakon-vechnykh-bedstviy.webp"},{name:"Электро папоротник",icon:"pic/WorldBoss/Elektro-paporotnik.webp"},{name:"Пернатый плесенник",icon:"pic/WorldBoss/Pernatyy-plesennik.webp"},{name:"Змей руин",icon:"pic/WorldBoss/Zmey-ruin.webp"},{name:"Стая вишапов глубин",icon:"pic/WorldBoss/Staya-vishapov-glubin.webp"},{name:"Золотой волчий вожак",icon:"pic/WorldBoss/Golden-Wolflord.png"},{name:"Гидро Гипостазис",icon:"pic/WorldBoss/Gidro-Gipostazis.png"},{name:"Манифестация грома",icon:"pic/WorldBoss/Elektro-Okeanid.png"},{name:"Бесконечный механический массив",icon:"pic/WorldBoss/Beskonechnyy-mekhanicheskiy-massiv.png"},{name:"Пиро гипостазис",icon:"pic/WorldBoss/Piro-gipostazis.png"},{name:"Магу Кенки",icon:"pic/WorldBoss/Magu-Kenki.webp"},{name:"Крио гипостазис",icon:"pic/WorldBoss/Cryo_Hypostasis.webp"},{name:"Древний геовишап",icon:"pic/WorldBoss/Drevniy-geovishap.webp"},{name:"Гео гипостазис",icon:"pic/WorldBoss/UI_MonsterIcon_Effigy_Rock.png"},{name:"Электро гипостазис",icon:"pic/WorldBoss/UI_MonsterIcon_Effigy_Electric.png"},{name:"Анемо гипостазис",icon:"pic/WorldBoss/UI_MonsterIcon_Effigy_Wind.png"},{name:"Крио папоротник",icon:"pic/WorldBoss/UI_MonsterIcon_cryo_regisvine.png"},{name:"Пиро орхидея",icon:"pic/WorldBoss/UI_MonsterIcon_Regisvine_Fire.png"},{name:"Океанид",icon:"pic/WorldBoss/Okeanid.webp"},{name:"Лучезарный лунный геккон",icon:"pic/WorldBoss/Luchezarnyi_-lunnyi_-gekkon.webp"},{name:"Повелитель скрытых глубин",icon:"pic/WorldBoss/Povelitel-skrytykh-glubin-SHyepot-koshmarov.webp"},{name:"Созерцатель: Падший страж",icon:"pic/WorldBoss/Sozertsatel-Padshiy-strazh.webp"},{name:"Бессмертный конструкт",icon:"pic/WorldBoss/Bessmertnyy-konstrukt.webp"},{name:"Крылатая химера-лев",icon:"pic/WorldBoss/Krylataya-khimera_lev.webp"}]; | |
| const WEEKLY_BOSSES=[ | |
| {name:"Игра перед вратами",icon:"pic/Allweeklybosses/Igra-pered-vratami.webp"},{name:"Доктор",icon:"pic/Allweeklybosses/Doktor.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:"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/"},{name:"Шестнадцатикратная мандрагора",icon:"pic/LocalLegends/placeholder.webp",guideUrl:"https://wotpack.ru/shestnadtsatikratnaya-mandragora-v-genshin-impact-kak-pobedit-i-proyti-ispytanie/"},{name:"Старая ведьма Хильетта",icon:"https://wotpack.ru/wp-content/uploads/2026/01/hiletta-v-genshin-impact-730x390.jpg",guideUrl:"https://wotpack.ru/hiletta-v-genshin-impact-kak-pobedit-babushku-i-proyti-ispytanie/"},{name:"Тоскующий волк-одиночка",icon:"https://wotpack.ru/wp-content/uploads/2026/01/kartochka-toskuyuschego-po-rodine-volka-odinochki-u-kateriny-730x411.jpg",guideUrl:"https://wotpack.ru/toskuyuschiy-po-rodine-volk-odinochka-v-genshin-impact-kak-pobedit-klyk-pustoshey/"},{name:"Автоматон Муштешир",icon:"https://wotpack.ru/wp-content/uploads/2026/07/mushteshir-v-genshin-impact-kak-pobedit-i-unichtozhit-chetyre-mehanizma-800x450.jpg",guideUrl:"https://wotpack.ru/mushteshir-v-genshin-impact-kak-pobedit-i-unichtozhit-chetyre-mehanizma/"},{name:"Чурльдрик",icon:"https://wotpack.ru/wp-content/uploads/2026/04/churldrik.-kartochka-v-hronikah-doblesti-800x450.jpg",guideUrl:"https://wotpack.ru/churldrik-v-genshin-impact-kak-pobedit-i-unichtozhit-vkusnyy-kotelok/"},{name:"Маха Васудеваяпутра",icon:"https://wotpack.ru/wp-content/uploads/2026/04/maha-vasudevayaputra-v-genshin-impact-kak-pobedit-do-fazy-obozhzheniya-800x445.jpg",guideUrl:"https://wotpack.ru/maha-vasudevayaputra-v-genshin-impact-kak-pobedit-do-fazy-obozhzheniya/"},{name:"Фуриоса",icon:"https://wotpack.ru/wp-content/uploads/2026/08/furiosa-v-menyu-kateriny-nagrady-800x450.jpg",guideUrl:"https://wotpack.ru/furiosa-v-genshin-impact-kak-pobedit-i-unichtozhit-korni-pregrady/"},{name:"Чурин",icon:"https://wotpack.ru/wp-content/uploads/2026/08/churin-v-genshin-impact-800x450.jpg",guideUrl:"https://wotpack.ru/churin-v-genshin-impact-gde-nayti-i-kak-pobedit-uchenika-vayatelya/"},{name:"Брандашмыг",icon:"https://wotpack.ru/wp-content/uploads/2026/08/himera-razboynik-v-genshin-impact-800x443.jpg",guideUrl:"https://wotpack.ru/himera-razboynik-v-genshin-impact-kak-pobedit-v-gigantskoy-forme-i-umenshennom-sostoyanii/"}]; | |
| const TRIALS=[ | |
| {level:1,points:1,name:"Око за Око",text:"Персонаж, активировавший Элементальный Взрыв, обязан оставаться на поле до завершения действия ульты. Переключаться на других героев в этот момент запрещено."}, | |
| {level:1,points:1,name:"Поцелуй Снежной",text:"Запрещено использовать любую еду, зелья или карманные баффы перед началом боя. Только базовая сила отряда."}, | |
| {level:1,points:1,name:"Слепая Вера",text:"Отключите мини-карту во время прохождения. Навигация к цели — только по памяти."}, | |
| {level:1,points:1,name:"Запретный Плод",text:"Запрещено подбирать элементальные частицы, созданные персонажами противоположной стихии."}, | |
| {level:2,points:3,name:"Право на Ошибку",text:"Если погибает хотя бы один персонаж — немедленно перезапустите бой. Ронова требует идеала: отряд выживает целиком или погибает полностью."}, | |
| {level:2,points:3,name:"Голод Бездны",text:"Полная изоляция от родных стихий региона: запрещено использовать персонажей региона, в котором вы находитесь."}, | |
| {level:2,points:3,name:"Печать Молчания",text:"Стихийные навыки и ульты разрешены только одному «жрецу» отряда. Остальные сражаются строго обычными и заряженными атаками."}, | |
| {level:2,points:3,name:"Эхо Прошлого",text:"Запрещено использовать персонажей, вышедших в последнем крупном патче. Допускаются только «старые» герои."}, | |
| {level:3,points:5,name:"Очи Роновы",text:"Каждые 45 секунд боя меняйте активного персонажа по строгому кругу: 1 → 2 → 3 → 4. Любое нарушение ротации — провал контракта."}, | |
| {level:3,points:5,name:"Час Искупления",text:"Сражение на 1–2 уровнях Мира выше текущего. Если уровень максимален — весь отряд экипирован артефактами не выше 4★."}, | |
| {level:3,points:5,name:"Взгляд Смерти",text:"Если HP главного ДД опускается ниже 30% — запрещено лечить его или накладывать щит. Он добивает босса на грани гибели или падает."}, | |
| {level:3,points:5,name:"Петля Времени",text:"Босс должен быть уничтожен строго за 120 секунд. Время вышло — контракт аннулируется."}]; | |
| const SYSTEM_HANDICAPS=[ | |
| "Ограничить FPS до 30 и выкрутить рендеринг на максимум","Не использовать кнопку спринта ни разу за весь бой","Прыгать каждые 5 секунд — без исключений","Минимальное разрешение экрана + максимальная графика","Полностью отключить музыку и звуковые эффекты","Не использовать карту и мини-карту","Поворачивать камеру минимум каждые 10 секунд","Переключать персонажей только клавишами 1–4, без мыши","После каждого полученного удара — три прыжка подряд","Комментировать вслух каждое своё действие","Запрет на еду и зелья во время боя","Играть в оконном режиме на половину экрана"]; | |
| const COOP_TASKS={ | |
| 2:[["Игрок 1: только Пиро/Электро персонажи","Игрок 2: только Гидро/Крио/Анемо персонажи"],["Игрок 1: саппорт и хил — без права наносить урон","Игрок 2: единственный источник урона"],["Игрок 1: только 4★ персонажи","Игрок 2: только 5★ персонажи"],["Игрок 1: персонажи Мондштадта и Ли Юэ","Игрок 2: персонажи Инадзумы, Сумеру и дальше"]], | |
| 3:[["Хостер: 2 персонажа — основной урон","Игрок 2: саппорт и хил","Игрок 3: контроль и стихийные реакции"],["Хостер: только 5★ персонажи","Игрок 2: только 4★ персонажи","Игрок 3: только стартовые герои и Путешественник"],["Хостер: Пиро-отряд","Игрок 2: Электро-отряд","Игрок 3: Гидро/Крио-отряд"]], | |
| 4:[["Игрок 1: главный ДД","Игрок 2: хил и саппорт","Игрок 3: щиты и контроль","Игрок 4: суб-ДД и реакции"],["Игрок 1: Пиро","Игрок 2: Электро","Игрок 3: Гидро или Крио","Игрок 4: Анемо, Гео или Дендро"],["Игроки 1–2: только 4★ персонажи","Игроки 3–4: только 5★ персонажи","Стихии в отряде не должны повторяться"]]}; | |
| const LEVEL_ROMAN={1:"I",2:"II",3:"III"}; | |
| const LEVEL_NAMES={1:"Прикосновение Тени",2:"Тлеющий Контракт",3:"Лик Роновы"}; | |
| const MODE_NAMES={1:"Соло",2:"Дуо",3:"Трио",4:"Фулкооп"}; | |
| /* ==================== ТРИКВЕТР ==================== */ | |
| const TQ=`<svg viewBox="0 0 100 100" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"> | |
| <circle cx="50" cy="50" r="45" stroke-opacity=".35" stroke-width="1.4"/> | |
| <path d="M50 50 C30 36 30 14 50 6 C70 14 70 36 50 50 Z"/> | |
| <g transform="rotate(120 50 50)"><path d="M50 50 C30 36 30 14 50 6 C70 14 70 36 50 50 Z"/></g> | |
| <g transform="rotate(240 50 50)"><path d="M50 50 C30 36 30 14 50 6 C70 14 70 36 50 50 Z"/></g> | |
| <circle cx="50" cy="50" r="4" fill="currentColor" stroke="none"/></svg>`; | |
| /* ==================== БОЛЬШОЙ ЖИВОЙ СИГИЛ ==================== */ | |
| const SIGIL=`<svg viewBox="0 0 120 120" fill="none" stroke="currentColor"> | |
| <g class="sg-ringA"><circle cx="60" cy="60" r="56" stroke-width="1" stroke-dasharray="3 8"/><path d="M60 4 l2 5 -2 5 -2 -5z" fill="currentColor" stroke="none"/><path d="M116 60 l-5 2 -5 -2 5 -2z" fill="currentColor" stroke="none"/><path d="M60 116 l-2 -5 2 -5 2 5z" fill="currentColor" stroke="none"/><path d="M4 60 l5 -2 5 2 -5 2z" fill="currentColor" stroke="none"/></g> | |
| <g class="sg-ringB"><circle cx="60" cy="60" r="47" stroke-width=".7" stroke-dasharray="14 10" stroke-opacity=".55"/></g> | |
| <g class="sg-tq" stroke-width="2.2"> | |
| <path d="M60 60 C44 48 44 28 60 20 C76 28 76 48 60 60Z"/> | |
| <path d="M60 60 C44 48 44 28 60 20 C76 28 76 48 60 60Z" transform="rotate(120 60 60)"/> | |
| <path d="M60 60 C44 48 44 28 60 20 C76 28 76 48 60 60Z" transform="rotate(240 60 60)"/></g> | |
| <g class="sg-eye"> | |
| <path d="M42 60 Q60 48 78 60 Q60 72 42 60Z" stroke-width="1.6" fill="rgba(0,0,0,.55)"/> | |
| <circle class="sg-iris" cx="60" cy="60" r="4.5" fill="currentColor" stroke="none"/></g></svg>`; | |
| document.getElementById('brand-sigil').innerHTML=SIGIL; | |
| document.getElementById('prompt-sigil').innerHTML=SIGIL; | |
| document.getElementById('about-sigil').innerHTML=SIGIL; | |
| document.querySelectorAll('.tq-sep').forEach(s=>{s.classList.add('tq');s.innerHTML=TQ;}); | |
| /* ==================== КОНФИГ / СОСТОЯНИЕ ==================== */ | |
| function getOrCreateUID(){let u=localStorage.getItem('genshinRouletteUID');if(!u){u=Math.random().toString(36).slice(2,15)+Math.random().toString(36).slice(2,15);localStorage.setItem('genshinRouletteUID',u);}return u;} | |
| const UID=getOrCreateUID(); | |
| const K=n=>`${UID}_${n}`; | |
| const config={animationSpeed:localStorage.getItem(K('animSpeed'))||'medium', | |
| excludedBosses:JSON.parse(localStorage.getItem(K('excludedBosses')))||{'world-bosses':[],'weekly-bosses':[],'local-legends':[]}}; | |
| let allCharacters=[...LOCAL_CHARACTERS]; | |
| let historyTeams=JSON.parse(localStorage.getItem(K('teamsHistory')))||[]; | |
| let historyBosses=JSON.parse(localStorage.getItem(K('bossesHistory')))||[]; | |
| let playerCharacters=JSON.parse(localStorage.getItem(K('playerCharacters')))||[]; | |
| let session=JSON.parse(localStorage.getItem(K('session')))||{tab:'team',bossType:'world',team:null,boss:null,challenge:null}; | |
| let playerCount=session.pc||1; | |
| let currentCategory=session.tab||'team'; | |
| let currentBossType=session.bossType||'world'; | |
| let isSpinning=false; | |
| const $=id=>document.getElementById(id); | |
| const spinBtn=$('spin-btn'),warnMsg=$('warn-msg'); | |
| const bgMusic=$('bg-music'),boomSound=$('boom-sound'); | |
| const sidebar=$('sidebar'); | |
| let isMusicPlaying=false; | |
| function saveSession(){session.pc=playerCount;localStorage.setItem(K('session'),JSON.stringify(session));} | |
| function getDistribution(p){return p===1?[4]:p===2?[2,2]:p===3?[2,1,1]:[1,1,1,1];} | |
| function getPlayerLabel(i,p){return p===3&&i===0?'Хостер':'Игрок '+(i+1);} | |
| function warn(t){warnMsg.textContent=t;clearTimeout(warn._t);warn._t=setTimeout(()=>warnMsg.textContent='',3500);} | |
| /* ==================== МУЗЫКА ==================== */ | |
| function updateMusicIcon(){musicIconOn.style.display=isMusicPlaying?'block':'none';musicIconOff.style.display=isMusicPlaying?'none':'block';} | |
| const audioPrompt=$('audio-prompt'),musicIconOn=$('music-icon-on'),musicIconOff=$('music-icon-off'),volumeSlider=$('volume-slider'); | |
| function initMusic(){ | |
| bgMusic.volume=.5;volumeSlider.value=50; | |
| const p=bgMusic.play(); | |
| if(p!==undefined){p.then(()=>{isMusicPlaying=true;updateMusicIcon();audioPrompt.style.opacity='0';setTimeout(()=>audioPrompt.classList.add('hidden'),500);}) | |
| .catch(()=>{isMusicPlaying=false;updateMusicIcon();audioPrompt.classList.remove('hidden');});} | |
| } | |
| audioPrompt.addEventListener('click',()=>{bgMusic.play().then(()=>{isMusicPlaying=true;updateMusicIcon();}).catch(()=>{});audioPrompt.style.opacity='0';setTimeout(()=>audioPrompt.classList.add('hidden'),500);}); | |
| $('music-toggle').addEventListener('click',()=>{if(isMusicPlaying){bgMusic.pause();isMusicPlaying=false;}else{bgMusic.play().catch(()=>{});isMusicPlaying=true;}updateMusicIcon();}); | |
| volumeSlider.addEventListener('input',e=>bgMusic.volume=e.target.value/100); | |
| /* ==================== УГЛИ И ПЕПЕЛ ==================== */ | |
| function initEmbers(){ | |
| const canvas=$('ember-canvas'),ctx=canvas.getContext('2d');let parts=[]; | |
| const resize=()=>{canvas.width=innerWidth;canvas.height=innerHeight;};resize();addEventListener('resize',resize); | |
| class P{constructor(t){this.t=t;this.reset(true);} | |
| reset(init){this.x=Math.random()*canvas.width;this.y=init?Math.random()*canvas.height:canvas.height+8; | |
| if(this.t==='ash'){this.s=Math.random()*1.6+.5;this.v=Math.random()*.35+.1;this.o=Math.random()*.22+.05;this.drift=(Math.random()-.5)*.25;} | |
| else{this.s=Math.random()*1.7+.6;this.v=Math.random()*.7+.35;this.o=Math.random()*.5+.25;this.drift=(Math.random()-.5)*.45;this.fl=Math.random()*Math.PI*2;}} | |
| step(){this.y-=this.v;this.x+=this.drift+Math.sin(this.y*.01+this.fl)*.15; | |
| if(this.y<-10||this.x<-10||this.x>canvas.width+10)this.reset(false); | |
| const a=this.t==='ash'?this.o:this.o*(0.65+0.35*Math.sin(Date.now()*.006+this.fl)); | |
| ctx.beginPath();ctx.arc(this.x,this.y,this.s,0,6.283); | |
| ctx.fillStyle=this.t==='ash'?`rgba(120,120,128,${a})`:`rgba(255,${48+Math.floor(60*Math.random())},40,${a})`; | |
| if(this.t==='ember'){ctx.shadowColor='rgba(255,48,64,.8)';ctx.shadowBlur=6;}else ctx.shadowBlur=0; | |
| ctx.fill();}} | |
| for(let i=0;i<46;i++)parts.push(new P('ash')); | |
| for(let i=0;i<24;i++)parts.push(new P('ember')); | |
| (function loop(){ctx.clearRect(0,0,canvas.width,canvas.height);parts.forEach(p=>p.step());requestAnimationFrame(loop);})(); | |
| } | |
| /* ==================== ОЧИ БЕЗДНЫ: 7 ГЛАЗ, РАНДОМ, ≥10 СМ ==================== */ | |
| const EYE_SVG=(cid)=>`<svg viewBox="0 0 120 60" style="overflow:visible;"> | |
| <defs><clipPath id="${cid}"><path d="M8 30 Q60 4 112 30 Q60 56 8 30Z"/></clipPath></defs> | |
| <path d="M8 30 Q60 4 112 30 Q60 56 8 30Z" fill="rgba(5,2,3,.55)" stroke="rgba(196,30,47,.85)" stroke-width="1.5"/> | |
| <g clip-path="url(#${cid})"> | |
| <g class="iris-g"> | |
| <circle cx="60" cy="30" r="13" fill="#2a0508" stroke="rgba(255,48,64,.9)" stroke-width="1.2"/> | |
| <ellipse cx="60" cy="30" rx="3" ry="9" fill="#000" stroke="rgba(255,48,64,.5)" stroke-width=".6"/> | |
| <circle cx="55" cy="25" r="1.8" fill="rgba(255,220,220,.5)"/> | |
| </g> | |
| <rect class="lid lid-t" x="0" y="0" width="120" height="30" fill="#020203"/> | |
| <rect class="lid lid-b" x="0" y="30" width="120" height="30" fill="#020203"/> | |
| </g> | |
| <path d="M8 30 Q60 4 112 30 Q60 56 8 30Z" fill="none" stroke="rgba(255,48,64,.3)" stroke-width=".6"/></svg>`; | |
| const mouse={x:innerWidth/2,y:innerHeight/2,idle:0}; | |
| let eyes=[]; | |
| const EYE_MIN_DIST=378; // ~10 см при 96dpi | |
| function placeEyes(){ | |
| const layer=$('eyes-layer');layer.innerHTML='';eyes=[]; | |
| const W=innerWidth,H=innerHeight; | |
| // Запретные зоны: сайдбар и центральная панель (чтобы глаза НЕ перекрывали контент) | |
| const forbid=[]; | |
| if(innerWidth>=1024){const sb=$('sidebar');if(sb){const r=sb.getBoundingClientRect();if(r.width>0)forbid.push({x:r.left,y:r.top,w:r.width,h:r.height});}} | |
| const panel=document.querySelector('.panel'); | |
| if(panel){const r=panel.getBoundingClientRect();forbid.push({x:r.left,y:r.top,w:r.width,h:r.height});} | |
| const pts=[];let attempts=0; | |
| while(pts.length<7&&attempts<600){ | |
| attempts++; | |
| const x=50+Math.random()*(W-100); | |
| const y=60+Math.random()*(H-180); | |
| let ok=true; | |
| for(const p of pts){if(Math.hypot(p.x-x,p.y-y)<EYE_MIN_DIST){ok=false;break;}} | |
| if(!ok)continue; | |
| for(const f of forbid){ | |
| if(x>f.x-40&&x<f.x+f.w+40&&y>f.y-40&&y<f.y+f.h+40){ok=false;break;} | |
| } | |
| if(!ok)continue; | |
| pts.push({x,y}); | |
| } | |
| // Если мало места — раскладываем по краям | |
| let fb=0; | |
| while(pts.length<7){ | |
| const edge=fb%4;fb++; | |
| let x,y; | |
| if(edge===0){x=80+Math.random()*(W-160);y=70;} | |
| else if(edge===1){x=80+Math.random()*(W-160);y=H-130;} | |
| else if(edge===2){x=innerWidth>=1024?320:60;y=100+Math.random()*(H-260);} | |
| else{x=W-90;y=120+Math.random()*(H-300);} | |
| let ok=true; | |
| for(const p of pts){if(Math.hypot(p.x-x,p.y-y)<EYE_MIN_DIST*.6){ok=false;break;}} | |
| if(ok)pts.push({x,y}); | |
| if(fb>40)break; | |
| } | |
| pts.forEach((p,i)=>createEye(layer,p.x,p.y,i)); | |
| } | |
| function createEye(layer,x,y,i){ | |
| const scale=0.75+Math.random()*0.55; | |
| const rot=(Math.random()*16-8).toFixed(1); | |
| const el=document.createElement('div'); | |
| el.className='abyss-eye'; | |
| el.style.left=(x-60*scale)+'px'; | |
| el.style.top=(y-30*scale)+'px'; | |
| el.style.width=(120*scale)+'px'; | |
| el.style.height=(60*scale)+'px'; | |
| el.style.transform=`rotate(${rot}deg)`; | |
| el.innerHTML=EYE_SVG('ec'+i); | |
| layer.appendChild(el); | |
| eyes.push({el,x,y,iris:el.querySelector('.iris-g'),lt:el.querySelector('.lid-t'),lb:el.querySelector('.lid-b'),open:0,px:0,py:0,auto:0,seed:i*1.7}); | |
| } | |
| function initEyes(){ | |
| placeEyes(); | |
| let rT;addEventListener('resize',()=>{clearTimeout(rT);rT=setTimeout(placeEyes,300);}); | |
| addEventListener('mousemove',e=>{mouse.x=e.clientX;mouse.y=e.clientY;mouse.idle=Date.now();}); | |
| // Иногда глаза открываются сами, когда курсор замер | |
| setInterval(()=>{if(Date.now()-mouse.idle>3500&&eyes.length){const e=eyes[Math.floor(Math.random()*eyes.length)];e.auto=Date.now()+2200;}},6000); | |
| (function loop(){ | |
| const now=Date.now(); | |
| eyes.forEach(e=>{ | |
| const d=Math.hypot(mouse.x-e.x,mouse.y-e.y); | |
| let target=Math.max(0,1-d/320); | |
| target=Math.pow(target,1.15); | |
| if(e.auto>now)target=Math.max(target,.85); | |
| target=Math.min(1,target+0.04*Math.sin(now*0.0012+e.seed)); | |
| e.open+=(target-e.open)*0.08; | |
| const h=30*(1-e.open); | |
| e.lt.setAttribute('height',Math.max(0,h).toFixed(2)); | |
| e.lb.setAttribute('y',(30+30*e.open).toFixed(2)); | |
| e.lb.setAttribute('height',Math.max(0,h).toFixed(2)); | |
| let tx=0,ty=0; | |
| if(d<400&&d>1){tx=(mouse.x-e.x)/d*6;ty=(mouse.y-e.y)/d*3.5;} | |
| e.px+=(tx-e.px)*.12;e.py+=(ty-e.py)*.12; | |
| e.iris.setAttribute('transform',`translate(${e.px.toFixed(2)} ${e.py.toFixed(2)})`); | |
| e.el.style.opacity=(0.22+e.open*0.75).toFixed(2); | |
| e.el.style.filter=`drop-shadow(0 0 ${(2+e.open*9).toFixed(1)}px rgba(163,0,0,${(0.15+e.open*0.55).toFixed(2)}))`; | |
| }); | |
| requestAnimationFrame(loop); | |
| })(); | |
| } | |
| /* --- Плавный параллакс колец --- */ | |
| function initParallax(){ | |
| const rings=$('abyss-rings');let mx=0,my=0,tx=0,ty=0; | |
| addEventListener('mousemove',e=>{tx=e.clientX/innerWidth-.5;ty=e.clientY/innerHeight-.5;}); | |
| (function loop(){mx+=(tx-mx)*.03;my+=(ty-my)*.03; | |
| rings.style.transform=`translate(${(mx*-22).toFixed(1)}px,${(my*-15).toFixed(1)}px)`;requestAnimationFrame(loop);})(); | |
| } | |
| /* ==================== КАРТОЧКИ ==================== */ | |
| function buildCard(frontBuilder,wide=false){ | |
| const wrap=document.createElement('div'); | |
| wrap.className='gcard-wrap'+(wide?' wide':''); | |
| wrap.innerHTML=`<div class="gcard"><div class="gface gface-rune"><span class="tq">${TQ}</span><span class="rune-x">✕ ✕ ✕</span></div><div class="gface gface-content"></div></div>`; | |
| frontBuilder(wrap.querySelector('.gface-content')); | |
| return wrap; | |
| } | |
| function attachTilt(wrap){ | |
| const card=wrap.querySelector('.gcard'); | |
| wrap.addEventListener('mousemove',e=>{ | |
| if(!card.classList.contains('flipped'))return; | |
| const r=wrap.getBoundingClientRect(); | |
| const rx=((e.clientY-r.top)/r.height-.5)*-8,ry=((e.clientX-r.left)/r.width-.5)*10; | |
| card.style.transform=`rotateY(${180+ry}deg) rotateX(${rx}deg)`; | |
| }); | |
| wrap.addEventListener('mouseleave',()=>{card.style.transform='';}); | |
| } | |
| function emptyState(){return `<div class="pane-empty"><span class="tq">${TQ}</span><p>Алтарь пуст. Нажмите «Призвать», и тьма ответит.</p></div>`;} | |
| /* ==================== РИТУАЛ ОТРЯДА ==================== */ | |
| function charFrontHTML(c){ | |
| return `<img class="char-face-img" src="${c.icon}" alt="${c.name}" onerror="this.onerror=null;this.src='https://placehold.co/96x96/0d0d0f/C41E2F?text=N/A'"> | |
| <div class="char-face-name" title="${c.name}">${c.name}</div> | |
| <div class="char-face-el"><img src="pic/Elements/Element_${c.element}.webp" onerror="this.style.display='none'" alt="">${c.element}</div>`; | |
| } | |
| async function spinTeam(){ | |
| if(isSpinning)return; | |
| const pool=playerCharacters.length>0?playerCharacters:allCharacters; | |
| const dist=getDistribution(playerCount),need=dist.reduce((a,b)=>a+b,0); | |
| if(pool.length<need){warn(`Недостаточно душ для ритуала: нужно ${need}, доступно ${pool.length}.`);return;} | |
| isSpinning=true;spinBtn.disabled=true;document.body.classList.add('dimmed'); | |
| $('actions-team').classList.add('hidden'); | |
| await veil(); | |
| document.body.classList.remove('dimmed'); | |
| const charsPool=[...pool],result=[]; | |
| const area=$('team-area');area.innerHTML=''; | |
| const allWraps=[]; | |
| for(let p=0;p<playerCount;p++){ | |
| const grp=document.createElement('div');grp.className='player-group'; | |
| const isHost=playerCount===3&&p===0; | |
| grp.innerHTML=`<div class="player-group-title ${isHost?'host':''}">${getPlayerLabel(p,playerCount)} · ${dist[p]} перс.</div><div class="cards-row"></div>`; | |
| area.appendChild(grp); | |
| const row=grp.querySelector('.cards-row'); | |
| const pChars=[]; | |
| for(let c=0;c<dist[p];c++){ | |
| const picked=charsPool.splice(Math.floor(Math.random()*charsPool.length),1)[0]; | |
| pChars.push(picked); | |
| const wrap=buildCard(el=>el.innerHTML=charFrontHTML(picked)); | |
| wrap.classList.add('pop-in');wrap.style.animationDelay=(allWraps.length*70)+'ms'; | |
| attachTilt(wrap);row.appendChild(wrap);allWraps.push(wrap); | |
| } | |
| result.push({label:getPlayerLabel(p,playerCount),host:isHost,chars:pChars}); | |
| } | |
| session.team={count:playerCount,groups:result};saveSession(); | |
| const speed=config.animationSpeed; | |
| const baseDelay=speed==='fast'?220:speed==='slow'?560:380; | |
| setTimeout(()=>{ | |
| allWraps.forEach((w,i)=>setTimeout(()=>{ | |
| w.querySelector('.gcard').classList.add('flipped'); | |
| boomSound.currentTime=0;boomSound.play().catch(()=>{}); | |
| },i*baseDelay)); | |
| setTimeout(()=>{isSpinning=false;spinBtn.disabled=false;$('actions-team').classList.remove('hidden');},allWraps.length*baseDelay+600); | |
| },allWraps.length*70+420); | |
| } | |
| function renderTeamInstant(){ | |
| if(!session.team)return; | |
| playerCount=session.team.count; | |
| const area=$('team-area');area.innerHTML=''; | |
| session.team.groups.forEach(g=>{ | |
| const grp=document.createElement('div');grp.className='player-group'; | |
| grp.innerHTML=`<div class="player-group-title ${g.host?'host':''}">${g.label} · ${g.chars.length} перс.</div><div class="cards-row"></div>`; | |
| const row=grp.querySelector('.cards-row'); | |
| g.chars.forEach(c=>{ | |
| const wrap=buildCard(el=>el.innerHTML=charFrontHTML(c)); | |
| attachTilt(wrap);row.appendChild(wrap); | |
| const card=wrap.querySelector('.gcard');card.classList.add('instant','flipped'); | |
| }); | |
| area.appendChild(grp); | |
| }); | |
| $('actions-team').classList.remove('hidden'); | |
| } | |
| /* ==================== РИТУАЛ БОССА ==================== */ | |
| async function spinBoss(){ | |
| if(isSpinning)return; | |
| isSpinning=true;spinBtn.disabled=true;document.body.classList.add('dimmed'); | |
| $('actions-boss').classList.add('hidden'); | |
| await veil();document.body.classList.remove('dimmed'); | |
| const data=currentBossType==='world'?{list:WORLD_BOSSES,key:'world-bosses'}:currentBossType==='weekly'?{list:WEEKLY_BOSSES,key:'weekly-bosses'}:{list:LOCAL_LEGENDS,key:'local-legends'}; | |
| const avail=data.list.filter(b=>!config.excludedBosses[data.key].includes(b.name)); | |
| const area=$('boss-area');area.innerHTML=''; | |
| if(!avail.length){area.innerHTML='<div class="pane-empty"><p style="color:var(--flame)">Все жертвы отвергнуты. Проверьте настройки боссов.</p></div>';isSpinning=false;spinBtn.disabled=false;return;} | |
| const pick=avail[Math.floor(Math.random()*avail.length)]; | |
| session.boss={type:currentBossType,name:pick.name,icon:pick.icon,guideUrl:pick.guideUrl||null};saveSession(); | |
| renderBossCard(pick,currentBossType,true); | |
| } | |
| function renderBossCard(pick,type,animate){ | |
| const area=$('boss-area');area.innerHTML=''; | |
| const isLegend=type==='legend'; | |
| const wrap=buildCard(el=>{ | |
| el.innerHTML=`<img class="boss-face-img" src="${pick.icon}" alt="${pick.name}" onerror="this.onerror=null;this.src='https://placehold.co/112x112/0d0d0f/C41E2F?text=N/A'"> | |
| <div class="boss-face-name">${pick.name}</div> | |
| <div class="boss-face-hint">${isLegend?(pick.guideUrl?'нажмите — гайд по легенде':'местная легенда'):(type==='world'?'мировой босс':'еженедельный босс')}</div>`; | |
| if(isLegend&&pick.guideUrl){el.style.cursor='pointer';el.addEventListener('click',()=>window.open(pick.guideUrl,'_blank'));} | |
| }); | |
| if(animate){wrap.classList.add('pop-in');attachTilt(wrap);area.appendChild(wrap); | |
| setTimeout(()=>{wrap.querySelector('.gcard').classList.add('flipped');boomSound.currentTime=0;boomSound.play().catch(()=>{}); | |
| setTimeout(()=>{isSpinning=false;spinBtn.disabled=false;$('actions-boss').classList.remove('hidden');},750);},480); | |
| }else{attachTilt(wrap);area.appendChild(wrap);const c=wrap.querySelector('.gcard');c.classList.add('instant','flipped');$('actions-boss').classList.remove('hidden');} | |
| } | |
| function renderBossInstant(){ | |
| if(!session.boss)return; | |
| renderBossCard({name:session.boss.name,icon:session.boss.icon,guideUrl:session.boss.guideUrl},session.boss.type,false); | |
| } | |
| /* ==================== КОНТРАКТ РОНОВЫ ==================== */ | |
| function pickTrial(){const r=Math.random();const lv=r<.45?1:r<.8?2:3;const pool=TRIALS.filter(t=>t.level===lv);return pool[Math.floor(Math.random()*pool.length)];} | |
| function challengeFrontHTML(ch){ | |
| let html=`<div class="contract"> | |
| <div class="contract-level"><span class="lvl">Уровень ${LEVEL_ROMAN[ch.trial.level]}</span> ${LEVEL_NAMES[ch.trial.level]}</div> | |
| <h3 class="contract-name">«${ch.trial.name}»</h3> | |
| <p class="contract-text">${ch.trial.text}</p>`; | |
| if(ch.tasks&&ch.tasks.length){ | |
| html+=`<div class="contract-block"><div class="contract-block-title">Распределение ролей</div>`; | |
| ch.tasks.forEach(t=>html+=`<div class="task-row ${t.host?'host':''}"><b>${t.player}</b><span>${t.text}</span></div>`); | |
| html+=`</div>`; | |
| } | |
| if(ch.handicaps.length){ | |
| html+=`<div class="contract-block"><div class="contract-block-title">Печати Системы</div>`; | |
| ch.handicaps.forEach(h=>html+=`<div class="handicap-row"><span class="tq">${TQ}</span><span>${h}</span></div>`); | |
| html+=`</div>`; | |
| } | |
| html+=`<div class="curse-pts pts-${ch.trial.points}">☩ ${ch.trial.points} ${ch.trial.points===1?'очко':(ch.trial.points<5?'очка':'очков')} проклятия</div></div>`; | |
| return html; | |
| } | |
| async function spinChallenge(){ | |
| if(isSpinning)return; | |
| isSpinning=true;spinBtn.disabled=true;document.body.classList.add('dimmed'); | |
| $('actions-challenge').classList.add('hidden'); | |
| await veil();document.body.classList.remove('dimmed'); | |
| const trial=pickTrial(); | |
| const shuffled=[...SYSTEM_HANDICAPS].sort(()=>Math.random()-.5); | |
| const handicaps=shuffled.slice(0,Math.random()<.25?2:1); | |
| let tasks=[]; | |
| if(playerCount>1&&COOP_TASKS[playerCount]){ | |
| const set=COOP_TASKS[playerCount][Math.floor(Math.random()*COOP_TASKS[playerCount].length)]; | |
| tasks=set.map((t,i)=>({player:getPlayerLabel(i,playerCount),text:t.replace(/^(Хостер|Игрок \d+):\s*/,''),host:playerCount===3&&i===0})); | |
| } | |
| const ch={trial:{name:trial.name,text:trial.text,level:trial.level,points:trial.points},handicaps,tasks}; | |
| session.challenge=ch;saveSession(); | |
| const area=$('challenge-area');area.innerHTML=''; | |
| const wrap=buildCard(el=>el.innerHTML=challengeFrontHTML(ch),true); | |
| if(trial.points>=5)wrap.classList.add('smoke-wrap'); | |
| const rune=wrap.querySelector('.gface-rune'); | |
| wrap.classList.add('pop-in');attachTilt(wrap);area.appendChild(wrap); | |
| const contentH=wrap.querySelector('.gface-content').scrollHeight; | |
| rune.style.minHeight=Math.max(320,contentH)+'px'; | |
| setTimeout(()=>{wrap.querySelector('.gcard').classList.add('flipped');boomSound.currentTime=0;boomSound.play().catch(()=>{}); | |
| setTimeout(()=>{isSpinning=false;spinBtn.disabled=false;$('actions-challenge').classList.remove('hidden');},900);},500); | |
| } | |
| function renderChallengeInstant(){ | |
| if(!session.challenge)return; | |
| const area=$('challenge-area');area.innerHTML=''; | |
| const wrap=buildCard(el=>el.innerHTML=challengeFrontHTML(session.challenge),true); | |
| if(session.challenge.trial.points>=5)wrap.classList.add('smoke-wrap'); | |
| attachTilt(wrap);area.appendChild(wrap); | |
| const rune=wrap.querySelector('.gface-rune'); | |
| rune.style.minHeight=Math.max(320,wrap.querySelector('.gface-content').scrollHeight)+'px'; | |
| const c=wrap.querySelector('.gcard');c.classList.add('instant','flipped'); | |
| $('actions-challenge').classList.remove('hidden'); | |
| } | |
| /* ==================== ВУАЛЬ ==================== */ | |
| function veil(){return new Promise(res=>{const v=$('summon-veil');v.classList.add('active');setTimeout(()=>{v.classList.remove('active');setTimeout(res,180);},640);});} | |
| /* ==================== ЛЕТОПИСЬ ==================== */ | |
| function saveTeamToHistory(){ | |
| if(!session.team)return; | |
| const chars=session.team.groups.flatMap(g=>g.chars); | |
| historyTeams.unshift({date:new Date().toISOString(),characters:chars,name:`Отряд · ${MODE_NAMES[playerCount]}`,type:'team'}); | |
| localStorage.setItem(K('teamsHistory'),JSON.stringify(historyTeams));renderHistory(); | |
| } | |
| function saveBossToHistory(){ | |
| if(!session.boss)return; | |
| const t=session.boss.type==='legend'?'legend':session.boss.type==='world'?'world-boss':'weekly-boss'; | |
| historyBosses.unshift({date:new Date().toISOString(),name:session.boss.name,icon:session.boss.icon,guideUrl:session.boss.guideUrl,type:t}); | |
| localStorage.setItem(K('bossesHistory'),JSON.stringify(historyBosses));renderHistory(); | |
| } | |
| function saveChallengeToHistory(){ | |
| if(!session.challenge)return; | |
| const ch=session.challenge; | |
| historyBosses.unshift({date:new Date().toISOString(),type:'challenge',cid:Date.now()+''+Math.floor(Math.random()*999), | |
| name:ch.trial.name,text:ch.trial.text,level:ch.trial.level,points:ch.trial.points,handicaps:ch.handicaps,tasks:ch.tasks,status:'active'}); | |
| localStorage.setItem(K('bossesHistory'),JSON.stringify(historyBosses));renderHistory(); | |
| } | |
| function setChallengeStatus(cid,st){ | |
| const e=historyBosses.find(x=>x.cid===cid); | |
| if(e){e.status=e.status===st?'active':st;localStorage.setItem(K('bossesHistory'),JSON.stringify(historyBosses));renderHistory();} | |
| } | |
| function renderHistory(){ | |
| const box=$('history-content');box.innerHTML=''; | |
| const all=[...historyTeams,...historyBosses].sort((a,b)=>{ | |
| if(a.type==='challenge'&&b.type==='challenge')return (b.points-a.points)||(new Date(b.date)-new Date(a.date)); | |
| if(a.type==='challenge')return -1;if(b.type==='challenge')return 1; | |
| return new Date(b.date)-new Date(a.date);}); | |
| if(!all.length){box.innerHTML='<p class="text-center rv-sub" style="font-style:italic;">Летопись пуста... заключите свой первый контракт.</p>';$('clear-history-button').classList.add('hidden');return;} | |
| $('clear-history-button').classList.remove('hidden'); | |
| all.forEach(item=>{ | |
| const d=new Date(item.date).toLocaleString('ru-RU'); | |
| const el=document.createElement('div'); | |
| if(item.type==='challenge'){ | |
| el.className=`chron-item st-${item.status}${item.points>=5?' flame-high':''}`; | |
| el.innerHTML=`<div class="chron-head"><div class="ch-status"><span class="tq">${TQ}</span></div> | |
| <div class="chron-title">Контракт «${item.name}» · Ур. ${LEVEL_ROMAN[item.level]} · <span class="pts-${item.points}" style="padding:1px 8px;">${item.points} ☩</span></div> | |
| <div class="chron-date">${d}</div></div> | |
| <p class="rv-sub" style="font-size:.82rem;line-height:1.6;margin:0 0 10px;">${item.text}</p> | |
| ${(item.tasks&&item.tasks.length)?`<div style="margin-bottom:8px;">${item.tasks.map(t=>`<div class="task-row ${t.host?'host':''}"><b>${t.player}</b><span>${t.text}</span></div>`).join('')}</div>`:''} | |
| ${(item.handicaps&&item.handicaps.length)?item.handicaps.map(h=>`<div class="handicap-row"><span class="tq">${TQ}</span><span>${h}</span></div>`).join(''):''} | |
| <div class="status-btns" style="margin-top:10px;"> | |
| <button data-st="success" class="${item.status==='success'?'sel-ok':''}">✓ Выполнено</button> | |
| <button data-st="fail" class="${item.status==='fail'?'sel-fail':''}">✗ Провалено</button> | |
| </div>`; | |
| el.querySelectorAll('.status-btns button').forEach(b=>b.addEventListener('click',()=>setChallengeStatus(item.cid,b.dataset.st))); | |
| }else if(item.type==='team'){ | |
| el.className='chron-item'; | |
| el.innerHTML=`<div class="chron-head"><div class="chron-title">${item.name}</div><div class="chron-date">${d}</div></div><div class="chron-cards"></div>`; | |
| const cc=el.querySelector('.chron-cards'); | |
| item.characters.forEach(c=>{ | |
| const m=document.createElement('div');m.className='mini-char'; | |
| m.innerHTML=`<img src="${c.icon}" onerror="this.onerror=null;this.src='https://placehold.co/56x56/0d0d0f/C41E2F?text=N/A'"><p>${c.name}</p>`; | |
| cc.appendChild(m);}); | |
| }else{ | |
| el.className='chron-item'; | |
| const tl=item.type==='legend'?'Местная легенда':item.type==='world-boss'?'Мировой босс':'Еженедельный босс'; | |
| el.innerHTML=`<div class="chron-head"><div class="chron-title">${tl} · ${item.name}</div><div class="chron-date">${d}</div></div> | |
| <div class="chron-cards"><div class="mini-char"><img src="${item.icon}" onerror="this.onerror=null;this.src='https://placehold.co/56x56/0d0d0f/C41E2F?text=N/A'"><p>${item.name}</p></div></div>`; | |
| } | |
| box.appendChild(el); | |
| }); | |
| } | |
| function clearHistory(){ | |
| historyTeams=[];historyBosses=[]; | |
| localStorage.removeItem(K('teamsHistory'));localStorage.removeItem(K('bossesHistory')); | |
| renderHistory(); | |
| } | |
| /* ==================== НАСТРОЙКИ ==================== */ | |
| function renderBossSettings(){ | |
| const cat=$('boss-category-select').value; | |
| const bosses=cat==='world-bosses'?WORLD_BOSSES:cat==='weekly-bosses'?WEEKLY_BOSSES:LOCAL_LEGENDS; | |
| const box=$('bosses-settings-container');box.innerHTML=''; | |
| bosses.forEach(boss=>{ | |
| const ex=config.excludedBosses[cat].includes(boss.name); | |
| const row=document.createElement('div');row.className='boss-toggle'; | |
| const id=`bs-${cat}-${boss.name.replace(/[^\wа-яё-]/gi,'')}`; | |
| row.innerHTML=`<input type="checkbox" id="${id}" ${ex?'':'checked'}><label for="${id}">${boss.name}</label>`; | |
| box.appendChild(row); | |
| row.querySelector('input').addEventListener('change',e=>{ | |
| const list=config.excludedBosses[cat]; | |
| if(e.target.checked){const i=list.indexOf(boss.name);if(i>-1)list.splice(i,1);} | |
| else if(!list.includes(boss.name))list.push(boss.name); | |
| localStorage.setItem(K('excludedBosses'),JSON.stringify(config.excludedBosses)); | |
| }); | |
| }); | |
| } | |
| function renderCharSelection(filter=''){ | |
| const box=$('player-characters-selection');box.innerHTML=''; | |
| allCharacters.filter(c=>c.name.toLowerCase().includes(filter.toLowerCase())).forEach(c=>{ | |
| const sel=playerCharacters.some(x=>x.name===c.name); | |
| const card=document.createElement('div'); | |
| card.className='player-char-card'+(sel?' selected':''); | |
| card.innerHTML=`<img class="avatar" src="${c.icon}" style="border-color:${sel?'var(--flame)':'#2c2c31'}" onerror="this.onerror=null;this.src='https://placehold.co/44x44/0d0d0f/C41E2F?text=N/A'"> | |
| <p>${c.name}</p>${sel?'<button class="remove-btn">×</button>':''}`; | |
| card.addEventListener('click',e=>{ | |
| if(e.target.classList.contains('remove-btn')){playerCharacters=playerCharacters.filter(x=>x.name!==c.name);} | |
| else{const i=playerCharacters.findIndex(x=>x.name===c.name);if(i>-1)playerCharacters.splice(i,1);else playerCharacters.push(c);} | |
| localStorage.setItem(K('playerCharacters'),JSON.stringify(playerCharacters)); | |
| renderCharSelection($('character-search').value); | |
| }); | |
| box.appendChild(card); | |
| }); | |
| $('clear-player-characters').classList.toggle('hidden',playerCharacters.length===0); | |
| } | |
| /* ==================== МОДАЛКИ ==================== */ | |
| function openModal(m,c){m.classList.remove('hidden');setTimeout(()=>{c.classList.remove('scale-95','opacity-0');c.classList.add('scale-100','opacity-100');},10);} | |
| function closeModal(m,c){c.classList.add('scale-95','opacity-0');c.classList.remove('scale-100','opacity-100');setTimeout(()=>m.classList.add('hidden'),300);} | |
| function confirmModal(msg,cb){ | |
| const m=document.createElement('div'); | |
| m.className='fixed inset-0 z-[200] flex justify-center items-center p-4'; | |
| m.style.cssText='background:rgba(0,0,0,.6);backdrop-filter:blur(6px);'; | |
| m.innerHTML=`<div class="modal-panel w-full max-w-sm relative text-center p-8"> | |
| <h3 class="f-display" style="font-size:1.1rem;font-weight:700;margin:0 0 16px;">Подтверждение</h3> | |
| <p class="rv-sub" style="font-size:.88rem;line-height:1.6;margin-bottom:24px;">${msg}</p> | |
| <div class="flex justify-center gap-3"><button class="rv-btn c-no">Отмена</button><button class="rv-btn rv-btn-danger c-yes">Да, сжечь</button></div></div>`; | |
| document.body.appendChild(m); | |
| m.querySelector('.c-no').addEventListener('click',()=>m.remove()); | |
| m.querySelector('.c-yes').addEventListener('click',()=>{cb();m.remove();}); | |
| m.addEventListener('click',e=>{if(e.target===m)m.remove();}); | |
| } | |
| /* ==================== НАВИГАЦИЯ ==================== */ | |
| function updateStage(){ | |
| const titles={team:'Призыв Отряда',boss:'Призыв Босса',challenge:'Контракт Роновы'}; | |
| const descs={ | |
| team:`Режим: ${MODE_NAMES[playerCount]} · ${getDistribution(playerCount).map((d,i)=>`${getPlayerLabel(i,playerCount)} = ${d}`).join(' · ')}`, | |
| boss:`Лик жертвы: ${currentBossType==='world'?'Мировые боссы':currentBossType==='weekly'?'Еженедельные боссы':'Местные легенды'}`, | |
| challenge:`Проклятие будет выбрано по режиму: ${MODE_NAMES[playerCount]}`}; | |
| $('stage-title').textContent=titles[currentCategory]; | |
| $('topbar-title').textContent=titles[currentCategory]; | |
| $('stage-desc').textContent=descs[currentCategory]; | |
| ['team','boss','challenge'].forEach(t=>$('pane-'+t).classList.toggle('hidden',t!==currentCategory)); | |
| $('boss-sub-settings').classList.toggle('hidden',currentCategory!=='boss'); | |
| } | |
| function totalCharsUpdate(){$('total-chars').textContent=getDistribution(playerCount).reduce((a,b)=>a+b,0);} | |
| document.querySelectorAll('#player-count-selector .btn-metal').forEach(b=>{ | |
| b.addEventListener('click',()=>{ | |
| document.querySelectorAll('#player-count-selector .btn-metal').forEach(x=>x.classList.remove('active')); | |
| b.classList.add('active');b.classList.add('flash');setTimeout(()=>b.classList.remove('flash'),450); | |
| playerCount=parseInt(b.dataset.val);totalCharsUpdate();updateStage();saveSession(); | |
| }); | |
| }); | |
| document.querySelectorAll('#category-selector .btn-metal').forEach(b=>{ | |
| b.addEventListener('click',()=>{ | |
| document.querySelectorAll('#category-selector .btn-metal').forEach(x=>x.classList.remove('active')); | |
| b.classList.add('active');b.classList.add('flash');setTimeout(()=>b.classList.remove('flash'),450); | |
| currentCategory=b.dataset.cat;updateStage();saveSession(); | |
| if(innerWidth<1024)sidebar.classList.remove('open'); | |
| }); | |
| }); | |
| document.querySelectorAll('#boss-type-selector .btn-metal').forEach(b=>{ | |
| b.addEventListener('click',()=>{ | |
| document.querySelectorAll('#boss-type-selector .btn-metal').forEach(x=>x.classList.remove('active')); | |
| b.classList.add('active');b.classList.add('flash');setTimeout(()=>b.classList.remove('flash'),450); | |
| currentBossType=b.dataset.type;updateStage();saveSession(); | |
| if(session.boss&&session.boss.type!==currentBossType){$('boss-area').innerHTML=emptyState();$('actions-boss').classList.add('hidden');session.boss=null;saveSession();} | |
| }); | |
| }); | |
| spinBtn.addEventListener('click',()=>{ | |
| if(currentCategory==='team')spinTeam(); | |
| else if(currentCategory==='boss')spinBoss(); | |
| else spinChallenge(); | |
| }); | |
| $('reroll-team-btn').addEventListener('click',spinTeam); | |
| $('reroll-boss-btn').addEventListener('click',spinBoss); | |
| $('reroll-challenge-btn').addEventListener('click',spinChallenge); | |
| $('save-team-btn').addEventListener('click',()=>{saveTeamToHistory();warn('Отряд вписан в Летопись.');}); | |
| $('save-boss-btn').addEventListener('click',()=>{saveBossToHistory();warn('Жертва вписана в Летопись.');}); | |
| $('save-challenge-btn').addEventListener('click',()=>{saveChallengeToHistory();warn('Контракт скреплён печатью в Летописи.');}); | |
| $('settings-toggle').addEventListener('click',()=>{if(innerWidth<1024)sidebar.classList.remove('open');openModal($('settings-modal'),$('settings-modal-content'));}); | |
| $('settings-close').addEventListener('click',()=>closeModal($('settings-modal'),$('settings-modal-content'))); | |
| $('settings-modal').addEventListener('click',e=>{if(e.target===$('settings-modal'))closeModal($('settings-modal'),$('settings-modal-content'));}); | |
| $('history-toggle').addEventListener('click',()=>{if(innerWidth<1024)sidebar.classList.remove('open');openModal($('history-modal'),$('history-modal-content'));}); | |
| $('history-close').addEventListener('click',()=>closeModal($('history-modal'),$('history-modal-content'))); | |
| $('history-modal').addEventListener('click',e=>{if(e.target===$('history-modal'))closeModal($('history-modal'),$('history-modal-content'));}); | |
| $('about-toggle').addEventListener('click',()=>{if(innerWidth<1024)sidebar.classList.remove('open');openModal($('about-modal'),$('about-modal-content'));}); | |
| $('about-close').addEventListener('click',()=>closeModal($('about-modal'),$('about-modal-content'))); | |
| $('about-modal').addEventListener('click',e=>{if(e.target===$('about-modal'))closeModal($('about-modal'),$('about-modal-content'));}); | |
| $('author-link').addEventListener('click',()=>openModal($('about-modal'),$('about-modal-content'))); | |
| $('clear-history-button').addEventListener('click',()=>confirmModal('Сжечь всю Летопись Контрактов? Пепел не вернуть.',clearHistory)); | |
| $('clear-player-characters').addEventListener('click',()=>confirmModal('Снять все отметки с персонажей?',()=>{playerCharacters=[];localStorage.removeItem(K('playerCharacters'));renderCharSelection('');})); | |
| $('character-search').addEventListener('input',e=>renderCharSelection(e.target.value)); | |
| $('boss-category-select').addEventListener('change',renderBossSettings); | |
| $('animation-speed').addEventListener('change',e=>{config.animationSpeed=e.target.value;localStorage.setItem(K('animSpeed'),config.animationSpeed);}); | |
| document.querySelectorAll('.settings-tab').forEach(t=>{ | |
| t.addEventListener('click',()=>{ | |
| document.querySelectorAll('.settings-tab').forEach(x=>x.classList.remove('active')); | |
| document.querySelectorAll('.settings-tab-result').forEach(x=>x.classList.add('hidden')); | |
| t.classList.add('active');$('tab-'+t.dataset.tab).classList.remove('hidden'); | |
| }); | |
| }); | |
| $('mobile-menu-btn').addEventListener('click',()=>sidebar.classList.toggle('open')); | |
| document.addEventListener('click',e=>{ | |
| if(innerWidth<1024&&sidebar.classList.contains('open')&&!sidebar.contains(e.target)&&!$('mobile-menu-btn').contains(e.target))sidebar.classList.remove('open'); | |
| }); | |
| /* ==================== ИНИЦИАЛИЗАЦИЯ ==================== */ | |
| document.querySelectorAll('#player-count-selector .btn-metal').forEach(b=>b.classList.toggle('active',parseInt(b.dataset.val)===playerCount)); | |
| document.querySelectorAll('#category-selector .btn-metal').forEach(b=>b.classList.toggle('active',b.dataset.cat===currentCategory)); | |
| document.querySelectorAll('#boss-type-selector .btn-metal').forEach(b=>b.classList.toggle('active',b.dataset.type===currentBossType)); | |
| $('animation-speed').value=config.animationSpeed; | |
| $('boss-category-select').value='world-bosses'; | |
| $('team-area').innerHTML=emptyState(); | |
| $('boss-area').innerHTML=emptyState(); | |
| $('challenge-area').innerHTML=emptyState(); | |
| initMusic();initEmbers();initEyes();initParallax(); | |
| renderCharSelection();renderHistory();renderBossSettings(); | |
| totalCharsUpdate();updateStage();saveSession(); | |
| renderTeamInstant();renderBossInstant();renderChallengeInstant(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |