Download indexBackup.html from Mycc/GG: direct link, hf CLI and curl.
- Browser
- Download file 123 kB
-
https://huggingface.co/spaces/Mycc/GG/resolve/main/indexBackup.html
- Command line
-
hf download hf://spaces/Mycc/GG/indexBackup.html
-
curl -L -o indexBackup.html https://huggingface.co/spaces/Mycc/GG/resolve/main/indexBackup.html
123 kB
| <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">✕ Записать в контракт ✕</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> | |