GG / indexBackup.html
Mycc's picture
Rename index.html to indexBackup.html
de5594d verified
Raw History Blame Contribute Delete
123 kB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Святилище Роновы — Контракты Тени</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/cinzel-decorative/400.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/cinzel-decorative/700.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/cinzel-decorative/900.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/inter/300.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/inter/400.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/inter/500.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/inter/600.css">
<style>
:root{
--bg:#000000; --bg2:#060606; --slab:#0D0D0D; --slab2:#121212;
--ember:#A30000; --ember2:#800000; --flame:#ff2a3c; --flame-soft:rgba(255,42,60,.55);
--bone:#E5E5E5; --ash:#B3B3B3; --dim:#6f6f6f;
--steel:#2a2a2a; --steel2:#1c1c1c; --silver:#8f8f8f;
}
*{ box-sizing:border-box; }
html{ scroll-behavior:smooth; }
body{
margin:0; min-height:100vh; overflow-x:hidden;
background:var(--bg); color:var(--bone);
font-family:'Inter',sans-serif; font-weight:300;
}
h1,h2,h3,h4,.goth{ font-family:'Cinzel Decorative',serif; letter-spacing:.06em; }
::-webkit-scrollbar{ width:6px; height:6px; }
::-webkit-scrollbar-track{ background:#0a0a0a; }
::-webkit-scrollbar-thumb{ background:#3a0d0d; border-radius:3px; }
::-webkit-scrollbar-thumb:hover{ background:var(--ember); }
/* ══════════ ЖИВОЙ ФОН: БЕЗДНА ══════════ */
.abyss{ position:fixed; inset:0; z-index:0; overflow:hidden; pointer-events:none; }
.abyss-glow{ position:absolute; left:50%; top:50%; width:130vmin; height:130vmin; transform:translate(-50%,-50%);
background:radial-gradient(circle, rgba(163,0,0,.14) 0%, rgba(128,0,0,.07) 34%, transparent 62%);
animation:breatheGlow 9s ease-in-out infinite; }
.abyss-glow2{ position:absolute; left:78%; top:14%; width:60vmin; height:60vmin; transform:translate(-50%,-50%);
background:radial-gradient(circle, rgba(163,0,0,.08), transparent 60%); }
@keyframes breatheGlow{ 0%,100%{opacity:.75; } 50%{opacity:1.05;} }
#parallax-rings{ position:absolute; inset:0; will-change:transform; }
.ring{ position:absolute; left:50%; top:50%; opacity:.5; }
.ring-a{ width:980px; height:980px; animation:ringSpin 160s linear infinite; }
.ring-b{ width:700px; height:700px; animation:ringSpin 110s linear infinite reverse; }
.ring-c{ width:1280px; height:1280px; animation:ringSpin 240s linear infinite; }
@keyframes ringSpin{ from{ transform:translate(-50%,-50%) rotate(0deg);} to{ transform:translate(-50%,-50%) rotate(360deg);} }
.bg-triq{ position:absolute; left:50%; top:50%; width:540px; height:540px; color:rgba(163,0,0,.07);
transform:translate(-50%,-50%); animation:triqBreath 14s ease-in-out infinite; }
@keyframes triqBreath{ 0%,100%{ transform:translate(-50%,-50%) rotate(0deg) scale(1);} 50%{ transform:translate(-50%,-50%) rotate(8deg) scale(1.05);} }
/* Киношный шум */
.grain{ position:fixed; left:-50%; top:-50%; width:200%; height:200%; z-index:400; pointer-events:none; opacity:.05;
background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='300' height='300'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.7'/></svg>");
animation:grainShift .9s steps(5) infinite; }
@keyframes grainShift{ 0%{transform:translate(0,0);} 20%{transform:translate(-2%,2%);} 40%{transform:translate(2%,-2%);} 60%{transform:translate(-1%,-2%);} 80%{transform:translate(2%,1%);} 100%{transform:translate(0,0);} }
#dust-canvas{ position:fixed; inset:0; z-index:1; pointer-events:none; }
/* ══════════ ОЧИ БЕЗДНЫ (края экрана) ══════════ */
.edge-eye{ position:fixed; z-index:20; pointer-events:none; }
.ee-zone{ position:absolute; inset:-26px; pointer-events:auto; }
.ee-shape{ width:74px; height:36px; opacity:.16; transform:scaleY(.07); transform-origin:center;
transition:transform .7s cubic-bezier(.2,.8,.2,1), opacity .7s; color:var(--flame);
filter:drop-shadow(0 0 8px rgba(255,42,60,.55)); }
.ee-shape.open{ opacity:.85; transform:scaleY(1); }
.ee-shape svg{ width:100%; height:100%; display:block; }
/* ══════════ РИТУАЛЬНАЯ ВСПЫШКА ══════════ */
#ritual-flash{ position:fixed; inset:0; z-index:95; pointer-events:none; opacity:0; }
#ritual-flash.active{ animation:rfShow .95s ease forwards; }
.rf-dark{ position:absolute; inset:0; background:#000; opacity:0; }
#ritual-flash.active .rf-dark{ animation:rfDark .95s ease forwards; }
.rf-iris{ position:absolute; left:50%; top:50%; width:120vmax; height:120vmax; transform:translate(-50%,-50%);
background:radial-gradient(circle, transparent 0%, transparent 12%, rgba(163,0,0,.5) 14%, rgba(0,0,0,.96) 30%); opacity:0; }
#ritual-flash.active .rf-iris{ animation:rfIris .95s cubic-bezier(.6,0,.4,1) forwards; }
@keyframes rfShow{ 0%{opacity:1;} 100%{opacity:0;} }
@keyframes rfDark{ 0%{opacity:0;} 30%{opacity:.85;} 60%{opacity:.5;} 100%{opacity:0;} }
@keyframes rfIris{ 0%{opacity:0; transform:translate(-50%,-50%) scale(1.4);} 25%{opacity:1; transform:translate(-50%,-50%) scale(.55);} 55%{opacity:1; transform:translate(-50%,-50%) scale(.75);} 100%{opacity:0; transform:translate(-50%,-50%) scale(1.5);} }
/* ══════════ КАРКАС ══════════ */
.app{ position:relative; z-index:10; }
.main-wrap{ margin-left:290px; min-height:100vh; display:flex; flex-direction:column; }
/* ══════════ SIDEBAR ══════════ */
.sidebar{ position:fixed; left:0; top:0; bottom:0; width:290px; z-index:50;
background:linear-gradient(180deg,#070707,#030303);
border-right:1px solid #161616; display:flex; flex-direction:column; transition:transform .35s cubic-bezier(.2,.8,.2,1); }
.sidebar::before{ content:""; position:absolute; top:0; right:-1px; bottom:0; width:1px;
background:linear-gradient(180deg, transparent 4%, rgba(163,0,0,.65) 30%, rgba(163,0,0,.65) 70%, transparent 96%);
box-shadow:0 0 14px rgba(163,0,0,.4); }
.brand{ padding:30px 22px 24px; border-bottom:1px solid #141414; display:flex; flex-direction:column; align-items:center; gap:14px; position:relative; overflow:hidden; }
.brand::after{ content:""; position:absolute; bottom:-30px; left:50%; transform:translateX(-50%); width:240px; height:60px;
background:radial-gradient(ellipse, rgba(163,0,0,.28), transparent 70%); pointer-events:none; }
.brand-sigil{ position:relative; width:96px; height:96px; cursor:pointer; animation:sigilBreathe 6s ease-in-out infinite; }
@keyframes sigilBreathe{ 0%,100%{ transform:scale(1);} 50%{ transform:scale(1.045);} }
.brand-sigil .sigil-core{ position:absolute; inset:8px; color:#cfcfcf; animation:sigilSpin 46s linear infinite;
filter:drop-shadow(0 0 6px rgba(255,42,60,.35)); transition:filter .4s; }
.brand-sigil:hover .sigil-core{ animation-duration:9s; filter:drop-shadow(0 0 14px rgba(255,42,60,.8)); color:#fff; }
@keyframes sigilSpin{ from{transform:rotate(0);} to{transform:rotate(360deg);} }
.brand-sigil .sigil-ring{ position:absolute; inset:0; animation:sigilSpin 30s linear infinite reverse; }
.brand-title{ text-align:center; }
.brand-title h1{ margin:0; font-size:1.15rem; font-weight:700; color:var(--bone); letter-spacing:.22em; text-shadow:0 0 18px rgba(163,0,0,.6); }
.brand-title p{ margin:4px 0 0; font-size:.6rem; letter-spacing:.55em; color:var(--dim); text-transform:uppercase; }
.sidebar-nav{ flex-grow:1; padding:22px 16px 16px; overflow-y:auto; }
.side-title{ display:flex; align-items:center; gap:10px; font-size:.58rem; letter-spacing:.4em; text-transform:uppercase;
color:var(--dim); margin:0 4px 12px; font-weight:600; }
.side-title::before,.side-title::after{ content:""; flex:1; height:1px; background:linear-gradient(90deg,transparent,#2a2a2a,transparent); }
.side-title svg{ width:11px; height:11px; color:var(--ember); flex-shrink:0; filter:drop-shadow(0 0 4px rgba(255,42,60,.6)); }
.sidebar-section{ margin-bottom:26px; }
/* Металлические кнопки-селекторы */
.metal-btn{ position:relative; padding:10px 8px; border-radius:2px; cursor:pointer; text-align:center;
background:linear-gradient(180deg,#161616,#0a0a0a); border:1px solid #242424; color:#8f8f8f;
font-family:'Inter',sans-serif; font-size:.8rem; font-weight:500; letter-spacing:.04em;
transition:color .25s, border-color .25s, box-shadow .25s, background .25s; }
.metal-btn:hover{ color:var(--bone); border-color:#3a3a3a; box-shadow:inset 0 0 14px rgba(163,0,0,.18), 0 0 10px rgba(163,0,0,.12); }
.metal-btn::after{ content:""; position:absolute; left:12%; right:12%; bottom:-1px; height:2px;
background:linear-gradient(90deg, transparent, var(--flame), transparent);
opacity:0; transform:scaleX(.25); transition:opacity .3s, transform .3s; }
.metal-btn.active{ color:#fff; background:linear-gradient(180deg,#1b1012,#0c0708); border-color:#472023;
box-shadow:inset 0 0 18px rgba(163,0,0,.3); }
.metal-btn.active::after{ opacity:1; transform:scaleX(1); box-shadow:0 0 12px var(--flame-soft); }
.metal-btn.flash{ animation:btnFlash .35s ease; }
@keyframes btnFlash{ 0%{ box-shadow:0 0 0 rgba(255,42,60,0);} 40%{ box-shadow:0 0 22px rgba(255,42,60,.7), inset 0 0 18px rgba(255,42,60,.4);} 100%{ box-shadow:none;} }
.nav-btn{ display:flex; align-items:center; gap:12px; width:100%; padding:11px 13px; margin-bottom:4px;
background:transparent; border:1px solid transparent; border-radius:2px; color:var(--ash); cursor:pointer;
font-family:'Inter',sans-serif; font-size:.85rem; font-weight:400; letter-spacing:.05em; transition:all .25s; text-align:left; }
.nav-btn svg{ width:17px; height:17px; flex-shrink:0; opacity:.8; }
.nav-btn:hover{ color:var(--bone); background:rgba(163,0,0,.07); border-color:#241416; }
.nav-btn.active{ color:#fff; background:linear-gradient(90deg, rgba(163,0,0,.2), transparent 85%);
border-color:#2c1416; box-shadow:inset 3px 0 0 var(--flame); }
.sidebar-foot{ padding:14px 18px; border-top:1px solid #141414; font-size:.62rem; letter-spacing:.14em; color:#4c4c4c; text-align:center; }
/* ══════════ ОСНОВНАЯ СЦЕНА ══════════ */
.topbar{ display:none; }
.main-content{ width:100%; max-width:1080px; margin:0 auto; padding:46px 28px 20px; }
.stage-head{ text-align:center; margin-bottom:34px; }
.stage-head h2{ margin:0 0 10px; font-size:clamp(1.5rem, 3.4vw, 2.4rem); font-weight:700; color:var(--bone);
text-shadow:0 0 30px rgba(163,0,0,.55); }
.stage-desc{ margin:14px auto 0; max-width:560px; font-size:.82rem; color:var(--ash); letter-spacing:.05em; line-height:1.6; }
.ornament{ display:flex; align-items:center; justify-content:center; gap:14px; color:var(--ember); }
.ornament::before,.ornament::after{ content:""; width:min(140px,26vw); height:1px;
background:linear-gradient(90deg, transparent, rgba(163,0,0,.8)); }
.ornament::after{ background:linear-gradient(90deg, rgba(163,0,0,.8), transparent); }
.orn-triq{ width:15px; height:15px; color:var(--flame); flex-shrink:0; filter:drop-shadow(0 0 6px rgba(255,42,60,.7)); animation:triqGlow 4s ease-in-out infinite; }
@keyframes triqGlow{ 0%,100%{ opacity:.75; transform:rotate(0deg);} 50%{ opacity:1; transform:rotate(120deg);} }
.stage-panel{ position:relative; background:linear-gradient(180deg, rgba(13,13,13,.86), rgba(6,6,6,.9));
border:1px solid #1d1d1d; backdrop-filter:blur(14px); border-radius:3px;
box-shadow:0 30px 60px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.03); }
.stage-panel::before{ content:""; position:absolute; inset:0; pointer-events:none; border-radius:3px;
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; opacity:.85;
background-position:top left,top left,top right,top right,bottom left,bottom left,bottom right,bottom right;
background-size:26px 1px,1px 26px,26px 1px,1px 26px,26px 1px,1px 26px,26px 1px,1px 26px; }
.result-zone{ min-height:340px; padding:36px 26px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:22px; }
.placeholder-hint{ color:#565656; font-size:.85rem; letter-spacing:.14em; text-transform:uppercase; text-align:center; line-height:2; }
.stage-error{ color:var(--flame); font-size:.9rem; text-align:center; border:1px dashed #4a1a1e; padding:16px 22px; background:rgba(163,0,0,.06); }
/* ══════════ РИТУАЛЬНЫЕ КАРТЫ ══════════ */
.player-group{ width:100%; max-width:760px; padding:20px 18px 24px; margin:0 auto 6px;
border:1px solid #191919; background:linear-gradient(180deg, rgba(163,0,0,.045), transparent); position:relative; }
.player-group::before{ content:""; position:absolute; top:-1px; left:18%; right:18%; height:1px;
background:linear-gradient(90deg, transparent, rgba(255,42,60,.6), transparent); }
.pg-title{ text-align:center; font-family:'Cinzel Decorative',serif; font-size:.78rem; letter-spacing:.34em;
text-transform:uppercase; color:var(--ash); margin-bottom:18px; }
.pg-title.host{ color:#ffb35c; text-shadow:0 0 12px rgba(255,160,60,.35); }
.pg-cards{ display:flex; flex-wrap:wrap; justify-content:center; gap:16px; }
.ritual-card{ perspective:900px; cursor:default; transition:transform .25s ease; will-change:transform; }
.rc-inner{ position:relative; width:100%; height:100%; transform-style:preserve-3d; transition:transform .85s cubic-bezier(.3,.7,.3,1); }
.ritual-card.flipped .rc-inner{ transform:rotateY(180deg); }
.rc-face{ position:absolute; inset:0; backface-visibility:hidden; -webkit-backface-visibility:hidden; border-radius:3px; overflow:hidden; }
.rc-back{ background:linear-gradient(160deg,#141414,#050505 70%); border:1px solid #262626;
display:flex; align-items:center; justify-content:center;
box-shadow:inset 0 0 30px rgba(0,0,0,.8), inset 0 1px 0 rgba(255,255,255,.05); }
.rc-back svg{ width:52%; height:auto; color:#4c4c4c; opacity:.8; transition:color .3s, opacity .3s; }
.rc-back::after{ content:""; position:absolute; inset:6px; border:1px solid #1f1f1f; }
.ritual-card:not(.flipped):hover .rc-back svg{ color:#8f8f8f; opacity:1; }
.rc-front{ transform:rotateY(180deg); background:linear-gradient(180deg,#101010,#060606);
border:1px solid #333; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px; padding:10px; }
.rc-front::before{ content:""; position:absolute; inset:4px; border:1px solid rgba(163,0,0,.4); pointer-events:none; }
.rc-front img{ 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); background:#111; }
.rc-name{ font-size:.7rem; font-weight:500; color:var(--bone); text-align:center; width:100%;
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; letter-spacing:.02em; }
.rc-sub{ font-size:.58rem; letter-spacing:.2em; text-transform:uppercase; color:var(--dim); }
.rc-char{ width:122px; height:168px; }
.rc-char .rc-front img{ width:76px; height:76px; }
.rc-boss{ width:210px; height:280px; }
.rc-boss .rc-front img{ width:118px; height:118px; }
.rc-boss .rc-name{ font-size:.82rem; white-space:normal; line-height:1.35; max-height:3.9em; }
.rc-guide{ margin-top:4px; font-size:.6rem; letter-spacing:.16em; text-transform:uppercase; color:var(--flame);
border:1px solid #4a1a1e; padding:5px 10px; background:rgba(163,0,0,.08); cursor:pointer; transition:all .25s; text-decoration:none; }
.rc-guide:hover{ box-shadow:0 0 14px rgba(255,42,60,.35); color:#fff; }
.card-shake{ animation:cardShake .3s ease; }
@keyframes cardShake{ 0%,100%{ transform:translateX(0);} 25%{ transform:translateX(-3px) rotate(-1deg);} 75%{ transform:translateX(3px) rotate(1deg);} }
/* ══════════ КОНТРАКТ (испытание) ══════════ */
.contract-card{ width:100%; max-width:660px; padding:40px 34px 30px; position:relative;
background:linear-gradient(180deg, rgba(16,10,11,.92), rgba(7,7,7,.95));
border:1px solid #33201f; box-shadow:0 0 50px rgba(163,0,0,.12), inset 0 0 40px rgba(0,0,0,.5); }
.contract-card::before{ content:""; position:absolute; inset:8px; border:1px solid rgba(163,0,0,.25); pointer-events:none; }
.contract-enter{ animation:contractRise .8s cubic-bezier(.2,.8,.2,1) both; }
@keyframes contractRise{ from{ opacity:0; transform:translateY(26px) scale(.97); filter:blur(4px);} to{ opacity:1; transform:none; filter:none;} }
.tier-badge{ display:flex; align-items:center; gap:14px; justify-content:center; margin-bottom:22px; }
.tb-roman{ font-family:'Cinzel Decorative',serif; font-size:2rem; font-weight:900; line-height:1;
width:58px; height:58px; display:flex; align-items:center; justify-content:center;
border:1px solid currentColor; border-radius:50%; box-shadow:0 0 18px currentColor, inset 0 0 12px rgba(0,0,0,.6); }
.tier-1{ color:#8f8f8f; } .tier-2{ color:#c25600; } .tier-3{ color:var(--flame); }
.tb-meta{ text-align:left; }
.tb-name{ display:block; font-family:'Cinzel Decorative',serif; font-size:.95rem; letter-spacing:.18em; text-transform:uppercase; color:var(--bone); }
.tb-points{ display:block; font-size:.62rem; letter-spacing:.3em; text-transform:uppercase; margin-top:4px; color:var(--dim); }
.cc-name{ margin:0 0 14px; text-align:center; font-size:1.35rem; font-weight:700; color:var(--bone); text-shadow:0 0 20px rgba(163,0,0,.5); }
.cc-text{ margin:0 0 6px; text-align:center; font-family:'Inter',sans-serif; font-weight:300; font-size:.95rem; line-height:1.75; color:var(--ash); }
.cc-divider{ display:flex; align-items:center; gap:12px; margin:26px 0 14px; color:var(--ember);
font-size:.6rem; letter-spacing:.4em; text-transform:uppercase; justify-content:center; }
.cc-divider::before,.cc-divider::after{ content:""; width:46px; height:1px; background:linear-gradient(90deg,transparent,rgba(163,0,0,.7)); }
.cc-divider::after{ background:linear-gradient(90deg,rgba(163,0,0,.7),transparent); }
.cc-divider svg{ width:12px; height:12px; color:var(--flame); filter:drop-shadow(0 0 5px rgba(255,42,60,.7)); }
.handicap-list{ list-style:none; margin:0; padding:0; }
.handicap-list li{ position:relative; padding:10px 14px 10px 34px; margin-bottom:7px; font-size:.85rem; color:var(--bone); font-weight:400;
background:rgba(163,0,0,.05); border:1px solid #201416; border-left:2px solid var(--ember); }
.handicap-list li::before{ content:"✕"; position:absolute; left:11px; top:50%; transform:translateY(-50%);
color:var(--flame); font-size:.75rem; text-shadow:0 0 8px rgba(255,42,60,.8); }
.player-task{ padding:12px 14px; margin-bottom:7px; background:rgba(255,255,255,.02); border:1px solid #1c1c1c; border-left:2px solid var(--ember); }
.player-task.host{ border-left-color:#ffb35c; }
.pt-title{ font-family:'Cinzel Decorative',serif; font-size:.62rem; letter-spacing:.3em; text-transform:uppercase; color:var(--flame); margin-bottom:5px; }
.player-task.host .pt-title{ color:#ffb35c; }
.pt-text{ font-size:.85rem; line-height:1.55; color:var(--bone); font-weight:400; }
.cc-curse{ margin-top:24px; text-align:center; font-size:.72rem; letter-spacing:.3em; text-transform:uppercase; color:var(--dim); }
.cc-curse b{ color:var(--flame); font-size:1rem; text-shadow:0 0 12px rgba(255,42,60,.8); margin-left:8px; }
.cc-actions{ margin-top:22px; display:flex; gap:12px; justify-content:center; flex-wrap:wrap; }
.smoking{ position:relative; }
.smoking::after{ content:""; position:absolute; inset:-14px; z-index:-1; filter:blur(14px); opacity:.5; pointer-events:none;
background:radial-gradient(40% 30% at 20% 80%, rgba(10,10,10,.9), transparent),
radial-gradient(45% 35% at 80% 85%, rgba(20,0,0,.85), transparent),
radial-gradient(35% 30% at 55% 95%, rgba(0,0,0,.9), transparent);
animation:smokeRise 5s ease-in-out infinite alternate; }
@keyframes smokeRise{ from{ transform:translateY(6px); opacity:.35;} to{ transform:translateY(-12px); opacity:.6;} }
/* ══════════ КНОПКИ ══════════ */
.summon-row{ display:flex; justify-content:center; margin-top:34px; }
.summon-btn{ position:relative; font-family:'Cinzel Decorative',serif; font-weight:700; font-size:1.15rem;
letter-spacing:.32em; text-transform:uppercase; color:#c9c9c9; cursor:pointer;
padding:20px 56px; border-radius:2px;
background:linear-gradient(180deg,#191919,#0a0a0a 60%,#050505);
border:1px solid #2e2e2e;
box-shadow:0 14px 34px rgba(0,0,0,.65), inset 0 1px 0 rgba(255,255,255,.06), inset 0 -8px 18px rgba(0,0,0,.7);
text-shadow:0 2px 3px #000; transition:color .3s, border-color .3s, box-shadow .3s; }
.summon-btn .s-cross{ display:inline-block; margin:0 14px; color:#777; transition:transform .8s cubic-bezier(.3,.7,.3,1), color .3s; }
.summon-btn:hover:not(:disabled){ color:#fff; border-color:#5c1218;
box-shadow:0 14px 34px rgba(0,0,0,.65), 0 0 34px rgba(163,0,0,.35), inset 0 0 24px rgba(163,0,0,.18);
animation:summonPulse 1.7s ease-in-out infinite; }
.summon-btn:hover:not(:disabled) .s-cross{ transform:rotate(180deg); color:var(--flame); text-shadow:0 0 12px rgba(255,42,60,.9); }
.summon-btn:disabled{ opacity:.55; cursor:wait; }
@keyframes summonPulse{ 0%,100%{ box-shadow:0 14px 34px rgba(0,0,0,.65), 0 0 22px rgba(163,0,0,.25), inset 0 0 18px rgba(163,0,0,.14);}
50%{ box-shadow:0 14px 34px rgba(0,0,0,.65), 0 0 46px rgba(163,0,0,.5), inset 0 0 30px rgba(163,0,0,.3);} }
.rv-btn{ font-family:'Inter',sans-serif; font-size:.72rem; font-weight:600; letter-spacing:.22em; text-transform:uppercase;
color:var(--ash); background:linear-gradient(180deg,#141414,#0a0a0a); border:1px solid #262626;
padding:12px 22px; border-radius:2px; cursor:pointer; transition:all .25s; }
.rv-btn:hover{ color:#fff; border-color:#4a1a1e; box-shadow:0 0 16px rgba(163,0,0,.3); }
.rv-btn-primary{ color:#ffdfe1; border-color:#5c1218; background:linear-gradient(180deg,#2a0c0f,#12060a); }
.rv-btn-primary:hover{ box-shadow:0 0 22px rgba(255,42,60,.4); color:#fff; }
.rv-btn-danger{ color:#e08a90; border-color:#3d1518; }
.rv-btn-danger:hover{ color:var(--flame); }
.rv-btn:disabled{ opacity:.5; cursor:not-allowed; }
.rv-btn-sm{ padding:9px 16px; font-size:.62rem; }
.save-bar{ display:flex; justify-content:center; gap:14px; margin-top:26px; flex-wrap:wrap; }
/* ══════════ МОДАЛКИ ══════════ */
.modal-wrap{ position:fixed; inset:0; z-index:100; display:flex; align-items:center; justify-content:center; padding:18px;
background:rgba(0,0,0,.55); backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px); }
.modal-panel{ background:rgba(13,13,13,.88); border:1px solid #242424; border-radius:3px;
backdrop-filter:blur(18px); box-shadow:0 30px 80px rgba(0,0,0,.7), 0 0 60px rgba(163,0,0,.12);
transform:scale(.95); opacity:0; transition:transform .3s cubic-bezier(.2,.8,.2,1), opacity .3s; }
.modal-wrap:not(.hidden) .modal-panel{ transform:scale(1); opacity:1; }
.modal-title{ font-size:1.35rem; font-weight:700; color:var(--bone); text-shadow:0 0 20px rgba(163,0,0,.5); margin:0; }
.settings-tab{ padding:9px 18px; border-radius:2px; background:transparent; border:1px solid transparent;
color:var(--dim); font-family:'Cinzel Decorative',serif; font-size:.68rem; letter-spacing:.16em; cursor:pointer;
text-transform:uppercase; transition:all .25s; white-space:nowrap; }
.settings-tab.active{ background:#150b0d; border-color:#3c181c; color:var(--flame); }
.settings-tab:hover:not(.active){ color:var(--bone); }
.rv-input{ background:#0c0c0c; border:1px solid #262626; border-radius:2px; padding:10px 14px; color:var(--bone);
font-family:'Inter',sans-serif; font-size:.85rem; font-weight:400; transition:all .25s; width:100%; }
.rv-input:focus{ outline:none; border-color:var(--ember); box-shadow:0 0 12px rgba(163,0,0,.3); }
select.rv-input option{ background:#0c0c0c; }
.boss-toggle{ display:flex; align-items:center; gap:10px; padding:9px 12px; border:1px solid #1c1c1c; border-radius:2px;
transition:all .2s; background:rgba(255,255,255,.012); }
.boss-toggle:hover{ border-color:#3c181c; background:rgba(163,0,0,.06); }
.boss-toggle input{ width:15px; height:15px; accent-color:var(--ember); cursor:pointer; flex-shrink:0; }
.boss-toggle label{ cursor:pointer; flex:1; font-size:.8rem; color:var(--ash); font-weight:400; }
.player-char-card{ position:relative; cursor:pointer; border:1px solid #1e1e1e; border-radius:2px; padding:8px 4px;
background:#0c0c0c; transition:all .25s; text-align:center; }
.player-char-card:hover{ border-color:#4a1a1e; transform:translateY(-2px); }
.player-char-card.selected{ border-color:var(--flame); box-shadow:0 0 14px rgba(255,42,60,.35); }
.player-char-card img{ width:44px; height:44px; border-radius:50%; object-fit:cover; margin:0 auto 6px; display:block;
border:1px solid #333; background:#111; }
.player-char-card.selected img{ border-color:var(--flame); }
.player-char-card p{ margin:0; font-size:.6rem; color:var(--ash); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; padding:0 3px; }
.player-char-card .remove-btn{ position:absolute; top:-7px; right:-7px; width:19px; height:19px; border-radius:50%;
background:linear-gradient(135deg,#7e0e18,#3c070c); color:#fff; font-size:.8rem; line-height:1; border:none; cursor:pointer;
opacity:0; transition:opacity .2s; box-shadow:0 2px 8px rgba(0,0,0,.5); }
.player-char-card:hover .remove-btn{ opacity:1; }
/* ══════════ ЛЕТОПИСЬ ══════════ */
.chron-entry{ position:relative; border:1px solid #1d1d1d; background:rgba(10,10,10,.7); padding:18px 18px 16px; border-radius:2px;
transition:border-color .3s, box-shadow .3s; }
.chron-entry.st-done{ border-color:#3d3d3d; box-shadow:0 0 0 1px rgba(180,180,180,.25), 0 0 22px rgba(163,0,0,.12); }
.chron-entry.st-failed{ opacity:.62; filter:grayscale(.55); }
.chron-head{ display:flex; align-items:center; gap:14px; margin-bottom:12px; flex-wrap:wrap; }
.st-sigil{ width:34px; height:34px; flex-shrink:0; position:relative; }
.st-sigil svg{ width:100%; height:100%; }
.st-active .st-sigil{ color:var(--flame); animation:stPulse 2s ease-in-out infinite; }
@keyframes stPulse{ 0%,100%{ filter:drop-shadow(0 0 3px rgba(255,42,60,.5)); opacity:.85;} 50%{ filter:drop-shadow(0 0 12px rgba(255,42,60,1)); opacity:1;} }
.st-done .st-sigil{ color:var(--flame); filter:drop-shadow(0 0 8px rgba(255,42,60,.9)); }
.st-failed .st-sigil{ color:#4a4a4a; filter:none; }
.st-failed .st-sigil::after{ content:""; position:absolute; left:8%; top:-4%; width:1.5px; height:112%;
background:linear-gradient(180deg, transparent, #000 20%, #6b6b6b 50%, #000 80%, transparent); transform:rotate(24deg); }
.chron-meta{ flex:1; min-width:140px; }
.chron-date{ font-size:.66rem; letter-spacing:.18em; color:var(--dim); text-transform:uppercase; }
.chron-mode{ font-size:.62rem; letter-spacing:.24em; color:var(--ash); text-transform:uppercase; margin-top:3px; }
.curse-badge{ display:flex; align-items:center; gap:8px; padding:7px 14px; border:1px solid #2a2a2a; border-radius:2px;
font-family:'Cinzel Decorative',serif; font-size:.7rem; letter-spacing:.2em; color:var(--ash); background:#0a0a0a; }
.curse-badge b{ color:var(--flame); font-size:.95rem; }
.curse-high{ border-color:#5c1218; color:#ffd9dc; animation:curseFlame 2.2s ease-in-out infinite; }
@keyframes curseFlame{ 0%,100%{ box-shadow:0 0 8px rgba(163,0,0,.35);} 50%{ box-shadow:0 0 26px rgba(255,42,60,.65);} }
.chron-body{ display:flex; flex-direction:column; gap:10px; }
.chron-block-title{ font-size:.58rem; letter-spacing:.34em; text-transform:uppercase; color:#5a5a5a; margin-bottom:6px; }
.chron-chars{ display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
.chron-char{ display:flex; flex-direction:column; align-items:center; width:64px; }
.chron-char img{ width:44px; height:44px; border-radius:50%; object-fit:cover; border:1.5px solid var(--ember); box-shadow:0 0 8px rgba(163,0,0,.4); background:#111; }
.chron-char span{ font-size:.56rem; color:var(--ash); margin-top:4px; text-align:center; line-height:1.2; max-height:2.4em; overflow:hidden; width:100%; }
.chron-group-label{ font-size:.56rem; letter-spacing:.22em; text-transform:uppercase; color:var(--dim); writing-mode:vertical-rl; text-orientation:mixed; }
.chron-boss{ display:flex; align-items:center; gap:12px; }
.chron-boss img{ width:52px; height:52px; border-radius:50%; object-fit:cover; border:1.5px solid var(--ember); background:#111; }
.chron-boss span{ font-size:.8rem; color:var(--bone); font-weight:400; }
.chron-trial{ font-size:.8rem; color:var(--ash); line-height:1.6; border-left:2px solid var(--ember); padding-left:12px; }
.chron-trial b{ color:var(--bone); font-weight:500; }
.chron-actions{ display:flex; gap:8px; margin-top:14px; flex-wrap:wrap; }
/* ══════════ FOOTER / TOAST / MUSIC ══════════ */
.app-footer{ margin-top:auto; padding:36px 24px 30px; text-align:center; position:relative; }
.app-footer::before{ content:""; position:absolute; top:0; left:15%; right:15%; height:1px;
background:linear-gradient(90deg, transparent, rgba(163,0,0,.5), transparent); }
.footer-author{ font-family:'Cinzel Decorative',serif; font-size:1rem; letter-spacing:.4em; color:var(--flame);
text-transform:uppercase; cursor:pointer; margin:16px 0 10px; transition:all .3s; display:inline-block; }
.footer-author:hover{ color:#fff; text-shadow:0 0 16px rgba(255,42,60,.9); }
.footer-disclaimer{ font-size:.66rem; color:#565656; line-height:1.8; max-width:520px; margin:0 auto; letter-spacing:.06em; }
#toast{ position:fixed; left:50%; bottom:34px; transform:translateX(-50%) translateY(20px); z-index:300;
background:rgba(13,13,13,.92); border:1px solid #4a1a1e; color:var(--bone); font-size:.75rem; letter-spacing:.14em;
padding:13px 26px; border-radius:2px; opacity:0; pointer-events:none; transition:all .4s cubic-bezier(.2,.8,.2,1);
box-shadow:0 0 30px rgba(163,0,0,.3); backdrop-filter:blur(8px); text-transform:uppercase; }
#toast.show{ opacity:1; transform:translateX(-50%) translateY(0); }
.music-control{ position:fixed; bottom:20px; right:20px; z-index:90; display:flex; align-items:center; gap:10px; }
.music-btn{ width:44px; height:44px; border-radius:2px; background:rgba(13,13,13,.85); backdrop-filter:blur(10px);
border:1px solid #262626; color:var(--flame); display:flex; align-items:center; justify-content:center; cursor:pointer; transition:all .25s; }
.music-btn:hover{ border-color:#4a1a1e; box-shadow:0 0 16px rgba(255,42,60,.35); }
.music-btn svg{ width:18px; height:18px; }
.volume-slider{ width:96px; height:3px; -webkit-appearance:none; appearance:none; background:rgba(163,0,0,.25); 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,42,60,.6); }
.volume-slider::-moz-range-thumb{ width:12px; height:12px; border-radius:2px; background:var(--flame); border:none; cursor:pointer; }
#audio-prompt{ position:fixed; inset:0; z-index:200; background:rgba(0,0,0,.96); display:flex; flex-direction:column;
align-items:center; justify-content:center; cursor:pointer; transition:opacity .6s; gap:10px; }
#audio-prompt .ap-sigil{ width:110px; height:110px; position:relative; margin-bottom:18px; animation:sigilBreathe 4s ease-in-out infinite; }
#audio-prompt .ap-sigil svg{ position:absolute; inset:0; }
.ap-title{ font-family:'Cinzel Decorative',serif; font-size:1.6rem; letter-spacing:.3em; color:var(--bone); text-shadow:0 0 30px rgba(163,0,0,.7); margin:0; text-transform:uppercase; }
.ap-sub{ font-size:.75rem; letter-spacing:.24em; color:var(--dim); text-transform:uppercase; margin:0; }
.hidden{ display:none !important; }
/* ══════════ АДАПТИВ ══════════ */
.mobile-menu-btn{ display:none; }
@media(max-width:1023px){
.sidebar{ transform:translateX(-102%); width:280px; box-shadow:30px 0 60px rgba(0,0,0,.7); }
.sidebar.open{ transform:translateX(0); }
.main-wrap{ margin-left:0; }
.topbar{ display:flex; align-items:center; justify-content:space-between; padding:12px 18px; position:sticky; top:0; z-index:40;
background:rgba(3,3,3,.85); backdrop-filter:blur(10px); border-bottom:1px solid #161616; }
.topbar h2{ margin:0; font-size:.8rem; letter-spacing:.24em; color:var(--ash); text-transform:uppercase; }
.mobile-menu-btn{ display:inline-flex; }
.main-content{ padding:30px 16px 10px; }
.edge-eye:nth-child(3), .edge-eye:nth-child(4){ display:none; }
}
.rv-iconbtn{ width:40px; height:40px; border-radius:2px; border:1px solid #262626; background:rgba(13,13,13,.85);
color:var(--flame); display:inline-flex; align-items:center; justify-content:center; cursor:pointer; transition:all .25s; }
.rv-iconbtn:hover{ border-color:#4a1a1e; box-shadow:0 0 14px rgba(255,42,60,.3); }
.rv-iconbtn svg{ width:17px; height:17px; }
@media(prefers-reduced-motion:reduce){
.edge-eye,.grain{ display:none; }
.ring,.bg-triq,.brand-sigil,.brand-sigil *{ animation:none !important; }
}
</style>
</head>
<body>
<!-- SVG-БИБЛИОТЕКА -->
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
<symbol id="triquetra" viewBox="0 0 100 100">
<g fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round">
<path d="M50 12 A22 22 0 0 0 50 52 A22 22 0 0 0 50 12 Z"/>
<path transform="rotate(120 50 50)" d="M50 12 A22 22 0 0 0 50 52 A22 22 0 0 0 50 12 Z"/>
<path transform="rotate(240 50 50)" d="M50 12 A22 22 0 0 0 50 52 A22 22 0 0 0 50 12 Z"/>
<circle cx="50" cy="50" r="7" stroke-width="2"/>
</g>
</symbol>
<symbol id="triquetra-ring" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="47" fill="none" stroke="currentColor" stroke-width="1.4" stroke-dasharray="4 7"/>
</symbol>
<symbol id="abyss-eye" viewBox="0 0 120 60">
<path d="M6 30 Q60 2 114 30 Q60 58 6 30Z" fill="rgba(0,0,0,.55)" stroke="currentColor" stroke-width="2"/>
<circle cx="60" cy="30" r="12" fill="none" stroke="currentColor" stroke-width="1.5" opacity=".8"/>
<circle class="ee-pupil" cx="60" cy="30" r="5" fill="currentColor"/>
<path d="M60 12 v-6 M60 48 v6" stroke="currentColor" stroke-width="1.5" opacity=".6"/>
</symbol>
</svg>
<!-- ЖИВОЙ ФОН -->
<div class="abyss" aria-hidden="true">
<div class="abyss-glow" id="glow-layer"></div>
<div class="abyss-glow2"></div>
<div id="parallax-rings">
<svg class="ring ring-a" viewBox="0 0 1000 1000" fill="none">
<circle cx="500" cy="500" r="470" stroke="#1c1c1c" stroke-width="1" stroke-dasharray="2 14"/>
<circle cx="500" cy="500" r="440" stroke="rgba(163,0,0,.28)" stroke-width="1" stroke-dasharray="60 26 8 26"/>
</svg>
<svg class="ring ring-b" viewBox="0 0 1000 1000" fill="none">
<circle cx="500" cy="500" r="470" stroke="#242424" stroke-width="1" stroke-dasharray="1 9"/>
<circle cx="500" cy="500" r="430" stroke="rgba(163,0,0,.2)" stroke-width="1.2" stroke-dasharray="120 40"/>
</svg>
<svg class="ring ring-c" viewBox="0 0 1000 1000" fill="none">
<circle cx="500" cy="500" r="480" stroke="rgba(163,0,0,.12)" stroke-width="1" stroke-dasharray="4 30"/>
</svg>
<svg class="bg-triq"><use href="#triquetra"/></svg>
</div>
</div>
<canvas id="dust-canvas" aria-hidden="true"></canvas>
<div class="grain" aria-hidden="true"></div>
<!-- ОЧИ БЕЗДНЫ -->
<div class="edge-eye" style="right:26px; top:96px;"><div class="ee-zone"></div><div class="ee-shape"><svg><use href="#abyss-eye"/></svg></div></div>
<div class="edge-eye" style="right:18px; top:46%;"><div class="ee-zone"></div><div class="ee-shape"><svg><use href="#abyss-eye"/></svg></div></div>
<div class="edge-eye" style="right:40px; bottom:130px;"><div class="ee-zone"></div><div class="ee-shape"><svg><use href="#abyss-eye"/></svg></div></div>
<div class="edge-eye" style="left:63%; top:22px;"><div class="ee-zone"></div><div class="ee-shape"><svg><use href="#abyss-eye"/></svg></div></div>
<!-- РИТУАЛЬНАЯ ВСПЫШКА -->
<div id="ritual-flash"><div class="rf-dark"></div><div class="rf-iris"></div></div>
<div class="app">
<!-- ═══ SIDEBAR ═══ -->
<aside class="sidebar" id="sidebar">
<div class="brand">
<div class="brand-sigil" id="brand-sigil" title="Трикветр Роновы">
<svg class="sigil-ring" style="color:#3a3a3a"><use href="#triquetra-ring"/></svg>
<svg class="sigil-core"><use href="#triquetra"/></svg>
</div>
<div class="brand-title">
<h1>СВЯТИЛИЩЕ</h1>
<p>Ронова · Тень Смерти</p>
</div>
</div>
<nav class="sidebar-nav">
<div class="sidebar-section">
<div class="side-title"><svg><use href="#triquetra"/></svg>Режим<span style="flex:1"></span></div>
<div class="grid grid-cols-4 gap-2" id="player-count-selector">
<button class="metal-btn active" data-val="1">Соло</button>
<button class="metal-btn" data-val="2">Дуо</button>
<button class="metal-btn" data-val="3">Трио</button>
<button class="metal-btn" data-val="4">Кооп</button>
</div>
<div class="mt-2 text-center text-[.62rem] tracking-[.2em] uppercase" style="color:#4c4c4c">
Персонажей: <span id="total-chars" style="color:var(--flame)">4</span>
</div>
</div>
<div class="sidebar-section">
<div class="side-title"><svg><use href="#triquetra"/></svg>Контракт</div>
<div id="category-selector">
<button class="nav-btn active" data-cat="characters">
<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="nav-btn" data-cat="bosses">
<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="nav-btn" data-cat="challenges">
<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="side-title"><svg><use href="#triquetra"/></svg>Тип босса</div>
<div class="grid grid-cols-3 gap-2" id="boss-type-selector">
<button class="metal-btn active" data-type="world">Мировые</button>
<button class="metal-btn" data-type="weekly">Недельные</button>
<button class="metal-btn" data-type="legend">Легенды</button>
</div>
</div>
<div class="sidebar-section">
<div class="side-title"><svg><use href="#triquetra"/></svg>Действия</div>
<button class="nav-btn" 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="nav-btn" 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="nav-btn" 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 class="sidebar-foot">✕ РОНОВА НАБЛЮДАЕТ ✕</div>
</aside>
<!-- ═══ ОСНОВНАЯ ЧАСТЬ ═══ -->
<div class="main-wrap">
<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>
<h2 id="topbar-title">Святилище Роновы</h2>
<div style="width:40px"></div>
</div>
<main class="main-content">
<div class="stage-head">
<h2 id="stage-title">Формирование Отряда</h2>
<div class="ornament"><svg class="orn-triq"><use href="#triquetra"/></svg></div>
<p class="stage-desc" id="stage-desc"></p>
</div>
<div class="stage-panel">
<!-- ОТРЯД -->
<section id="panel-characters">
<div class="result-zone" id="team-result"></div>
</section>
<!-- БОСС -->
<section id="panel-bosses" class="hidden">
<div class="result-zone" id="boss-result"></div>
</section>
<!-- ИСПЫТАНИЕ -->
<section id="panel-challenges" class="hidden">
<div class="result-zone" id="challenge-result"></div>
</section>
</div>
<div class="summon-row">
<button id="spin-btn" class="summon-btn">
<span class="s-cross">✕</span>ПРИЗВАТЬ<span class="s-cross">✕</span>
</button>
</div>
<div class="save-bar hidden" id="save-actions">
<button class="rv-btn rv-btn-primary" id="save-contract-btn">✕ &nbsp;Записать в контракт&nbsp; ✕</button>
</div>
</main>
<footer class="app-footer">
<div class="ornament"><svg class="orn-triq"><use href="#triquetra"/></svg></div>
<span class="footer-author" id="author-link">Mycc</span>
<p class="footer-disclaimer">
Это фанатский проект. Не аффилирован и не одобрен HoYoverse.<br>
Genshin Impact — товарный знак miHoYo / HoYoverse. Развлекательный контент.
</p>
</footer>
</div>
</div>
<!-- НАСТРОЙКИ -->
<div id="settings-modal" class="modal-wrap hidden">
<div class="modal-panel w-full max-w-4xl" style="height:86vh; display:flex; flex-direction:column; padding:26px;">
<div class="flex justify-between items-center mb-5">
<h3 class="modal-title">Настройки Святилища</h3>
<button id="settings-close" class="rv-iconbtn" style="font-size:1.1rem;">✕</button>
</div>
<div class="flex gap-2 mb-5 border-b border-[#1c1c1c] pb-4 overflow-x-auto">
<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 pr-2">
<div id="tab-general" class="settings-tab-content hidden">
<div class="max-w-sm">
<label class="block text-[.62rem] tracking-[.3em] uppercase mb-3" style="color:var(--dim)">Скорость ритуала</label>
<select id="animation-speed" class="rv-input">
<option value="fast">Быстрая</option>
<option value="medium" selected>Средняя</option>
<option value="slow">Медленная</option>
</select>
<p class="mt-5 text-[.72rem] leading-relaxed" style="color:#565656">Все настройки, результаты и Летопись сохраняются локально. Алтарь запомнит ваше состояние даже после закрытия вкладки.</p>
</div>
</div>
<div id="tab-chars" class="settings-tab-content">
<p class="text-[.75rem] mb-4 leading-relaxed" style="color:var(--ash)">Отметьте персонажей, которыми владеете. Рулетка будет выбирать только из них. Если ничего не выбрано — доступны все.</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-content 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-2"></div>
</div>
</div>
</div>
</div>
<!-- ЛЕТОПИСЬ -->
<div id="history-modal" class="modal-wrap hidden">
<div class="modal-panel w-full max-w-4xl" style="height:86vh; display:flex; flex-direction:column; padding:26px;">
<div class="flex justify-between items-center mb-2 flex-wrap gap-3">
<h3 class="modal-title">Летопись Контрактов</h3>
<div class="flex gap-2 items-center">
<button id="chron-sort-btn" class="rv-btn rv-btn-sm">Сортировка: Новые</button>
<button id="clear-history-button" class="rv-btn rv-btn-danger rv-btn-sm hidden">Сжечь всё</button>
<button id="history-close" class="rv-iconbtn" style="font-size:1.1rem;">✕</button>
</div>
</div>
<p class="text-[.66rem] tracking-[.2em] uppercase mb-5" style="color:#565656">Очки Проклятия = режим + босс + уровень испытания + порчи</p>
<div id="history-content" class="flex-grow overflow-y-auto pr-2 flex flex-col gap-4"></div>
</div>
</div>
<!-- О ПРОЕКТЕ -->
<div id="about-modal" class="modal-wrap hidden">
<div class="modal-panel w-full max-w-xl" style="padding:36px;">
<div class="text-center mb-6">
<div style="width:76px; height:76px; margin:0 auto 18px; position:relative;">
<svg style="position:absolute; inset:0; color:var(--flame); animation:sigilSpin 24s linear infinite;"><use href="#triquetra"/></svg>
</div>
<h3 class="modal-title mb-1">О Святилище</h3>
<div class="ornament mt-3"><svg class="orn-triq"><use href="#triquetra"/></svg></div>
</div>
<div class="space-y-4 text-[.85rem] leading-relaxed" style="color:var(--ash)">
<p><b style="color:var(--bone)">Святилище Роновы</b> — развлекательный фанатский инструмент для сообщества Genshin Impact: случайные отряды, боссы и «Контракты» — испытания трёх уровней Проклятия, чтобы оживить привычный геймплей.</p>
<p>Ронова — Тень Смерти, чей образ вдохновил эстетику алтаря: трикветр, багровое пламя и очи, следящие из тени.</p>
<p>Проект создан <b style="color:var(--flame)">Mycc</b>. Это исключительно развлекательный контент — не аффилирован и не одобрен HoYoverse.</p>
<p class="text-[.68rem]" style="color:#565656">Все данные хранятся локально в вашем браузере и никуда не передаются.</p>
</div>
<div class="text-center mt-8">
<button id="about-close" class="rv-btn">Закрыть</button>
</div>
</div>
</div>
<div id="toast"></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>
<div id="audio-prompt">
<div class="ap-sigil">
<svg style="color:var(--flame); animation:sigilSpin 14s linear infinite;"><use href="#triquetra"/></svg>
<svg style="color:#3a3a3a; animation:sigilSpin 9s linear infinite reverse;"><use href="#triquetra-ring"/></svg>
</div>
<h2 class="ap-title">Святилище Роновы</h2>
<p class="ap-sub">Нажмите где угодно, чтобы скрепить контракт со звуком</p>
</div>
<audio id="bg-music" src="hoyo_.mp3" loop preload="auto"></audio>
<audio id="boom-sound" src="boom.mp3" preload="auto"></audio>
<script>
document.addEventListener('DOMContentLoaded', () => {
/* ════════════════ ДАННЫЕ ════════════════ */
const LOCAL_CHARACTERS=[
{name:"Альбедо",icon:"pic/albedo.png",element:"Geo"},{name:"Аль-Хайтам",icon:"pic/alhaitham.png",element:"Dendro"},{name:"Алой",icon:"pic/aloy.png",element:"Cryo"},{name:"Эмбер",icon:"pic/amber.png",element:"Pyro"},{name:"Аратаки Итто",icon:"pic/arataki_itto.png",element:"Geo"},{name:"Арлекино",icon:"pic/arlecchino.png",element:"Pyro"},{name:"Бай Чжу",icon:"pic/baizhu.png",element:"Dendro"},{name:"Барбара",icon:"pic/barbara.png",element:"Hydro"},{name:"Бэй Доу",icon:"pic/beidou.png",element:"Electro"},{name:"Беннет",icon:"pic/bennett.png",element:"Pyro"},{name:"Кандакия",icon:"pic/candace.png",element:"Hydro"},{name:"Шарлотта",icon:"pic/charlotte.png",element:"Cryo"},{name:"Часка",icon:"pic/chasca.png",element:"Anemo"},{name:"Тиори",icon:"pic/chiori.png",element:"Geo"},{name:"Чун Юнь",icon:"pic/chongyun.png",element:"Cryo"},{name:"Ситлали",icon:"pic/citlali.png",element:"Cryo"},{name:"Клоринда",icon:"pic/clorinde.png",element:"Electro"},{name:"Коллеи",icon:"pic/collei.png",element:"Dendro"},{name:"Сайно",icon:"pic/cyno.png",element:"Electro"},{name:"Дэхья",icon:"pic/dehya.png",element:"Pyro"},{name:"Дилюк",icon:"pic/diluc.png",element:"Pyro"},{name:"Диона",icon:"pic/diona.png",element:"Cryo"},{name:"Дори",icon:"pic/dori.png",element:"Electro"},{name:"Эмилия",icon:"pic/emilie.png",element:"Dendro"},{name:"Эскофье",icon:"pic/escoffier.png",element:"Cryo"},{name:"Эола",icon:"pic/eula.png",element:"Cryo"},{name:"Фарузан",icon:"pic/faruzan.png",element:"Anemo"},{name:"Фишль",icon:"pic/fischl.png",element:"Electro"},{name:"Фремине",icon:"pic/freminet.png",element:"Cryo"},{name:"Фурина",icon:"pic/furina.png",element:"Hydro"},{name:"Га Мин",icon:"pic/gaming.png",element:"Pyro"},{name:"Гань Юй",icon:"pic/ganyu.png",element:"Cryo"},{name:"Горо",icon:"pic/gorou.png",element:"Geo"},{name:"Ху Тао",icon:"pic/hu_tao.png",element:"Pyro"},{name:"Ифу",icon:"pic/ifa.png",element:"Anemo"},{name:"Иансан",icon:"pic/iansan.png",element:"Electro"},{name:"Джинн",icon:"pic/jean.png",element:"Anemo"},{name:"Кадзуха",icon:"pic/kaedehara_kazuha.png",element:"Anemo"},{name:"Кэйа",icon:"pic/kaeya.png",element:"Cryo"},{name:"Качина",icon:"pic/Kachina.webp",element:"Geo"},{name:"Аяка",icon:"pic/kamisato_ayaka.png",element:"Cryo"},{name:"Аято",icon:"pic/kamisato_ayato.png",element:"Hydro"},{name:"Кавэх",icon:"pic/kaveh.png",element:"Dendro"},{name:"Кэ Цин",icon:"pic/keqing.png",element:"Electro"},{name:"Кинич",icon:"pic/kinich.png",element:"Dendro"},{name:"Кирара",icon:"pic/kirara.png",element:"Dendro"},{name:"Кли",icon:"pic/klee.png",element:"Pyro"},{name:"Сара",icon:"pic/kujou_sara.png",element:"Electro"},{name:"Шинобу",icon:"pic/kuki_shinobu.png",element:"Electro"},{name:"Лань Янь",icon:"pic/lan_yan.png",element:"Anemo"},{name:"Лейла",icon:"pic/layla.png",element:"Cryo"},{name:"Лиза",icon:"pic/lisa.png",element:"Electro"},{name:"Линнет",icon:"pic/lynette.png",element:"Anemo"},{name:"Лини",icon:"pic/lyney.png",element:"Pyro"},{name:"Мавуика",icon:"pic/mavuika.png",element:"Pyro"},{name:"Мика",icon:"pic/mika.png",element:"Cryo"},{name:"Мона",icon:"pic/mona.png",element:"Hydro"},{name:"Муалани",icon:"pic/mualani.png",element:"Hydro"},{name:"Нахида",icon:"pic/nahida.png",element:"Dendro"},{name:"Навия",icon:"pic/navia.png",element:"Geo"},{name:"Нёвиллет",icon:"pic/neuvillette.png",element:"Hydro"},{name:"Нилу",icon:"pic/nilou.png",element:"Hydro"},{name:"Нин Гуан",icon:"pic/ningguang.png",element:"Geo"},{name:"Ноэлль",icon:"pic/noelle.png",element:"Geo"},{name:"Оророн",icon:"pic/ororon.png",element:"Electro"},{name:"Ци Ци",icon:"pic/qiqi.png",element:"Cryo"},{name:"Райдэн",icon:"pic/raiden_shogun.png",element:"Electro"},{name:"Рэйзор",icon:"pic/razor.png",element:"Electro"},{name:"Розалья",icon:"pic/rosaria.png",element:"Cryo"},{name:"Кокоми",icon:"pic/sangonomiya_kokomi.png",element:"Hydro"},{name:"Саю",icon:"pic/sayu.png",element:"Anemo"},{name:"Сетос",icon:"pic/sethos.png",element:"Electro"},{name:"Шэнь Хэ",icon:"pic/shenhe.png",element:"Cryo"},{name:"Хэйдзо",icon:"pic/shikanoin_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/"}];
/* ═══ КОНТРАКТЫ РОНОВЫ: 3 УРОВНЯ ПРОКЛЯТИЯ ═══ */
const TRIAL_TIERS={
1:{name:'Прикосновение Тени',points:1,roman:'I',list:[
{name:'Око за Око',text:'Персонаж, активировавший Элементальный Взрыв, обязан оставаться на поле боя до завершения её анимации или действия. Переключаться на других героев во время ульты запрещено.'},
{name:'Поцелуй Снежной',text:'Запрещено использовать любую еду, зелья или карманные баффы перед началом боя. Только базовая сила отряда.'},
{name:'Слепая Вера',text:'Отключите мини-карту во время прохождения. Навигация к цели — только по памяти и чутью.'},
{name:'Запретный Плод',text:'Запрещено подбирать элементальные частицы, созданные персонажами противоположной стихии. Каждый питается лишь своей верой.'}]},
2:{name:'Тлеющий Контракт',points:3,roman:'II',list:[
{name:'Право на Ошибку',text:'Если во время боя погибает хотя бы один персонаж — вы обязаны немедленно сдаться (выйти или перезапустить подземелье). Ронова требует идеального исполнения: отряд выживает целиком или погибает полностью.'},
{name:'Голод Бездны',text:'Полная изоляция от родных стихий региона. Запрещено использовать персонажей Фонтейна в Фонтейне, Натлана в Натлане и так далее.'},
{name:'Печать Молчания',text:'Использовать элементальный навык и ульту разрешено только одному выбранному «жрецу» отряда. Остальные сражаются строго обычными и заряженными атаками.'},
{name:'Эхо Прошлого',text:'Запрещено использовать в отряде персонажей, вышедших в текущем (последнем) крупном патче игры. Допускаются только «старые» герои.'}]},
3:{name:'Лик Роновы',points:5,roman:'III',list:[
{name:'Очи Роновы',text:'Каждые 45 секунд боя вы обязаны менять активного персонажа по строгому кругу: 1 → 2 → 3 → 4 → 1. Любое нарушение ротации — провал контракта.'},
{name:'Час Искупления',text:'Сражение должно происходить строго на 1–2 уровнях Мира выше вашего текущего. Если уровень Мира максимальный — весь отряд экипирован артефактами не выше 4★ (фиолетовыми).'},
{name:'Взгляд Смерти',text:'Если HP вашего главного ДД опускается ниже 30%, вы не имеете права лечить его или накладывать щит. Он обязан добить врага «при смерти» — либо погибнуть.'},
{name:'Петля Времени',text:'Цель должна быть уничтожена строго за отведённое время — 120 секунд. Если время вышло, контракт аннулируется.'}]}
};
/* Системные порчи (НЕ игровые, буквально системные) */
const SYSTEM_HANDICAPS=[
'Ограничить FPS до 30 и выкрутить рендеринг на максимум','Ни разу не использовать кнопку спринта (Shift)','Прыгать каждые 5 секунд — в бою и вне его',
'Минимальное разрешение экрана + максимальные настройки графики','Полностью отключить музыку и звуки в игре','Играть в оконном режиме на 50% экрана',
'Не использовать карту и мини-карту','Управление только мышью / только клавиатурой (на выбор)','Камеру поворачивать минимум каждые 10 секунд',
'Переключать персонажей только клавишами 1–4','После каждого полученного урона — три прыжка подряд','Каждую минуту менять активного персонажа на случайного',
'Комментировать вслух каждое своё действие','Отключить V-Sync','Не использовать автоприцел — только ручная камера',
'Играть с инвертированной осью Y','Каждые 2 минуты делать скриншот «жертвоприношения»','Запрет на использование еды и зелий в бою'];
/* Роли для коопа */
const ROLE_TASKS={
2:[{title:'Разделение стихий',tasks:['использует ТОЛЬКО Пиро/Электро персонажей','использует ТОЛЬКО Гидро/Крио/Анемо персонажей']},
{title:'Саппорт и Клинок',tasks:['играет только саппортом/хилом (Беннет, Барбара, Кокоми, Джинн)','главный ДД — наносит весь урон']},
{title:'Контракт ветеранов',tasks:['играет только 4★ персонажами','играет только 5★ персонажами']},
{title:'Элементальный дуэт',tasks:['весь отряд — один элемент','весь отряд — тот же элемент']},
{title:'Бюджетный контракт',tasks:['запрещены 5★ персонажи и 5★ оружие','запрещены 5★ персонажи и 5★ оружие']},
{title:'Охотники на легенду',tasks:['отвлекает врага и готовит реакции','единственный, кто наносит урон (без хила)']},
{title:'Региональный обмен',tasks:['только Ли Юэ / Инадзума','только Сумеру / Фонтейн / Натлан']}],
3:[{title:'Треугольник силы',tasks:['2 ДД-персонажа, главный урон','только саппорт/хил (Беннет, Кокоми, Джинн)','контроль и реакции (Анемо/Крио/Электро)']},
{title:'Контракт Трёх Королей',tasks:['только 5★ персонажи','только 4★ персонажи','только стартовые герои и Путешественник']},
{title:'Региональный альянс',tasks:['только Мондштадт','только Ли Юэ / Инадзума','только Сумеру / Фонтейн / Натлан']},
{title:'Распределение ролей',tasks:['ДД + саппорт (2 перс.)','только щиты и защита','не наносит прямой урон — только реакции']},
{title:'Три стихии',tasks:['только Пиро','только Электро','только Гидро/Крио']}],
4:[{title:'Классический отряд',tasks:['главный ДД','саппорт/хил (Беннет, Кокоми, Барбара)','щит/контроль (Чжун Ли, Ноэлль, Венти)','суб-ДД и реакции (Фишль, Син Цю, Сян Линь)']},
{title:'Четыре стихии',tasks:['только Пиро','только Электро','только Гидро/Крио','только Анемо/Гео/Дендро']},
{title:'Бюджетный квартет',tasks:['только 4★','только 4★','только 4★','только стартовые / Путешественник']},
{title:'Региональный калейдоскоп',tasks:['только Мондштадт','только Ли Юэ / Инадзума','только Сумеру','только Фонтейн / Натлан']},
{title:'Слепая случайность',tasks:['играет тем, кого выбрала рулетка','играет тем, кого выбрала рулетка','играет тем, кого выбрала рулетка','играет тем, кого выбрала рулетка']}]
};
/* ════════════════ СОСТОЯНИЕ ════════════════ */
function getOrCreateUID(){ let u=localStorage.getItem('genshinRouletteUID'); if(!u){u=Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2);localStorage.setItem('genshinRouletteUID',u);} return u; }
const UID=getOrCreateUID();
const K={ session:`${UID}_rvSession`, chron:`${UID}_rvChronicle`, chars:`${UID}_playerCharacters`, excl:`${UID}_rvExcluded`, speed:`${UID}_rvSpeed` };
const $=id=>document.getElementById(id);
const charMap={}; LOCAL_CHARACTERS.forEach(c=>charMap[c.name]=c);
const wait=ms=>new Promise(r=>setTimeout(r,ms));
const pick=a=>a[Math.floor(Math.random()*a.length)];
const state={ category:'characters', playerCount:1, bossType:'world', teamResult:null, bossResult:null, challengeResult:null, spinning:false };
let playerCharacters=[]; try{ playerCharacters=JSON.parse(localStorage.getItem(K.chars))||[]; }catch(e){}
let excludedBosses={ 'world-bosses':[],'weekly-bosses':[],'local-legends':[] }; try{ const e=JSON.parse(localStorage.getItem(K.excl)); if(e) excludedBosses=e; }catch(e){}
let chronicle=[]; try{ chronicle=JSON.parse(localStorage.getItem(K.chron))||[]; }catch(e){}
let chronSort='date';
let animSpeed=localStorage.getItem(K.speed)||'medium';
function getDistribution(n){ return n===1?[4]: n===2?[2,2]: n===3?[2,1,1]:[1,1,1,1]; }
function playerLabel(i,n){ return (n===3&&i===0)?'Хостер':'Игрок '+(i+1); }
function modePoints(n){ return {1:0,2:1,3:2,4:3}[n]||0; }
function bossPoints(k){ return {world:1,weekly:3,legend:5}[k]||0; }
function computeCurse(){
let c=modePoints(state.playerCount);
if(state.bossResult) c+=bossPoints(state.bossResult.kind);
if(state.challengeResult) c+=state.challengeResult.tierPoints+state.challengeResult.handicaps.length;
return c;
}
/* ════════════════ ЗВУК ════════════════ */
let actx=null;
function audioCtx(){ try{ if(!actx) actx=new (window.AudioContext||window.webkitAudioContext)(); if(actx.state==='suspended') actx.resume(); }catch(e){} return actx; }
function playRitualTone(){ const c=audioCtx(); if(!c) return; try{
const o=c.createOscillator(),g=c.createGain(); o.type='sine';
o.frequency.setValueAtTime(72,c.currentTime); o.frequency.exponentialRampToValueAtTime(34,c.currentTime+.55);
g.gain.setValueAtTime(.0001,c.currentTime); g.gain.exponentialRampToValueAtTime(.5,c.currentTime+.07);
g.gain.exponentialRampToValueAtTime(.0001,c.currentTime+1);
o.connect(g).connect(c.destination); o.start(); o.stop(c.currentTime+1.1);
}catch(e){} }
function playTick(){ const c=audioCtx(); if(!c) return; try{
const o=c.createOscillator(),g=c.createGain(); o.type='triangle';
o.frequency.setValueAtTime(540,c.currentTime); o.frequency.exponentialRampToValueAtTime(220,c.currentTime+.12);
g.gain.setValueAtTime(.12,c.currentTime); g.gain.exponentialRampToValueAtTime(.0001,c.currentTime+.16);
o.connect(g).connect(c.destination); o.start(); o.stop(c.currentTime+.2);
}catch(e){} }
/* Музыка */
const bgMusic=$('bg-music'), musicToggle=$('music-toggle'), volumeSlider=$('volume-slider');
const musicIconOn=$('music-icon-on'), musicIconOff=$('music-icon-off'), audioPrompt=$('audio-prompt');
let isMusicPlaying=false;
function updateMusicIcon(){ musicIconOn.style.display=isMusicPlaying?'block':'none'; musicIconOff.style.display=isMusicPlaying?'none':'block'; }
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'),600); })
.catch(()=>{ isMusicPlaying=false; updateMusicIcon(); }); }
}
audioPrompt.addEventListener('click',()=>{ audioCtx(); bgMusic.play().then(()=>{isMusicPlaying=true;updateMusicIcon();}).catch(()=>{}); audioPrompt.style.opacity='0'; setTimeout(()=>audioPrompt.classList.add('hidden'),600); });
musicToggle.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 initDust(){
const canvas=$('dust-canvas'), ctx=canvas.getContext('2d'); let parts=[];
function resize(){ canvas.width=innerWidth; canvas.height=innerHeight; } resize(); addEventListener('resize',resize);
class P{
constructor(ember){ this.ember=ember; this.reset(true); }
reset(init){
this.x=Math.random()*canvas.width; this.y=init?Math.random()*canvas.height:canvas.height+10;
this.size=this.ember?Math.random()*1.8+.6:Math.random()*2.2+.8;
this.vx=(Math.random()-.5)*(this.ember?.35:.18);
this.vy=this.ember?-(Math.random()*.55+.18):(Math.random()-.5)*.12;
this.o=Math.random()*(this.ember?.55:.28)+.08;
this.fl=Math.random()*Math.PI*2;
}
update(){
this.fl+=.03; this.x+=this.vx+Math.sin(this.fl)*.12; this.y+=this.vy; this.o-=.0011;
if(this.y<-12||this.o<=0) this.reset(false);
}
draw(){
const a=Math.max(0,this.o+(this.ember?Math.sin(this.fl*2)*.12:0));
ctx.beginPath(); ctx.arc(this.x,this.y,this.size,0,Math.PI*2);
ctx.fillStyle=this.ember?`rgba(255,60,70,${a})`:`rgba(120,120,125,${a*.7})`;
if(this.ember){ ctx.shadowColor='rgba(255,42,60,.8)'; ctx.shadowBlur=6; } else ctx.shadowBlur=0;
ctx.fill(); ctx.shadowBlur=0;
}
}
for(let i=0;i<42;i++) parts.push(new P(false));
for(let i=0;i<26;i++) parts.push(new P(true));
(function loop(){ ctx.clearRect(0,0,canvas.width,canvas.height); parts.forEach(p=>{p.update();p.draw();}); requestAnimationFrame(loop); })();
}
/* Параллакс: кольца + свечение */
function initParallax(){
const rings=$('parallax-rings'), glow=$('glow-layer');
let tx=0,ty=0,cx=0,cy=0;
addEventListener('mousemove',e=>{ tx=e.clientX/innerWidth-.5; ty=e.clientY/innerHeight-.5; });
(function loop(){
cx+=(tx-cx)*.045; cy+=(ty-cy)*.045;
if(rings) rings.style.transform=`translate(${cx*-34}px,${cy*-24}px)`;
if(glow) glow.style.transform=`translate(calc(-50% + ${cx*18}px), calc(-50% + ${cy*14}px))`;
requestAnimationFrame(loop);
})();
}
/* Очи Бездны */
let ritualEyesOpen=false;
function initEdgeEyes(){
const eyes=[];
document.querySelectorAll('.edge-eye').forEach(el=>{
const shape=el.querySelector('.ee-shape'), pupil=el.querySelector('.ee-pupil'), zone=el.querySelector('.ee-zone');
const eye={el,shape,pupil,open:false}; eyes.push(eye);
zone.addEventListener('mouseenter',()=>{ eye.open=true; shape.classList.add('open'); });
zone.addEventListener('mouseleave',()=>{ eye.open=false; if(!ritualEyesOpen) shape.classList.remove('open'); });
zone.addEventListener('touchstart',()=>{ eye.open=true; shape.classList.add('open'); setTimeout(()=>{eye.open=false; if(!ritualEyesOpen) shape.classList.remove('open');},3000); },{passive:true});
});
document.addEventListener('mousemove',e=>{
eyes.forEach(eye=>{
if(!eye.open&&!ritualEyesOpen) return;
const r=eye.el.getBoundingClientRect();
const dx=e.clientX-(r.left+r.width/2), dy=e.clientY-(r.top+r.height/2);
const d=Math.hypot(dx,dy)||1, m=Math.min(5,d/45);
eye.pupil.setAttribute('transform',`translate(${(dx/d*m).toFixed(2)} ${(dy/d*m).toFixed(2)})`);
});
});
window.__forceEyes=ms=>{ ritualEyesOpen=true; eyes.forEach(e=>e.shape.classList.add('open')); setTimeout(()=>{ ritualEyesOpen=false; eyes.forEach(e=>{ if(!e.open) e.shape.classList.remove('open'); }); },ms); };
}
function ritualFlash(){
const f=$('ritual-flash'); f.classList.remove('active'); void f.offsetWidth; f.classList.add('active');
if(window.__forceEyes) window.__forceEyes(1500);
setTimeout(()=>f.classList.remove('active'),1000);
}
/* ════════════════ UI HELPERS ════════════════ */
let toastTimer=null;
function showToast(msg){ const t=$('toast'); t.textContent=msg; t.classList.add('show'); clearTimeout(toastTimer); toastTimer=setTimeout(()=>t.classList.remove('show'),2600); }
function flashBtn(btn){ btn.classList.remove('flash'); void btn.offsetWidth; btn.classList.add('flash'); }
function bindTilt(el){
el.addEventListener('mousemove',e=>{
const r=el.getBoundingClientRect();
const x=(e.clientX-r.left)/r.width-.5, y=(e.clientY-r.top)/r.height-.5;
el.style.transform=`perspective(700px) rotateY(${x*11}deg) rotateX(${-y*9}deg) translateY(-5px)`;
});
el.addEventListener('mouseleave',()=>{ el.style.transform=''; });
}
function buildRitualCard(cls){
const card=document.createElement('div');
card.className='ritual-card '+cls;
card.innerHTML=`<div class="rc-inner">
<div class="rc-face rc-back"><svg><use href="#triquetra"/></svg></div>
<div class="rc-face rc-front"></div></div>`;
bindTilt(card);
return card;
}
function charFront(ch){
const f=document.createElement('div'); f.className='rc-face rc-front';
f.innerHTML=`<img src="${ch.icon}" alt="${ch.name}" onerror="this.onerror=null;this.src='https://placehold.co/84x84/141414/FF4655?text=N/A'">
<div class="rc-name" title="${ch.name}">${ch.name}</div><div class="rc-sub">${ch.element||''}</div>`;
return f;
}
async function flipSequence(cards){
for(const c of cards){
c.classList.add('card-shake');
await wait(230);
c.classList.remove('card-shake');
c.classList.add('flipped');
playTick();
const b=$('boom-sound'); if(b){ b.currentTime=0; b.play().catch(()=>{}); }
await wait(210);
}
}
/* ════════════════ РЕНДЕР РЕЗУЛЬТАТОВ ════════════════ */
function renderTeamPlaceholder(){
const zone=$('team-result'); zone.innerHTML='';
const hint=document.createElement('div'); hint.className='placeholder-hint';
hint.innerHTML='Карты лежат рубашкой вверх.<br>Нажмите «Призвать», чтобы раскрыть судьбу.';
zone.appendChild(hint);
const row=document.createElement('div'); row.style.cssText='display:flex;gap:16px;opacity:.45;';
for(let i=0;i<4;i++){ const c=buildRitualCard('rc-char'); row.appendChild(c); }
zone.appendChild(row);
}
function renderBossPlaceholder(){
const zone=$('boss-result'); zone.innerHTML='';
const hint=document.createElement('div'); hint.className='placeholder-hint';
hint.innerHTML='Одна запечатанная карта ждёт.<br>Кто станет вашей жертвой?';
zone.appendChild(hint);
const c=buildRitualCard('rc-boss'); c.style.opacity='.45';
zone.appendChild(c);
}
function renderChallengePlaceholder(){
const zone=$('challenge-result'); zone.innerHTML='';
const hint=document.createElement('div'); hint.className='placeholder-hint';
hint.innerHTML='Контракт не подписан.<br>Ронова готова предложить условия.';
zone.appendChild(hint);
}
function renderTeam(animate){
const zone=$('team-result'); zone.innerHTML='';
if(!state.teamResult){ renderTeamPlaceholder(); return; }
state.teamResult.players.forEach((chars,pi)=>{
const g=document.createElement('div'); g.className='player-group';
const host=state.playerCount===3&&pi===0;
g.innerHTML=`<div class="pg-title ${host?'host':''}">${playerLabel(pi,state.playerCount)} · ${chars.length} перс.</div>`;
const row=document.createElement('div'); row.className='pg-cards';
chars.forEach(ch=>{
const card=buildRitualCard('rc-char');
card.querySelector('.rc-inner').replaceChild(charFront(ch),card.querySelector('.rc-front'));
if(!animate) card.classList.add('flipped');
row.appendChild(card);
});
g.appendChild(row); zone.appendChild(g);
});
if(animate) return flipSequence(zone.querySelectorAll('.ritual-card'));
}
function renderBoss(animate){
const zone=$('boss-result'); zone.innerHTML='';
if(!state.bossResult){ renderBossPlaceholder(); return Promise.resolve(); }
const b=state.bossResult.boss;
const card=buildRitualCard('rc-boss');
const front=document.createElement('div'); front.className='rc-face rc-front';
const kindName={world:'Мировой босс',weekly:'Еженедельный босс',legend:'Местная легенда'}[state.bossResult.kind];
front.innerHTML=`<img src="${b.icon}" alt="${b.name}" onerror="this.onerror=null;this.src='https://placehold.co/118x118/141414/FF4655?text=N/A'">
<div class="rc-name">${b.name}</div><div class="rc-sub">${kindName}</div>`;
if(state.bossResult.kind==='legend'&&b.guideUrl){
const a=document.createElement('a'); a.className='rc-guide'; a.href=b.guideUrl; a.target='_blank'; a.textContent='Гайд';
a.addEventListener('click',e=>e.stopPropagation()); front.appendChild(a);
}
card.querySelector('.rc-inner').replaceChild(front,card.querySelector('.rc-front'));
if(!animate) card.classList.add('flipped');
zone.appendChild(card);
if(animate) return flipSequence([card]);
}
function renderChallenge(animate){
const zone=$('challenge-result'); zone.innerHTML='';
if(!state.challengeResult){ renderChallengePlaceholder(); return; }
const c=state.challengeResult;
const card=document.createElement('div');
card.className='contract-card'+(c.tierId===3?' smoking':'')+(animate?' contract-enter':'');
let html=`<div class="tier-badge tier-${c.tierId}">
<span class="tb-roman">${c.roman}</span>
<span class="tb-meta"><span class="tb-name">${c.tierName}</span><span class="tb-points">+${c.tierPoints} очк. проклятия</span></span></div>
<h3 class="cc-name">«${c.trial.name}»</h3>
<p class="cc-text">${c.trial.text}</p>`;
if(c.tasks&&c.tasks.length){
html+=`<div class="cc-divider"><svg><use href="#triquetra"/></svg>Роли отряда<svg><use href="#triquetra"/></svg></div>`;
c.tasks.forEach(t=>{ html+=`<div class="player-task ${t.host?'host':''}"><div class="pt-title">${t.player}</div><div class="pt-text">${t.text}</div></div>`; });
}
if(c.handicaps.length){
html+=`<div class="cc-divider"><svg><use href="#triquetra"/></svg>Системные порчи<svg><use href="#triquetra"/></svg></div><ul class="handicap-list">`;
c.handicaps.forEach(h=>html+=`<li>${h}</li>`); html+=`</ul>`;
}
html+=`<div class="cc-curse">Итоговое проклятие контракта:<b>${computeCurse()}</b></div>
<div class="cc-actions">
<button class="rv-btn rv-btn-sm" id="reroll-trial">↻ Перебросить контракт</button>
<button class="rv-btn rv-btn-sm" id="reroll-handicap">↻ Перебросить порчи</button>
</div>`;
card.innerHTML=html;
zone.appendChild(card);
$('reroll-trial').addEventListener('click',()=>spinChallenge('trial'));
$('reroll-handicap').addEventListener('click',()=>spinChallenge('handicap'));
}
function updateSaveBar(){
$('save-actions').classList.toggle('hidden',!(state.teamResult||state.bossResult||state.challengeResult));
}
function updateStage(){
const modeNames={1:'Соло',2:'Дуо',3:'Трио',4:'Фулл-кооп'};
const dist=getDistribution(state.playerCount);
const distStr=dist.map((d,i)=>`${playerLabel(i,state.playerCount)}=${d}`).join(' · ');
let title,desc;
if(state.category==='characters'){ title='Формирование Отряда'; desc=`${modeNames[state.playerCount]} · ${distStr} · Рулетка выберет ${dist.reduce((a,b)=>a+b,0)} персонажей и раскроет карты рубашкой вверх.`; }
else if(state.category==='bosses'){ const tn={world:'Мировые боссы',weekly:'Еженедельные боссы',legend:'Местные легенды'}; title='Поиск Босса'; desc=`${tn[state.bossType]} · Очки проклятия за босса: +${bossPoints(state.bossType)}.`; }
else{ title='Контракт Роновы'; desc=`Испытание для режима «${modeNames[state.playerCount]}». Три уровня Проклятия: I (+1) · II (+3) · III (+5).`; }
$('stage-title').textContent=title; $('stage-desc').textContent=desc;
$('total-chars').textContent=dist.reduce((a,b)=>a+b,0);
}
function showCategory(cat){
state.category=cat;
document.querySelectorAll('#category-selector .nav-btn').forEach(b=>{
const on=b.dataset.cat===cat; b.classList.toggle('active',on); if(on) flashBtn(b);
});
$('panel-characters').classList.toggle('hidden',cat!=='characters');
$('panel-bosses').classList.toggle('hidden',cat!=='bosses');
$('panel-challenges').classList.toggle('hidden',cat!=='challenges');
$('boss-sub-settings').classList.toggle('hidden',cat!=='bosses');
updateStage(); persistSession();
}
/* ════════════════ ГЕНЕРАЦИЯ ════════════════ */
function setSpinLock(on,label){
state.spinning=on; const b=$('spin-btn');
b.disabled=on;
if(on){ b.innerHTML='<span class="s-cross">✕</span>'+label+'<span class="s-cross">✕</span>'; }
else{ b.innerHTML='<span class="s-cross">✕</span>ПРИЗВАТЬ<span class="s-cross">✕</span>'; }
}
function stageSpeed(){ return animSpeed==='fast'?.55:animSpeed==='slow'?1.6:1; }
async function spinTeam(){
if(state.spinning) return;
const pool=playerCharacters.length?playerCharacters:LOCAL_CHARACTERS;
const dist=getDistribution(state.playerCount);
const need=dist.reduce((a,b)=>a+b,0);
if(pool.length<need){
$('team-result').innerHTML=`<div class="stage-error">Недостаточно персонажей: нужно ${need}, доступно ${pool.length}.<br>Расширьте список в Настройках или выберите режим проще.</div>`;
return;
}
setSpinLock(true,'РИТУАЛ...');
ritualFlash(); playRitualTone();
await wait(520*stageSpeed());
const p=[...pool]; const players=[];
dist.forEach(cnt=>{ const arr=[]; for(let i=0;i<cnt;i++) arr.push(p.splice(Math.floor(Math.random()*p.length),1)[0]); players.push(arr); });
state.teamResult={distribution:dist,players};
renderTeam(false);
await flipSequence($('team-result').querySelectorAll('.ritual-card'));
updateSaveBar(); persistSession(); setSpinLock(false);
}
async function spinBoss(){
if(state.spinning) return;
const data=state.bossType==='world'?{list:WORLD_BOSSES,key:'world-bosses'}
:state.bossType==='weekly'?{list:WEEKLY_BOSSES,key:'weekly-bosses'}
:{list:LOCAL_LEGENDS,key:'local-legends'};
const available=data.list.filter(b=>!excludedBosses[data.key].includes(b.name));
if(!available.length){
$('boss-result').innerHTML=`<div class="stage-error">Все цели этой категории исключены в Настройках. Верните хотя бы одну душу в пул.</div>`;
return;
}
setSpinLock(true,'ИЩЕМ ЖЕРТВУ...');
ritualFlash(); playRitualTone();
await wait(650*stageSpeed());
state.bossResult={kind:state.bossType,boss:pick(available)};
renderBoss(false);
await flipSequence($('boss-result').querySelectorAll('.ritual-card'));
updateSaveBar(); persistSession(); setSpinLock(false);
}
function rollHandicaps(){ const s=[...SYSTEM_HANDICAPS].sort(()=>Math.random()-.5); return s.slice(0,1+Math.floor(Math.random()*2)); }
function rollTier(){ const r=Math.random(); return r<.45?1:r<.8?2:3; }
function buildTasks(){
if(state.playerCount===1) return [];
const t=pick(ROLE_TASKS[state.playerCount]);
return t.tasks.map((text,i)=>({player:playerLabel(i,state.playerCount),text:`${text}`,host:state.playerCount===3&&i===0,title:t.title}));
}
async function spinChallenge(part){
if(state.spinning) return;
setSpinLock(true,'ЧИТАЕМ КОНТРАКТ...');
ritualFlash(); playRitualTone();
await wait(560*stageSpeed());
const prev=state.challengeResult;
let tierId,tier,trial,tasks,handicaps;
if(part==='handicap'&&prev){
tierId=prev.tierId; trial=prev.trial; tasks=prev.tasks; handicaps=rollHandicaps();
}else{
tierId=rollTier(); trial=pick(TRIAL_TIERS[tierId].list);
tasks=(state.playerCount>1)?buildTasks():[];
handicaps=(part==='trial'&&prev)?prev.handicaps:rollHandicaps();
}
tier=TRIAL_TIERS[tierId];
state.challengeResult={tierId,tierName:tier.name,tierPoints:tier.points,roman:tier.roman,trial,tasks,handicaps};
renderChallenge(true); playTick();
updateSaveBar(); persistSession(); setSpinLock(false);
}
$('spin-btn').addEventListener('click',()=>{
if(state.category==='characters') spinTeam();
else if(state.category==='bosses') spinBoss();
else spinChallenge('all');
});
/* ════════════════ ЛЕТОПИСЬ ════════════════ */
function persistChronicle(){ localStorage.setItem(K.chron,JSON.stringify(chronicle)); }
function saveContract(){
if(!(state.teamResult||state.bossResult||state.challengeResult)){ showToast('Сначала призовите хотя бы одну сущность'); return; }
const entry={ id:Date.now(), date:new Date().toISOString(), status:'active', curse:computeCurse(), mode:state.playerCount,
team:state.teamResult?{distribution:state.teamResult.distribution,players:state.teamResult.players.map(a=>a.map(c=>c.name))}:null,
boss:state.bossResult?{kind:state.bossResult.kind,name:state.bossResult.boss.name}:null,
challenge:state.challengeResult?JSON.parse(JSON.stringify(state.challengeResult)):null };
chronicle.unshift(entry); persistChronicle(); renderChronicle();
showToast('Контракт записан в Летопись');
}
$('save-contract-btn').addEventListener('click',saveContract);
function resolveBossByName(name){
const all=[...WORLD_BOSSES.map(b=>({...b,kind:'world'})),...WEEKLY_BOSSES.map(b=>({...b,kind:'weekly'})),...LOCAL_LEGENDS.map(b=>({...b,kind:'legend'}))];
return all.find(b=>b.name===name);
}
function setStatus(id,status){ const e=chronicle.find(x=>x.id===id); if(e){ e.status=status; persistChronicle(); renderChronicle(); } }
function deleteEntry(id){ chronicle=chronicle.filter(x=>x.id!==id); persistChronicle(); renderChronicle(); }
function renderChronicle(){
const box=$('history-content'); box.innerHTML='';
if(!chronicle.length){
box.innerHTML='<p class="text-center italic" style="color:#565656; padding:40px 0;">Летопись пуста... заключите свой первый контракт.</p>';
$('clear-history-button').classList.add('hidden'); return;
}
$('clear-history-button').classList.remove('hidden');
let list=[...chronicle];
if(chronSort==='curse') list.sort((a,b)=>b.curse-a.curse||new Date(b.date)-new Date(a.date));
else list.sort((a,b)=>new Date(b.date)-new Date(a.date));
list.forEach(item=>{
const el=document.createElement('div');
el.className='chron-entry st-'+item.status;
/* шапка */
const head=document.createElement('div'); head.className='chron-head';
head.innerHTML=`<div class="st-sigil"><svg><use href="#triquetra"/></svg></div>
<div class="chron-meta">
<div class="chron-date">${new Date(item.date).toLocaleString()}</div>
<div class="chron-mode">Режим: ${{1:'Соло',2:'Дуо',3:'Трио',4:'Фулл-кооп'}[item.mode]||'?'}</div>
</div>
<div class="curse-badge ${item.curse>=8?'curse-high':''}">ПРОКЛЯТИЕ <b>${item.curse}</b></div>`;
el.appendChild(head);
/* тело */
const body=document.createElement('div'); body.className='chron-body';
if(item.team){
const blk=document.createElement('div');
blk.innerHTML='<div class="chron-block-title">Отряд</div>';
const row=document.createElement('div'); row.className='chron-chars';
item.team.players.forEach((arr,pi)=>{
const lb=document.createElement('span'); lb.className='chron-group-label';
lb.textContent=playerLabel(pi,item.mode); row.appendChild(lb);
arr.forEach(nm=>{
const ch=charMap[nm]||{name:nm,icon:'',element:''};
const c=document.createElement('div'); c.className='chron-char';
c.innerHTML=`<img src="${ch.icon}" alt="${nm}" onerror="this.onerror=null;this.src='https://placehold.co/44x44/141414/FF4655?text=N/A'"><span>${nm}</span>`;
row.appendChild(c);
});
});
blk.appendChild(row); body.appendChild(blk);
}
if(item.boss){
const b=resolveBossByName(item.boss.name);
const blk=document.createElement('div');
blk.innerHTML='<div class="chron-block-title">Босс</div>';
const row=document.createElement('div'); row.className='chron-boss';
row.innerHTML=`<img src="${b?b.icon:''}" alt="" onerror="this.onerror=null;this.src='https://placehold.co/52x52/141414/FF4655?text=N/A'"><span>${item.boss.name}</span>`;
blk.appendChild(row); body.appendChild(blk);
}
if(item.challenge){
const blk=document.createElement('div');
blk.innerHTML='<div class="chron-block-title">Контракт</div>';
const tr=document.createElement('div'); tr.className='chron-trial';
let inner=`<b>[${item.challenge.roman}] ${item.challenge.tierName}</b> — «${item.challenge.trial.name}»`;
if(item.challenge.tasks&&item.challenge.tasks.length) inner+='<br>'+item.challenge.tasks.map(t=>`${t.player}: ${t.text}`).join(' · ');
if(item.challenge.handicaps&&item.challenge.handicaps.length) inner+='<br><span style="color:#6f6f6f">Порчи: '+item.challenge.handicaps.join('; ')+'</span>';
tr.innerHTML=inner; blk.appendChild(tr); body.appendChild(blk);
}
el.appendChild(body);
/* действия */
const act=document.createElement('div'); act.className='chron-actions';
if(item.status==='active'){
const ok=document.createElement('button'); ok.className='rv-btn rv-btn-sm rv-btn-primary'; ok.textContent='✓ Выполнено';
ok.addEventListener('click',()=>setStatus(item.id,'done'));
const fail=document.createElement('button'); fail.className='rv-btn rv-btn-sm rv-btn-danger'; fail.textContent='✕ Провалено';
fail.addEventListener('click',()=>setStatus(item.id,'failed'));
act.appendChild(ok); act.appendChild(fail);
}else{
const back=document.createElement('button'); back.className='rv-btn rv-btn-sm'; back.textContent='↺ Вернуть в актив';
back.addEventListener('click',()=>setStatus(item.id,'active')); act.appendChild(back);
}
const del=document.createElement('button'); del.className='rv-btn rv-btn-sm rv-btn-danger'; del.textContent='Сжечь';
del.addEventListener('click',()=>showConfirm('Сжечь этот контракт безвозвратно?',()=>deleteEntry(item.id)));
act.appendChild(del);
el.appendChild(act);
box.appendChild(el);
});
}
$('chron-sort-btn').addEventListener('click',()=>{
chronSort=chronSort==='date'?'curse':'date';
$('chron-sort-btn').textContent='Сортировка: '+(chronSort==='date'?'Новые':'По Проклятию');
renderChronicle();
});
$('clear-history-button').addEventListener('click',()=>showConfirm('Сжечь всю Летопись Контрактов?',()=>{ chronicle=[]; persistChronicle(); renderChronicle(); }));
/* ════════════════ НАСТРОЙКИ ════════════════ */
function renderBossSettings(){
const cat=$('boss-category-select').value;
const list=cat==='world-bosses'?WORLD_BOSSES:cat==='weekly-bosses'?WEEKLY_BOSSES:LOCAL_LEGENDS;
const box=$('bosses-settings-container'); box.innerHTML='';
list.forEach(boss=>{
const isEx=excludedBosses[cat].includes(boss.name);
const row=document.createElement('div'); row.className='boss-toggle';
const id='bx-'+boss.name.replace(/[^\wа-яА-ЯёЁ]+/g,'-');
row.innerHTML=`<input type="checkbox" id="${id}" ${isEx?'':'checked'}><label for="${id}">${boss.name}</label>`;
box.appendChild(row);
row.querySelector('input').addEventListener('change',e=>{
const l=excludedBosses[cat];
if(e.target.checked){ const i=l.indexOf(boss.name); if(i>-1) l.splice(i,1); }
else if(!l.includes(boss.name)) l.push(boss.name);
localStorage.setItem(K.excl,JSON.stringify(excludedBosses));
});
});
}
function renderPlayerChars(filter=''){
const box=$('player-characters-selection'); box.innerHTML='';
LOCAL_CHARACTERS.filter(c=>c.name.toLowerCase().includes(filter.toLowerCase())).forEach(ch=>{
const card=document.createElement('div'); card.className='player-char-card';
const sel=playerCharacters.some(c=>c.name===ch.name);
if(sel) card.classList.add('selected');
card.innerHTML=`<img src="${ch.icon}" alt="${ch.name}" onerror="this.onerror=null;this.src='https://placehold.co/44x44/141414/FF4655?text=N/A'">
<p>${ch.name}</p>${sel?'<button class="remove-btn">×</button>':''}`;
card.addEventListener('click',e=>{
if(e.target.classList.contains('remove-btn')){ playerCharacters=playerCharacters.filter(c=>c.name!==ch.name); }
else{ const i=playerCharacters.findIndex(c=>c.name===ch.name); if(i>-1) playerCharacters.splice(i,1); else playerCharacters.push(ch); }
localStorage.setItem(K.chars,JSON.stringify(playerCharacters));
renderPlayerChars($('character-search').value);
});
box.appendChild(card);
});
$('clear-player-characters').classList.toggle('hidden',!playerCharacters.length);
}
$('character-search').addEventListener('input',e=>renderPlayerChars(e.target.value));
$('clear-player-characters').addEventListener('click',()=>showConfirm('Очистить список ваших персонажей?',()=>{ playerCharacters=[]; localStorage.removeItem(K.chars); renderPlayerChars(''); }));
$('boss-category-select').addEventListener('change',renderBossSettings);
$('animation-speed').addEventListener('change',e=>{ animSpeed=e.target.value; localStorage.setItem(K.speed,animSpeed); });
/* ════════════════ МОДАЛКИ ════════════════ */
function openModal(id){ $(id).classList.remove('hidden'); }
function closeModal(id){ $(id).classList.add('hidden'); }
$('settings-toggle').addEventListener('click',()=>{ closeSidebarMobile(); openModal('settings-modal'); });
$('settings-close').addEventListener('click',()=>closeModal('settings-modal'));
$('settings-modal').addEventListener('click',e=>{ if(e.target===$('settings-modal')) closeModal('settings-modal'); });
$('history-toggle').addEventListener('click',()=>{ closeSidebarMobile(); renderChronicle(); openModal('history-modal'); });
$('history-close').addEventListener('click',()=>closeModal('history-modal'));
$('history-modal').addEventListener('click',e=>{ if(e.target===$('history-modal')) closeModal('history-modal'); });
$('about-toggle').addEventListener('click',()=>{ closeSidebarMobile(); openModal('about-modal'); });
$('about-close').addEventListener('click',()=>closeModal('about-modal'));
$('about-modal').addEventListener('click',e=>{ if(e.target===$('about-modal')) closeModal('about-modal'); });
$('author-link').addEventListener('click',()=>openModal('about-modal'));
document.querySelectorAll('.settings-tab').forEach(tab=>{
tab.addEventListener('click',()=>{
document.querySelectorAll('.settings-tab').forEach(t=>t.classList.remove('active'));
document.querySelectorAll('.settings-tab-content').forEach(c=>c.classList.add('hidden'));
tab.classList.add('active'); flashBtn(tab);
document.getElementById('tab-'+tab.dataset.tab).classList.remove('hidden');
});
});
function showConfirm(msg,onOk){
const m=document.createElement('div'); m.className='modal-wrap';
m.innerHTML=`<div class="modal-panel" style="max-width:380px; padding:32px; text-align:center;">
<h3 class="modal-title" style="font-size:1.05rem; margin:0 0 14px;">Подтверждение</h3>
<p style="color:var(--ash); font-size:.85rem; line-height:1.6; margin:0 0 22px;">${msg}</p>
<div style="display:flex; gap:12px; justify-content:center;">
<button class="rv-btn rv-btn-sm c-cancel">Отмена</button>
<button class="rv-btn rv-btn-sm rv-btn-danger c-ok">Подтвердить</button>
</div></div>`;
document.body.appendChild(m);
m.querySelector('.c-cancel').addEventListener('click',()=>m.remove());
m.querySelector('.c-ok').addEventListener('click',()=>{ onOk(); m.remove(); });
m.addEventListener('click',e=>{ if(e.target===m) m.remove(); });
}
/* ════════════════ СЕЛЕКТОРЫ ════════════════ */
document.querySelectorAll('#player-count-selector .metal-btn').forEach(btn=>{
btn.addEventListener('click',()=>{
document.querySelectorAll('#player-count-selector .metal-btn').forEach(b=>b.classList.remove('active'));
btn.classList.add('active'); flashBtn(btn);
state.playerCount=parseInt(btn.dataset.val);
updateStage(); persistSession();
});
});
document.querySelectorAll('#category-selector .nav-btn').forEach(btn=>{
btn.addEventListener('click',()=>{ closeSidebarMobile(); showCategory(btn.dataset.cat); });
});
document.querySelectorAll('#boss-type-selector .metal-btn').forEach(btn=>{
btn.addEventListener('click',()=>{
document.querySelectorAll('#boss-type-selector .metal-btn').forEach(b=>b.classList.remove('active'));
btn.classList.add('active'); flashBtn(btn);
state.bossType=btn.dataset.type;
updateStage(); persistSession();
});
});
/* мобильное меню */
const sidebar=$('sidebar');
$('mobile-menu-btn').addEventListener('click',e=>{ e.stopPropagation(); sidebar.classList.toggle('open'); });
document.addEventListener('click',e=>{ if(innerWidth<1024&&sidebar.classList.contains('open')&&!sidebar.contains(e.target)&&e.target!==$('mobile-menu-btn')) sidebar.classList.remove('open'); });
function closeSidebarMobile(){ if(innerWidth<1024) sidebar.classList.remove('open'); }
$('brand-sigil').addEventListener('click',()=>showToast('Ронова наблюдает за вами...'));
/* ════════════════ СОХРАНЕНИЕ СЕССИИ ════════════════ */
function persistSession(){
try{
localStorage.setItem(K.session,JSON.stringify({
category:state.category, playerCount:state.playerCount, bossType:state.bossType,
team:state.teamResult?{distribution:state.teamResult.distribution,players:state.teamResult.players.map(a=>a.map(c=>c.name))}:null,
boss:state.bossResult?{kind:state.bossResult.kind,name:state.bossResult.boss.name}:null,
challenge:state.challengeResult
}));
}catch(e){}
}
function restoreSession(){
let s=null; try{ s=JSON.parse(localStorage.getItem(K.session)); }catch(e){}
if(!s) return;
if(s.playerCount){ state.playerCount=s.playerCount; document.querySelectorAll('#player-count-selector .metal-btn').forEach(b=>b.classList.toggle('active',parseInt(b.dataset.val)===state.playerCount)); }
if(s.bossType){ state.bossType=s.bossType; document.querySelectorAll('#boss-type-selector .metal-btn').forEach(b=>b.classList.toggle('active',b.dataset.type===state.bossType)); }
if(s.team){
const players=s.team.players.map(arr=>arr.map(nm=>charMap[nm]||{name:nm,icon:'',element:''}));
state.teamResult={distribution:s.team.distribution,players};
}
if(s.boss){ const b=resolveBossByName(s.boss.name); if(b) state.bossResult={kind:s.boss.kind,boss:b}; }
if(s.challenge) state.challengeResult=s.challenge;
if(s.category) state.category=s.category;
}
/* ════════════════ СТАРТ ════════════════ */
restoreSession();
document.querySelectorAll('#category-selector .nav-btn').forEach(b=>b.classList.toggle('active',b.dataset.cat===state.category));
$('panel-characters').classList.toggle('hidden',state.category!=='characters');
$('panel-bosses').classList.toggle('hidden',state.category!=='bosses');
$('panel-challenges').classList.toggle('hidden',state.category!=='challenges');
$('boss-sub-settings').classList.toggle('hidden',state.category!=='bosses');
renderTeam(false); renderBoss(false); renderChallenge(false);
updateStage(); updateSaveBar();
$('animation-speed').value=animSpeed;
initMusic(); initDust(); initParallax(); initEdgeEyes();
renderPlayerChars(); renderBossSettings();
});
</script>
</body>
</html>