NEON CASES — кейсы, апгрейдер, кликер
by LuckyBuilder792967 lines209.2 KB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NEON CASES — кейсы, апгрейдер, кликер</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#080b11; --panel:#111823; --panel2:#18212e; --line:#243040;
--text:#e8eef7; --muted:#8496ab; --accent:#ff8a00;
--glow-1:#ff00ff; --glow-2:#00e5ff; --glow-3:#ff8a00;
}
[data-theme="light"] {
--bg:#e8eef7; --panel:#ffffff; --panel2:#f0f4f9; --line:#d0d9e6;
--text:#111823; --muted:#6b7a8d; --accent:#e07300;
}
html{scrollbar-color:#2c3b4d #0b1118}
body{font-family:'Segoe UI',Roboto,Arial,sans-serif;background-color:var(--bg);color:var(--text);min-height:100vh;position:relative;overflow-x:hidden;transition:background-color 0.3s, color 0.3s;}
.bg-anim { position: fixed; inset: -50%; z-index: -3; background: linear-gradient(45deg, #05070a, #160b26, #0b2616, #05070a); background-size: 400% 400%; animation: gradientShift 25s ease infinite; }
@keyframes gradientShift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
body::before{content:'';position:fixed;inset:-20%;z-index:-2;
background:radial-gradient(circle at 20% 30%, rgba(255, 0, 255, 0.15), transparent 50%),
radial-gradient(circle at 80% 70%, rgba(0, 229, 255, 0.15), transparent 50%),
radial-gradient(circle at 50% 50%, rgba(255, 138, 0, 0.1), transparent 60%),
radial-gradient(circle at 10% 90%, rgba(157, 78, 221, 0.1), transparent 55%),
radial-gradient(circle at 90% 10%, rgba(57, 255, 20, 0.08), transparent 55%);
filter:blur(100px);pointer-events:none;animation:bgShift 20s ease-in-out infinite;}
@keyframes bgShift{0%,100%{transform:rotate(0deg) scale(1);}33%{transform:rotate(6deg) scale(1.05);}66%{transform:rotate(-4deg) scale(1.02);}}
[data-theme="light"] .bg-anim { background: linear-gradient(45deg, #e8eef7, #f9e8ff, #e8fff5, #e8eef7); background-size: 400% 400%; }
[data-theme="light"] body::before{background:radial-gradient(circle at 20% 30%, rgba(255, 0, 255, 0.06), transparent 50%),radial-gradient(circle at 80% 70%, rgba(0, 229, 255, 0.06), transparent 50%),radial-gradient(circle at 50% 50%, rgba(255, 138, 0, 0.05), transparent 60%);}
body::after{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;
background-image:radial-gradient(1.5px 1.5px at 15% 25%, rgba(255,209,102,0.6), transparent),radial-gradient(1px 1px at 35% 65%, rgba(0,229,255,0.5), transparent),radial-gradient(1.5px 1.5px at 75% 15%, rgba(255,0,255,0.4), transparent),radial-gradient(1px 1px at 85% 85%, rgba(57,255,20,0.5), transparent),radial-gradient(1px 1px at 50% 50%, rgba(255,138,0,0.4), transparent),radial-gradient(1.5px 1.5px at 25% 85%, rgba(157,78,221,0.4), transparent),radial-gradient(1px 1px at 65% 45%, rgba(255,209,102,0.4), transparent);
background-size:100% 100%;opacity:.6;animation:floatParticles 30s ease-in-out infinite;}
@keyframes floatParticles{0%,100%{transform:translate(0,0);}50%{transform:translate(-5px,-10px);}}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:var(--bg)}
::-webkit-scrollbar-thumb{background:linear-gradient(180deg,var(--line),var(--muted));border-radius:6px;border:2px solid var(--bg)}
::-webkit-scrollbar-thumb:hover{background:var(--muted)}
header{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:16px;padding:12px 22px;background:rgba(10,14,21,.85);backdrop-filter:blur(16px);border-bottom:1px solid var(--line);flex-wrap:wrap;box-shadow:0 8px 32px -16px #000;}
[data-theme="light"] header{background:rgba(255,255,255,0.85);}
.brand{font-weight:800;letter-spacing:2px;font-size:16px;display:flex;align-items:center;gap:9px}
.brand .dot{width:10px;height:10px;border-radius:50%;background:var(--accent);box-shadow:0 0 20px var(--accent),0 0 40px var(--accent);animation:dotPulse 2s ease-in-out infinite;}
@keyframes dotPulse{0%,100%{transform:scale(1);}50%{transform:scale(1.15);}}
.tabs{display:flex;gap:6px;flex-wrap:wrap}
.tab{background:transparent;border:1px solid transparent;color:var(--muted);padding:8px 16px;border-radius:8px;cursor:pointer;font-size:14px;font-weight:600;transition:.2s;font-family:inherit;position:relative;overflow:hidden}
.tab::before{content:'';position:absolute;inset:0;background:radial-gradient(circle at center,rgba(255,138,0,0.2),transparent 70%);opacity:0;transition:opacity 0.3s;}
.tab:hover{color:var(--text);background:var(--panel2)}
.tab:hover::before{opacity:1;}
.tab.active{color:var(--text);background:var(--panel);border-color:var(--line);box-shadow:inset 0 -2px 0 var(--accent),0 4px 20px -10px var(--accent)}
.badge{background:var(--accent);color:#111;border-radius:10px;padding:1px 7px;font-size:11px;font-weight:800;margin-left:4px;box-shadow:0 0 10px -2px var(--accent)}
.badge.upg{background:#ff5c8a;color:#fff;box-shadow:0 0 10px -2px #ff5c8a}
.badge.q{background:#39ff14;color:#0a2a00;box-shadow:0 0 10px -2px #39ff14}
.wallet{margin-left:auto;display:flex;align-items:center;gap:10px;position:relative}
.money{font-size:18px;font-weight:800;color:#ffd166;white-space:nowrap;text-shadow:0 0 20px rgba(255,209,102,.5),0 0 40px rgba(255,209,102,.2)}
.snd{background:transparent;border:1px solid var(--line);color:var(--muted);width:38px;height:38px;border-radius:8px;cursor:pointer;font-size:16px;font-family:inherit;display:flex;align-items:center;justify-content:center;transition:.2s}
.snd:hover{color:var(--text);border-color:var(--muted);transform:scale(1.05)}
/* Стиль для кнопки Доната */
.tab[data-view="donate"] { color: #ff3366; border-color: rgba(255, 51, 102, 0.2); }
.tab[data-view="donate"]:hover { background: rgba(255, 51, 102, 0.1); color: #ff3366; box-shadow: 0 0 15px -5px #ff3366; border-color: #ff3366; }
.tab[data-view="donate"]::before { background: radial-gradient(circle at center, rgba(255, 51, 102, 0.3), transparent 70%); }
.level-badge{display:flex;align-items:center;gap:8px;padding:6px 12px;background:var(--panel2);border-radius:8px;border:1px solid var(--line);}
.level-badge .lvl-num{font-size:12px;font-weight:900;color:var(--accent);white-space:nowrap;text-shadow:0 0 10px var(--accent)}
.level-badge .lvl-progress{width:80px;height:6px;background:var(--bg);border-radius:3px;overflow:hidden;border:1px solid var(--line);position:relative}
.level-badge .lvl-fill{height:100%;background:linear-gradient(90deg,var(--accent),#ffd166);transition:width 0.4s;box-shadow:0 0 10px var(--accent);position:relative;overflow:hidden}
.level-badge .lvl-fill::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(255,255,255,0.4),transparent);animation:shimmerLvl 2s linear infinite;}
@keyframes shimmerLvl{0%{transform:translateX(-100%);}100%{transform:translateX(100%);}}
main{max-width:1280px;margin:0 auto;padding:24px 22px 90px}
.view{display:none;animation:fade .3s ease}
.view.active{display:block}
@keyframes fade{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
.page-title{font-size:22px;margin-bottom:18px;font-weight:800;background:linear-gradient(90deg,#fff,#ffd166);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;}
[data-theme="light"] .page-title{background:linear-gradient(90deg,#111,#e07300);-webkit-background-clip:text;background-clip:text;}
.sub-title{font-size:13px;margin:0 0 12px;color:var(--muted);text-transform:uppercase;letter-spacing:1px;font-weight:700}
.cat-tabs{display:flex;gap:10px;margin-bottom:20px;flex-wrap:wrap;}
.cat-tab{background:var(--panel2);border:1px solid var(--line);color:var(--muted);padding:8px 18px;border-radius:10px;cursor:pointer;font-weight:700;font-size:14px;transition:.25s;font-family:inherit;position:relative;overflow:hidden}
.cat-tab::after{content:'';position:absolute;inset:0;background:linear-gradient(45deg,transparent 40%,rgba(255,255,255,0.08) 50%,transparent 60%);transform:translateX(-100%);transition:transform 0.6s;}
.cat-tab:hover{color:var(--text);border-color:var(--muted);transform:translateY(-2px);}
.cat-tab:hover::after{transform:translateX(100%);}
.cat-tab.active{background:var(--panel);color:var(--text);border-color:var(--accent);box-shadow:0 0 20px -5px var(--accent),inset 0 1px 0 rgba(255,255,255,0.05)}
.cat-tab[data-cat="Киберпанк 77"]{border-color:#fcee0a;color:#fcee0a;}
.cat-tab[data-cat="Киберпанк 77"]:hover{background:rgba(252,238,10,0.08);}
.cat-tab[data-cat="Киберпанк 77"].active{background:#fcee0a;color:#000;box-shadow:0 0 20px -5px #fcee0a;}
.cat-tab[data-cat="Легенды"]{border-color:#9d4edd;color:#9d4edd;}
.cat-tab[data-cat="Легенды"]:hover{background:rgba(157,78,221,0.08);}
.cat-tab[data-cat="Легенды"].active{background:#9d4edd;color:#fff;box-shadow:0 0 25px -3px #9d4edd;}
.case-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:16px}
.case-card{background:linear-gradient(165deg,var(--panel2),var(--panel));border:1px solid var(--line);border-radius:16px;padding:16px;cursor:pointer;transition:.3s cubic-bezier(.2,.8,.3,1);position:relative;overflow:hidden;transform:translateZ(0)}
.case-card::before{content:'';position:absolute;inset:0;pointer-events:none;opacity:.25;background:radial-gradient(340px 180px at 50% 0%,var(--cc),transparent 72%);transition:opacity 0.3s;}
.case-card::after{content:'';position:absolute;left:-60%;top:0;width:40%;height:100%;background:linear-gradient(100deg,transparent,rgba(255,255,255,.12),transparent);transform:skewX(-18deg);transition:.7s;pointer-events:none}
.case-card:hover::after{left:130%}
.case-card:hover{transform:translateY(-8px) scale(1.02);border-color:var(--cc);box-shadow:0 20px 45px -18px var(--cc),0 0 0 1px var(--cc) inset}
.case-card:hover::before{opacity:.5;}
.case-art{height:140px;display:flex;align-items:center;justify-content:center;margin-bottom:10px;position:relative}
.case-art::before{content:'';position:absolute;width:80%;height:80%;background:radial-gradient(circle,var(--cc),transparent 70%);opacity:.12;filter:blur(20px);pointer-events:none;z-index:-1}
.case-name{font-weight:700;font-size:15px;text-align:center}
.case-meta{display:flex;justify-content:space-between;align-items:center;margin-top:12px;font-size:12px;color:var(--muted)}
.case-price{color:#ffd166;font-weight:800;font-size:15px;text-shadow:0 0 10px rgba(255,209,102,.3)}
.case-stat-mini{font-size:10px;color:var(--muted);margin-top:6px;padding-top:6px;border-top:1px dashed var(--line);display:flex;justify-content:space-between}
.case-stat-mini b{color:#ffd166}
.gun{width:100%;height:100%;display:block}
.open-top{display:flex;align-items:center;gap:16px;margin-bottom:6px}
.open-title h2{font-size:20px}
.open-title span{font-size:13px;color:var(--muted)}
.case-stats{display:flex;gap:15px;font-size:12px;color:var(--muted);margin-top:8px;flex-wrap:wrap}
.case-stats b{color:#ffd166}
.case-stats .stat-good{color:#39ff14;font-weight:700}
.roulette{position:relative;height:180px;overflow:hidden;border-radius:14px;user-select:none;background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);margin:18px 0;box-shadow:inset 0 0 80px -20px var(--accent),0 10px 30px -15px #000}
.roulette::before,.roulette::after{content:'';position:absolute;top:0;bottom:0;width:140px;z-index:3;pointer-events:none}
.roulette::before{left:0;background:linear-gradient(90deg,var(--panel),transparent)}
.roulette::after{right:0;background:linear-gradient(270deg,var(--panel),transparent)}
.strip{position:absolute;left:0;top:0;height:100%;display:flex;align-items:center;will-change:transform}
.rl-item{flex:0 0 144px;width:144px;height:150px;margin:0 6px;border-radius:10px;padding:8px;background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--line);border-bottom:3px solid var(--rc);display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden;box-shadow:inset 0 -24px 30px -24px var(--rc),0 2px 8px -4px #000;position:relative}
.rl-item::before{content:'';position:absolute;inset:0;background:radial-gradient(120px 60px at 50% 0%,var(--rc),transparent 70%);opacity:.2;pointer-events:none}
.rl-item .art{height:56px;width:100%;display:flex;align-items:center;justify-content:center;position:relative}
.rl-name{font-size:10px;color:var(--muted);margin-top:8px;width:100%;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;position:relative}
.rl-skin{font-size:11px;font-weight:700;width:100%;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;position:relative}
.roulette-line{position:absolute;left:50%;top:0;bottom:0;width:2px;z-index:4;pointer-events:none;background:linear-gradient(180deg,transparent,#ff8a00,#ff8a00,transparent);box-shadow:0 0 24px #ff8a00,0 0 48px #ff8a00;transform:translateX(-1px)}
.roulette-line::before,.roulette-line::after{content:'';position:absolute;left:50%;transform:translateX(-50%);border:solid transparent;border-width:8px;}
.roulette-line::before{top:0;border-bottom-color:#ff8a00;margin-top:-8px;filter:drop-shadow(0 0 10px #ff8a00);}
.roulette-line::after{bottom:0;border-top-color:#ff8a00;margin-bottom:-8px;filter:drop-shadow(0 0 10px #ff8a00);}
.open-actions{display:flex;gap:10px;flex-wrap:wrap}
.multi-open{display:flex;gap:6px;align-items:center;margin-bottom:12px;flex-wrap:wrap}
.multi-open span{font-size:13px;color:var(--muted);font-weight:700}
.multi-btn{background:var(--panel2);border:1px solid var(--line);color:var(--muted);padding:6px 14px;border-radius:8px;cursor:pointer;font-weight:800;font-size:13px;transition:0.2s;font-family:inherit}
.multi-btn:hover{border-color:var(--muted);color:var(--text);transform:translateY(-1px)}
.multi-btn.active{background:linear-gradient(180deg,var(--accent),#e07300);color:#111;border-color:var(--accent);box-shadow:0 0 12px -2px var(--accent)}
.btn{background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--line);color:var(--text);padding:12px 22px;border-radius:10px;cursor:pointer;font-weight:700;font-size:14px;font-family:inherit;transition:.2s;box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 2px 6px -2px #000}
.btn:hover{background:var(--panel);border-color:var(--muted);transform:translateY(-2px);box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 6px 15px -5px #000}
.btn:active{transform:translateY(0)}
.btn:disabled{opacity:.45;cursor:not-allowed;transform:none}
.btn.primary{background:linear-gradient(180deg,#ff9b1f,#e07300);border-color:#ffb14d;color:#1a1206;box-shadow:0 6px 18px -6px #ff8a00,inset 0 1px 0 rgba(255,255,255,.4)}
.btn.primary:hover{background:linear-gradient(180deg,#ffb04a,#f08000);box-shadow:0 10px 25px -8px #ff8a00,inset 0 1px 0 rgba(255,255,255,.4)}
.btn.ghost{background:transparent;border-color:var(--line);color:var(--muted)}
.btn.ghost:hover{color:var(--text);border-color:var(--muted)}
.result{display:none;margin-top:20px}
.result.show{display:block;animation:pop .4s cubic-bezier(.2,1.4,.5,1)}
@keyframes pop{from{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}
.res-card{position:relative;overflow:hidden;border-radius:16px;padding:22px;text-align:center;background:linear-gradient(165deg,var(--panel2),var(--panel));border:1px solid var(--rc);box-shadow:0 0 50px -14px var(--rc),0 10px 30px -15px #000}
.res-card::before{content:'';position:absolute;inset:0;pointer-events:none;opacity:.3;background:radial-gradient(420px 220px at 50% 0%,var(--rc),transparent 70%)}
.res-card::after{content:'';position:absolute;inset:-50%;background:conic-gradient(from 0deg,transparent,var(--rc),transparent 30%);opacity:.15;animation:spinGlow 8s linear infinite;pointer-events:none;}
@keyframes spinGlow{100%{transform:rotate(360deg);}}
.res-art{height:150px;width:100%;max-width:440px;margin:0 auto;position:relative;filter:drop-shadow(0 0 25px var(--rc));}
.res-rar{font-size:12px;font-weight:800;letter-spacing:1.5px;text-transform:uppercase;color:var(--rc);margin-top:10px;position:relative;text-shadow:0 0 10px var(--rc)}
.res-name{font-size:20px;font-weight:800;margin-top:6px;position:relative}
.res-name span{color:var(--muted);font-weight:600}
.res-price{font-size:26px;font-weight:900;color:#ffd166;margin-top:8px;position:relative;text-shadow:0 0 20px rgba(255,209,102,.4)}
.res-actions{display:flex;gap:10px;justify-content:center;margin-top:16px;position:relative;flex-wrap:wrap}
.multi-results{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px;margin-top:16px}
.multi-res-card{background:var(--panel2);border:1px solid var(--line);border-bottom:3px solid var(--rc);border-radius:10px;padding:10px;text-align:center;box-shadow:inset 0 -20px 26px -22px var(--rc),0 2px 8px -4px #000;transition:transform 0.2s}
.multi-res-card:hover{transform:translateY(-3px);border-color:var(--rc)}
.multi-res-card .art{height:50px;margin-bottom:6px}
.multi-res-card .m{font-size:10px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.multi-res-card .s{font-size:12px;font-weight:800;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.multi-res-card .p{font-size:11px;color:#ffd166;font-weight:700;margin-top:3px}
.contents{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
.mini{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);border-bottom:3px solid var(--rc);border-radius:10px;padding:10px;text-align:center;box-shadow:inset 0 -20px 26px -22px var(--rc),0 2px 8px -4px #000;position:relative;transition:.2s;overflow:hidden}
.mini:hover{transform:translateY(-2px);border-color:var(--rc)}
.mini::before{content:'';position:absolute;inset:0;background:radial-gradient(100px 50px at 50% 0%,var(--rc),transparent 70%);opacity:.15;pointer-events:none}
.mini .art{height:52px;margin-bottom:6px;filter:drop-shadow(0 4px 8px rgba(0,0,0,0.5));position:relative}
.mini .m-model{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;position:relative}
.mini .m-skin{font-size:12px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;position:relative}
.mini .m-price{font-size:11px;color:#ffd166;font-weight:700;margin-top:4px;position:relative}
.inv-top{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;margin-bottom:16px}
.inv-info{font-size:13px;color:var(--muted);display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.inv-info b{color:#ffd166}
.inv-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:12px}
.item-card{background:linear-gradient(165deg,var(--panel2),var(--panel));border:1px solid var(--line);border-radius:12px;padding:12px;position:relative;overflow:hidden;border-bottom:3px solid var(--rc);transition:.25s;box-shadow:0 2px 8px -4px #000}
.item-card:hover{transform:translateY(-4px);box-shadow:0 16px 32px -16px var(--rc),0 0 0 1px var(--rc) inset;border-color:var(--rc)}
.item-card::before{content:'';position:absolute;inset:0;pointer-events:none;opacity:.18;background:radial-gradient(240px 120px at 50% 0%,var(--rc),transparent 70%)}
.item-art{height:68px;margin-bottom:8px;position:relative;filter:drop-shadow(0 6px 14px rgba(0,0,0,0.6))}
.item-model{font-size:11px;color:var(--muted);position:relative}
.item-skin{font-size:14px;font-weight:800;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.item-price{font-size:13px;font-weight:900;color:#ffd166;margin-top:4px;position:relative;text-shadow:0 0 10px rgba(255,209,102,.4)}
.item-rar{font-size:10px;font-weight:800;letter-spacing:1px;text-transform:uppercase;color:var(--rc);margin-top:4px;position:relative;text-shadow:0 0 8px currentColor}
.item-btns{display:flex;gap:4px;margin-top:10px;position:relative}
.sell-btn{background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--line);color:var(--text);padding:8px;border-radius:8px;cursor:pointer;font-weight:700;font-size:11px;font-family:inherit;transition:.2s;position:relative;flex:1;white-space:nowrap}
.sell-btn:hover{background:var(--panel);border-color:var(--muted);transform:translateY(-1px)}
.sell-btn.upg{background:rgba(255,92,138,0.1);border-color:rgba(255,92,138,0.3);color:#ffa6c9}
.sell-btn.upg:hover{background:rgba(255,92,138,0.25);border-color:#ff5c8a;color:#fff;box-shadow:0 0 15px -4px #ff5c8a}
.upg-layout{display:grid;grid-template-columns:300px 1fr;gap:18px;align-items:start}
@media(max-width:900px){.upg-layout{grid-template-columns:1fr}}
.upg-left{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:14px;max-height:78vh;overflow-y:auto;box-shadow:inset 0 0 20px rgba(0,0,0,0.3),0 4px 20px -10px #000}
.upg-inv{display:grid;grid-template-columns:1fr;gap:8px}
.upg-inv-item{background:linear-gradient(90deg,var(--panel2),var(--panel));border:1px solid var(--line);border-left:3px solid var(--rc);border-radius:10px;padding:8px;cursor:pointer;display:flex;align-items:center;gap:10px;transition:.2s}
.upg-inv-item:hover{border-color:var(--rc);transform:translateX(3px);box-shadow:0 0 20px -8px var(--rc)}
.upg-inv-item.selected{border-color:var(--rc);box-shadow:0 0 0 1px var(--rc),0 0 25px -6px var(--rc);background:var(--panel)}
.upg-inv-item .art{width:70px;height:46px;flex-shrink:0;filter:drop-shadow(0 4px 8px rgba(0,0,0,0.5))}
.upg-inv-item .info{flex:1;min-width:0}
.upg-inv-item .m{font-size:10px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.upg-inv-item .s{font-size:12px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.upg-inv-item .p{font-size:11px;color:#ffd166;font-weight:700}
.upg-right{display:flex;flex-direction:column;gap:14px}
.upg-panels{display:grid;grid-template-columns:1fr 260px 1fr;gap:16px;align-items:stretch;padding:20px;background:linear-gradient(165deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:18px;box-shadow:0 10px 40px -10px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,.05);}
@media(max-width:760px){.upg-panels{grid-template-columns:1fr}}
.upg-card{background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--line);border-radius:14px;overflow:hidden;position:relative;display:flex;flex-direction:column;min-height:210px;box-shadow:inset 0 0 30px rgba(0,0,0,0.3);transition:.3s}
.upg-card.selected-card{border-color:var(--rc);box-shadow:0 0 30px -6px var(--rc), inset 0 0 20px rgba(0,0,0,0.3)}
.upg-card::before{content:'';position:absolute;inset:0;pointer-events:none;opacity:.18;background:radial-gradient(260px 160px at 50% 0%,var(--rc,#2a3a52),transparent 72%)}
.upg-card-head{padding:10px 14px;font-size:11px;letter-spacing:1.5px;text-transform:uppercase;color:var(--muted);font-weight:800;border-bottom:1px solid var(--line);background:rgba(0,0,0,0.2);display:flex;justify-content:space-between;align-items:center;position:relative;z-index:1}
.upg-card-head .chip{background:var(--bg);border:1px solid var(--rc,#2a3a52);color:var(--rc,#8496ab);border-radius:6px;padding:2px 8px;font-size:10px;letter-spacing:.5px;box-shadow:0 0 10px -2px var(--rc)}
.upg-card-body{padding:16px;flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;position:relative;z-index:1}
.upg-empty{color:var(--muted);font-size:13px;padding:20px 0;opacity:.7}
.upg-slot-item{width:100%}
.upg-slot-item .art{height:75px;margin-bottom:8px;filter:drop-shadow(0 0 22px var(--rc)) drop-shadow(0 10px 15px rgba(0,0,0,0.7));}
.upg-slot-item .m{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:1px}
.upg-slot-item .s{font-size:14px;font-weight:800;text-shadow:0 2px 4px #000}
[data-theme="light"] .upg-slot-item .s{text-shadow:none;}
.upg-slot-item .p{font-size:18px;font-weight:900;color:#ffd166;margin-top:6px;text-shadow:0 0 12px rgba(255,209,102,0.5)}
.upg-slot-item .ch{font-size:13px;font-weight:900;margin-top:6px;letter-spacing:1px;text-shadow:0 0 10px currentColor}
.upg-wheel-wrap{display:flex;align-items:center;justify-content:center;position:relative}
.upg-wheel{position:relative;width:240px;height:240px;border-radius:50%;box-shadow:0 0 60px -10px rgba(0,0,0,0.5), inset 0 0 20px #000;}
.wheel-ticks{position:absolute;inset:-15px;border-radius:50%;background:repeating-conic-gradient(#4a5c72 0deg 1deg,transparent 1deg 4deg);-webkit-mask:radial-gradient(circle,transparent 0 75%,#000 75.5% 100%);mask:radial-gradient(circle,transparent 0 75%,#000 75.5% 100%);opacity:.9;animation:spinTicks 60s linear infinite;}
@keyframes spinTicks{100%{transform:rotate(360deg)}}
.wheel-outer-glow{position:absolute;inset:-5px;border-radius:50%;box-shadow:0 0 20px 5px rgba(255,138,0,0.15);transition:.3s;pointer-events:none}
.wheel-face{position:absolute;inset:0;border-radius:50%;border:8px solid #0a0e14;box-shadow:0 0 0 2px var(--line),inset 0 0 50px rgba(0,0,0,.9),0 15px 35px -15px #000;background:conic-gradient(var(--win-color,#2a3a52) 0 calc(var(--chance,0)*1%),var(--bg) calc(var(--chance,0)*1%) 100%);transition:background .3s;}
.wheel-inner-ring{position:absolute;inset:15px;border-radius:50%;border:1px solid rgba(255,255,255,0.08);pointer-events:none;}
.wheel-center{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:100px;height:100px;background:radial-gradient(circle at 35% 30%,#1c2836,#070b10);border:2px solid var(--line);border-radius:50%;display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:6;box-shadow:0 0 30px -5px rgba(0,0,0,.9),inset 0 0 20px rgba(0,0,0,.9), 0 0 20px var(--win-color,#2a3a52)}
.wheel-chance{font-size:22px;font-weight:900;line-height:1;text-shadow:0 0 20px currentColor;transition:color .3s}
.wheel-chance-sub{font-size:9px;color:var(--muted);text-transform:uppercase;letter-spacing:2px;margin-top:3px;font-weight:700}
.needle{position:absolute;left:50%;top:50%;width:6px;height:48%;transform-origin:center bottom;transform:translate(-50%,-100%) rotate(0deg);z-index:7;pointer-events:none}
.needle-body{position:absolute;inset:0;border-radius:3px;background:linear-gradient(180deg,#fff 0%,#ffd166 20%,#ff8a00 60%,#8a4a00 100%);box-shadow:0 0 20px rgba(255,150,0,0.9), 0 2px 5px rgba(0,0,0,0.5);}
.needle-head{position:absolute;top:-18px;left:50%;width:0;height:0;transform:translateX(-50%);border-left:12px solid transparent;border-right:12px solid transparent;border-bottom:20px solid #fff;filter:drop-shadow(0 0 12px rgba(255,200,50,1));}
.needle-base{position:absolute;bottom:-6px;left:50%;transform:translateX(-50%);width:16px;height:16px;background:var(--panel);border:2px solid #ff8a00;border-radius:50%;box-shadow:0 0 12px rgba(255,138,0,0.9);}
.upg-targets{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
.upg-target{background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--line);border-bottom:3px solid var(--rc);border-radius:12px;padding:10px;cursor:pointer;transition:.25s;position:relative;overflow:hidden;box-shadow:0 2px 8px -4px #000}
.upg-target::before{content:'';position:absolute;inset:0;pointer-events:none;opacity:.12;background:radial-gradient(180px 90px at 50% 0%,var(--rc),transparent 70%)}
.upg-target:hover{transform:translateY(-5px);border-color:var(--rc);box-shadow:0 15px 30px -15px var(--rc),0 0 0 1px var(--rc) inset}
.upg-target.selected{border-color:var(--rc);box-shadow:0 0 0 2px var(--rc),0 0 30px -6px var(--rc);background:var(--panel)}
.upg-target .art{height:52px;margin-bottom:6px;position:relative;filter:drop-shadow(0 4px 8px rgba(0,0,0,0.6))}
.upg-target .m{font-size:10px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;position:relative}
.upg-target .s{font-size:12px;font-weight:800;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;position:relative}
.upg-target .p{font-size:13px;font-weight:900;color:#ffd166;margin-top:4px;position:relative;text-shadow:0 0 8px rgba(255,209,102,.3)}
.upg-target .ch{font-size:12px;font-weight:800;color:var(--rc);margin-top:2px;position:relative;text-shadow:0 0 10px currentColor}
.upg-target .ml{position:absolute;top:8px;right:8px;font-size:10px;font-weight:900;z-index:2;background:var(--bg);border:1px solid var(--rc);color:var(--rc);border-radius:6px;padding:2px 6px;box-shadow:0 0 10px -3px var(--rc)}
#upgConfirm:not(:disabled){animation:pulseBtn 2s infinite;}
@keyframes pulseBtn{0%{box-shadow:0 0 0 0 rgba(255,138,0,0.5)}70%{box-shadow:0 0 0 12px rgba(255,138,0,0)}100%{box-shadow:0 0 0 0 rgba(255,138,0,0)}}
.upg-result{display:none;margin-top:16px}
.upg-result.show{display:block;animation:pop .4s cubic-bezier(.2,1.4,.5,1)}
.upg-result-card{position:relative;overflow:hidden;border-radius:14px;padding:18px;text-align:center;background:linear-gradient(165deg,var(--panel2),var(--panel));border:1px solid var(--rc);box-shadow:0 0 55px -10px var(--rc),0 10px 30px -15px #000}
.upg-result-title{font-size:24px;font-weight:900;letter-spacing:2px;color:var(--rc);text-transform:uppercase;text-shadow:0 0 24px var(--rc)}
.upg-result-item{height:90px;width:240px;margin:12px auto;filter:drop-shadow(0 0 30px var(--rc))}
.upg-result-name{font-size:16px;font-weight:800}
.upg-result-price{font-size:20px;font-weight:900;margin-top:6px;text-shadow:0 2px 5px #000}
[data-theme="light"] .upg-result-price{text-shadow:none;}
.clicker-layout{display:grid;grid-template-columns:280px 1fr 300px;gap:24px;align-items:start;max-width:1100px;margin:0 auto;}
@media(max-width:1000px){.clicker-layout{grid-template-columns:1fr;gap:30px}}
@media(max-width:1000px){.clicker-layout .clicker-center{order:-1}}
.clicker-stats{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:16px;padding:20px;box-shadow:inset 0 0 20px rgba(0,0,0,0.3),0 4px 20px -10px #000;}
.stat-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;font-size:14px;}
.stat-row:last-child{margin-bottom:0}
.stat-label{color:var(--muted)}
.stat-val{font-weight:800;color:var(--text);font-size:16px;text-shadow:0 0 10px rgba(255,255,255,0.2)}
[data-theme="light"] .stat-val{text-shadow:none;}
.stat-val.gold{color:#ffd166;text-shadow:0 0 18px rgba(255,209,102,0.5)}
.clicker-center{display:flex;flex-direction:column;align-items:center;justify-content:center;padding-top:20px;min-height:600px;}
.clicker-title{font-size:32px;font-weight:900;margin-bottom:8px;text-align:center;background:linear-gradient(90deg,#ff8a00,#ffd166,#ff8a00);background-size:200% auto;-webkit-background-clip:text;-webkit-text-fill-color:transparent;filter:drop-shadow(0 0 20px rgba(255,138,0,0.5));animation:shineText 3s linear infinite;}
@keyframes shineText{0%{background-position:0% center;}100%{background-position:200% center;}}
.clicker-desc{font-size:14px;color:var(--muted);margin-bottom:30px;text-align:center;max-width:400px;}
.clicker-btn{position:relative;width:260px;height:260px;border-radius:50%;background:radial-gradient(circle at 35% 30%, #2a3a52, #080b11 80%);border:6px solid #4a5c72;box-shadow:0 0 70px -10px rgba(255,138,0,0.4),inset 0 0 60px rgba(255,138,0,0.15),inset 0 0 20px #000,0 20px 40px rgba(0,0,0,0.9);cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;transition:transform .08s cubic-bezier(0.1, 0.8, 0.3, 1), box-shadow .15s;user-select:none; -webkit-user-select:none; z-index:10;}
.clicker-btn::before{content:'';position:absolute;inset:-15px;border-radius:50%;background:repeating-conic-gradient(rgba(255,138,0,0.5) 0deg 2deg,transparent 2deg 10deg);-webkit-mask:radial-gradient(circle,transparent 0 75%,#000 76% 100%);mask:radial-gradient(circle,transparent 0 75%,#000 76% 100%);animation:spinTicks 20s linear infinite;pointer-events:none;opacity:0.7;}
.clicker-btn::after{content:'';position:absolute;inset:-2px;border-radius:50%;border:2px solid transparent;background:linear-gradient(45deg,transparent,#ffd166,transparent) border-box;-webkit-mask:linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);-webkit-mask-composite:xor; mask-composite:exclude;opacity:0.9;pointer-events:none;}
.clicker-btn:hover{box-shadow:0 0 90px -5px rgba(255,138,0,0.7),inset 0 0 70px rgba(255,138,0,0.35),inset 0 0 20px #000,0 20px 40px rgba(0,0,0,0.9);border-color:#ff8a00;transform:translateY(-3px) scale(1.02);}
.clicker-btn:active, .clicker-btn.pressed{transform:scale(0.92) translateY(4px);box-shadow:0 0 40px 0 rgba(255,138,0,1),inset 0 0 90px rgba(255,138,0,0.7),inset 0 0 20px #000,0 5px 15px rgba(0,0,0,0.95);border-color:#fff;}
.clicker-btn .core{width:120px;height:120px;background:radial-gradient(circle at 40% 40%, #ffd166, #ff8a00 60%, #7a2e00);border-radius:50%;display:flex;flex-direction:column;align-items:center;justify-content:center;box-shadow:0 0 40px #ff8a00, inset 0 0 25px rgba(0,0,0,0.6);transition:.1s;}
.clicker-btn:active .core, .clicker-btn.pressed .core{box-shadow:0 0 60px #fff, inset 0 0 35px rgba(255,255,255,0.9);transform:scale(1.06);}
.clicker-btn .core svg{width:70px;height:70px;filter:drop-shadow(0 4px 8px rgba(0,0,0,0.6))}
.clicker-btn .click-val{margin-top:20px;font-size:24px;font-weight:900;color:#fff;text-shadow:0 2px 5px #000, 0 0 20px rgba(255,255,255,0.6);background:rgba(0,0,0,0.5);padding:5px 20px;border-radius:20px;border:1px solid rgba(255,255,255,0.15);}
.robot-zone{width:100%;height:240px;margin-top:20px;background:radial-gradient(ellipse at 50% 100%, rgba(255,138,0,0.15) 0%, rgba(0,0,0,0.4) 70%);border-radius:20px;border-bottom:2px solid var(--line);display:flex;justify-content:center;align-items:flex-end;gap:20px;padding-bottom:20px;position:relative;overflow:hidden;box-shadow:inset 0 -30px 50px -20px rgba(0,0,0,0.8);}
.robot-zone::before{content:'';position:absolute;inset:0;background-image:linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px);background-size:30px 30px;opacity:0.15;pointer-events:none;mask-image:radial-gradient(ellipse at 50% 100%, black 20%, transparent 80%);}
.robot-platform{position:absolute;bottom:0;left:50%;transform:translateX(-50%);width:80%;height:30px;background:linear-gradient(180deg,var(--panel2),var(--panel));border-radius:50%;border-top:2px solid var(--accent);box-shadow:0 -10px 30px -5px var(--accent);pointer-events:none;}
.robot{position:relative;animation:float 3s infinite ease-in-out;transition:.3s;filter:drop-shadow(0 10px 15px rgba(0,0,0,0.8));z-index:2;}
.robot:nth-child(even){animation-delay:-1.5s;}
.robot:nth-child(3n){animation-delay:-0.7s;}
@keyframes float{0%{transform:translateY(0);}50%{transform:translateY(-15px);}100%{transform:translateY(0);}}
.robot .laser-beam{position:absolute;bottom:100%;left:50%;width:4px;height:0;background:linear-gradient(180deg, #ffd166, #ff8a00, transparent);box-shadow:0 0 15px #ff8a00, 0 0 30px #ff8a00;transform-origin:bottom;transform:translateX(-50%) rotate(0deg);opacity:0;border-radius:2px;z-index:-1;}
.robot.shooting .laser-beam{animation:shootLaser 0.25s ease-out;}
@keyframes shootLaser{0%{height:0;opacity:1;transform:translateX(-50%) rotate(0deg);}30%{height:250px;opacity:1;transform:translateX(-50%) rotate(0deg);}100%{height:250px;opacity:0;transform:translateX(-50%) rotate(0deg);}}
.robot .eye-glow{transition: all 0.1s;}
.robot.shooting .eye-glow{filter:drop-shadow(0 0 15px #39ff14) drop-shadow(0 0 30px #39ff14);fill:#fff;}
.clicker-shop{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:16px;padding:20px;box-shadow:inset 0 0 20px rgba(0,0,0,0.3),0 4px 20px -10px #000;}
.shop-title{font-size:18px;font-weight:800;margin-bottom:16px;color:var(--text);border-bottom:1px solid var(--line);padding-bottom:10px}
.shop-item{background:linear-gradient(90deg,var(--panel2),var(--panel));border:1px solid var(--line);border-radius:12px;padding:12px;margin-bottom:12px;display:flex;align-items:center;gap:12px;transition:.2s;position:relative;overflow:hidden;}
.shop-item:hover{border-color:#ff8a00;box-shadow:0 0 25px -8px #ff8a00;transform:translateY(-2px)}
.shop-icon{width:40px;height:40px;background:var(--bg);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:20px;box-shadow:inset 0 0 10px rgba(0,0,0,0.3)}
.shop-info{flex:1}
.shop-name{font-size:13px;font-weight:700;color:var(--text)}
.shop-desc{font-size:11px;color:var(--muted);margin-top:2px}
.shop-buy{background:linear-gradient(180deg,#ff8a00,#e07300);border:none;border-radius:6px;color:#111;padding:6px 12px;font-weight:800;font-size:12px;cursor:pointer;transition:.2s;font-family:inherit;box-shadow:0 2px 12px rgba(255,138,0,0.4);white-space:nowrap;}
.shop-buy:hover{background:linear-gradient(180deg,#ffb04a,#f08000);transform:scale(1.05);box-shadow:0 4px 16px rgba(255,138,0,0.6)}
.shop-buy:disabled{background:var(--line);color:var(--muted);box-shadow:none;cursor:not-allowed;transform:none}
.shop-lvl{position:absolute;top:8px;right:8px;font-size:10px;font-weight:800;color:#ffd166;background:rgba(0,0,0,0.6);padding:2px 6px;border-radius:4px}
.floating-text{position:fixed;pointer-events:none;font-weight:900;font-size:32px;color:#39ff14;text-shadow:0 2px 20px rgba(57,255,20,0.9), 0 0 40px rgba(57,255,20,0.5);z-index:999;animation:floatUp 1s cubic-bezier(0.1, 0.8, 0.3, 1) forwards;}
@keyframes floatUp{0%{transform:translate(-50%, -50%) scale(0.8);opacity:1;}100%{transform:translate(-50%, -180%) scale(1.6);opacity:0;}}
.daily-card{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;background:linear-gradient(135deg,var(--panel2),var(--panel));border:1px solid var(--line);border-radius:16px;padding:18px 20px;margin-bottom:22px;position:relative;overflow:hidden;box-shadow:0 4px 20px -10px #000}
.daily-card::before{content:'';position:absolute;inset:0;pointer-events:none;opacity:.2;background:radial-gradient(380px 180px at 0% 0%,#ffd166,transparent 72%)}
.daily-card.claimed{opacity:.65}
.daily-title{font-size:16px;font-weight:800;position:relative}
.daily-desc{font-size:12px;color:var(--muted);margin-top:3px;position:relative}
.quest-list{display:grid;gap:12px;max-width:820px}
.quest-card{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:14px;padding:16px;position:relative;overflow:hidden;transition:.2s;box-shadow:0 4px 20px -10px #000}
.quest-card.done{border-color:#39ff14;box-shadow:0 0 30px -12px #39ff14,0 0 0 1px rgba(57,255,20,0.3) inset}
.quest-card.claimed{opacity:.6}
.quest-top{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}
.quest-title{font-weight:800;font-size:15px}
.quest-desc{font-size:12px;color:var(--muted);margin-top:4px}
.quest-reward{font-weight:900;color:#ffd166;white-space:nowrap;font-size:15px;text-shadow:0 0 10px rgba(255,209,102,.4)}
.qbar{height:10px;background:var(--bg);border-radius:6px;overflow:hidden;margin-top:12px;border:1px solid var(--line);position:relative}
.qbar > i{display:block;height:100%;background:linear-gradient(90deg,#ff8a00,#ffd166);border-radius:6px;transition:width .4s;box-shadow:0 0 10px rgba(255,138,0,0.5);position:relative}
.qbar > i::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(255,255,255,0.3),transparent);animation:shimmerLvl 2s linear infinite;}
.qmeta{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);margin-top:6px}
.quest-claim{margin-top:12px}
.quest-claim .btn{width:100%;padding:10px}
.empty{color:var(--muted);text-align:center;padding:40px 20px;font-size:14px}
#toast{position:fixed;left:50%;bottom:26px;transform:translate(-50%,20px);z-index:100;background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--line);color:var(--text);padding:12px 22px;border-radius:10px;font-size:14px;font-weight:600;opacity:0;pointer-events:none;transition:.3s;box-shadow:0 10px 40px -10px #000;max-width:90vw;text-align:center}
#toast.show{opacity:1;transform:translate(-50%,0)}
@media(max-width:640px){
.roulette{height:150px}
.rl-item{height:124px;flex:0 0 120px;width:120px}
.rl-item .art{height:46px}
.case-art{height:110px}
.upg-wheel{width:200px;height:200px}
.wheel-center{width:80px;height:80px}
.wheel-chance{font-size:18px}
.clicker-btn{width:220px;height:220px}
.clicker-btn .core{width:100px;height:100px}
.clicker-btn .core svg{width:60px;height:60px}
.robot-zone{height:180px;gap:10px;}
.robot svg{width:60px;height:60px;}
header{padding:10px 14px;gap:10px}
.brand{font-size:14px}
.tab{padding:7px 11px;font-size:13px}
.money{font-size:16px}
.level-badge .lvl-progress{width:50px}
}
#live-feed{position:fixed;bottom:20px;left:20px;width:320px;background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:12px;padding:12px;z-index:90;box-shadow:0 8px 30px -10px #000;pointer-events:none;display:flex;flex-direction:column;gap:8px;}
.feed-item{display:flex;align-items:center;gap:10px;font-size:13px;animation:slideIn 0.5s ease-out;background:linear-gradient(90deg,var(--panel2),var(--panel));padding:6px 8px;border-radius:6px;}
@keyframes slideIn{from{opacity:0;transform:translateX(-25px);}to{opacity:1;transform:translateX(0);}}
.feed-item .name{color:var(--muted);font-weight:600;}
.feed-item .item{font-weight:800;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-shadow:0 0 8px currentColor}
.inv-filters{display:flex;gap:10px;margin-bottom:16px;align-items:center;}
.inv-filters select{background:var(--panel);color:var(--text);border:1px solid var(--line);padding:8px 12px;border-radius:8px;font-family:inherit;font-size:13px;cursor:pointer;outline:none;transition:.2s}
.inv-filters select:hover{border-color:var(--muted)}
.inv-filters select:focus{border-color:var(--accent);box-shadow:0 0 0 2px rgba(255,138,0,0.2)}
.profile-card{max-width:800px;margin:0 auto;background:linear-gradient(165deg,var(--panel2),var(--panel));border:1px solid var(--line);border-radius:20px;padding:30px;text-align:center;box-shadow:0 10px 50px -20px #000;}
.profile-avatar{width:100px;height:100px;border-radius:50%;background:linear-gradient(135deg,var(--panel2),var(--panel));border:3px solid var(--line);margin:0 auto 15px;display:flex;align-items:center;justify-content:center;font-size:40px;color:var(--muted);cursor:pointer;transition:.3s;}
.profile-avatar:hover{border-color:var(--accent);color:var(--text);transform:scale(1.05);box-shadow:0 0 25px -5px var(--accent)}
.prof-input{background:var(--bg);border:1px solid var(--line);color:var(--text);font-size:18px;font-weight:800;padding:8px 12px;border-radius:8px;text-align:center;width:100%;max-width:300px;outline:none;transition:.2s;}
.prof-input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(255,138,0,0.15)}
.profile-stats{display:grid;grid-template-columns:1fr 1fr;gap:15px;margin-top:25px;text-align:left;}
.prof-stat-box{background:linear-gradient(135deg,var(--panel2),var(--panel));padding:12px;border-radius:12px;border:1px solid var(--line);}
.prof-stat-label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:1px;}
.prof-stat-val{font-size:18px;font-weight:900;color:#ffd166;margin-top:4px;text-shadow:0 0 10px rgba(255,209,102,.3)}
.prestige-btn{width:100%;margin-top:15px;background:linear-gradient(180deg,#ffd700,#b8860b);color:#000;border:none;padding:15px;border-radius:12px;font-size:16px;font-weight:900;cursor:pointer;transition:.2s;box-shadow:0 4px 20px rgba(255,215,0,0.4);}
.prestige-btn:hover{transform:translateY(-2px);box-shadow:0 8px 25px rgba(255,215,0,0.6);}
.prestige-btn:disabled{background:var(--line);color:var(--muted);cursor:not-allowed;transform:none;box-shadow:none;}
/* ===== КРАШ: ТОРГОВЫЙ ТЕРМИНАЛ ===== */
.crash-layout{display:flex;gap:20px;max-width:1280px;margin:0 auto;align-items:flex-start;}
@media(max-width:1000px){.crash-layout{flex-direction:column;}}
.crash-container{flex:1;background:#0b0e14;border:1px solid var(--line);border-radius:16px;padding:16px;box-shadow:0 10px 50px -20px #000;position:relative;}
.crash-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;flex-wrap:wrap;gap:10px;}
.crash-history{display:flex;gap:6px;flex-wrap:wrap;}
.crash-hist-item{padding:4px 10px;border-radius:6px;font-weight:900;font-size:12px;font-family:monospace;}
.crash-hist-item.win{background:rgba(57,255,20,0.15);color:#39ff14;border:1px solid rgba(57,255,20,0.4);box-shadow:0 0 10px -3px #39ff14}
.crash-hist-item.loss{background:rgba(255,59,59,0.15);color:#ff3b3b;border:1px solid rgba(255,59,59,0.4);box-shadow:0 0 10px -3px #ff3b3b}
.crash-players{width:240px;background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:16px;padding:16px;box-shadow:0 10px 40px -20px #000;}
@media(max-width:1000px){.crash-players{width:100%;}}
.player-row{display:flex;justify-content:space-between;align-items:center;font-size:13px;padding:8px 0;border-bottom:1px solid var(--line);}
.player-row:last-child{border-bottom:none;}
.player-name{color:var(--muted);font-weight:600;}
.player-bet{font-weight:800;color:#ffd166;}
.player-mult{font-weight:800;font-size:12px;}
.player-mult.win{color:#39ff14;}
.player-mult.loss{color:#ff3b3b;}
.crash-canvas-wrap{position:relative;width:100%;height:420px;background:#0d1117;border-radius:8px;overflow:hidden;border:1px solid #1f2937;margin-bottom:16px;}
#crashCanvas{width:100%;height:100%;display:block;}
.crash-multiplier{position:absolute;top:20px;left:20px;font-size:42px;font-weight:900;color:var(--text);text-shadow:0 2px 10px rgba(0,0,0,0.8);pointer-events:none;transition:color 0.2s;font-family:monospace;letter-spacing:1px;z-index:10;}
.crash-multiplier.crashed{color:#ff3b3b;text-shadow:0 0 20px rgba(255,59,59,0.6);}
.crash-pnl{position:absolute;top:70px;left:20px;font-size:18px;font-weight:800;color:var(--muted);pointer-events:none;font-family:monospace;z-index:10;}
.crash-pnl.positive{color:#39ff14;}
.crash-pnl.negative{color:#ff3b3b;}
.crash-controls{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap;background:#111823;padding:12px;border-radius:12px;border:1px solid var(--line);}
.crash-input{background:#0b0e14;border:1px solid var(--line);color:var(--text);padding:10px 14px;border-radius:8px;font-size:15px;font-weight:700;width:140px;outline:none;transition:.2s}
.crash-input:focus{border-color:var(--accent);box-shadow:0 0 0 2px rgba(255,138,0,0.2)}
.crash-btn{padding:10px 24px;border:none;border-radius:8px;font-size:15px;font-weight:900;cursor:pointer;transition:.2s;font-family:inherit;white-space:nowrap;text-transform:uppercase;letter-spacing:0.5px;}
.crash-btn.play{background:linear-gradient(180deg,#39ff14,#2bb510);color:#000;box-shadow:0 0 15px rgba(57,255,20,0.4);}
.crash-btn.play:hover{background:linear-gradient(180deg,#6bff50,#3fd420);transform:translateY(-1px);box-shadow:0 4px 20px rgba(57,255,20,0.6)}
.crash-btn.cashout{background:linear-gradient(180deg,#ffd700,#e5b800);color:#000;box-shadow:0 0 15px rgba(255,215,0,0.4);min-width:180px;}
.crash-btn.cashout:hover{background:linear-gradient(180deg,#ffe84d,#ffcc00);transform:translateY(-1px)}
.crash-btn.cashout.loss-mode{background:linear-gradient(180deg,#ff8a00,#e07300);}
.crash-btn.cashout.loss-mode:hover{background:linear-gradient(180deg,#ffb04a,#f08000)}
.crash-btn:disabled{opacity:0.5;cursor:not-allowed;box-shadow:none;transform:none}
.crash-auto{display:flex;align-items:center;justify-content:center;gap:10px;margin-top:12px;font-size:13px;color:var(--muted);}
.crash-auto input{width:80px;padding:6px 10px;border-radius:6px;border:1px solid var(--line);background:var(--bg);color:var(--text);font-size:13px;font-family:inherit;outline:none;}
.confetti-piece{position:fixed;width:10px;height:10px;background:#f00;opacity:0;pointer-events:none;z-index:9999;animation:confetti-fall 2s ease-out forwards;border-radius:2px}
@keyframes confetti-fall{0%{opacity:1;transform:translateY(0) rotate(0deg);}100%{opacity:0;transform:translateY(100vh) rotate(720deg);}}
.music-controls{display:flex;gap:5px;align-items:center;}
#musicSelect{background:var(--panel);border:1px solid var(--line);color:var(--muted);border-radius:6px;padding:0 10px;height:38px;outline:none;font-family:inherit;font-size:13px;cursor:pointer;transition:.2s}
#musicSelect:hover{color:var(--text);border-color:var(--muted);}
#musicSelect option{background:var(--panel);color:var(--text);}
.settings-modal{position:fixed;inset:0;background:rgba(0,0,0,0.75);z-index:1000;display:none;align-items:center;justify-content:center;backdrop-filter:blur(8px);}
.settings-modal.open{display:flex;}
.settings-content{background:linear-gradient(165deg,var(--panel2),var(--panel));padding:30px;border-radius:20px;width:400px;border:1px solid var(--line);box-shadow:0 20px 60px -10px #000,0 0 40px -10px var(--accent);max-width:90vw;}
.settings-title{font-size:20px;font-weight:900;margin-bottom:20px;text-align:center;color:var(--text);}
.setting-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;color:var(--text);font-weight:600;gap:12px;flex-wrap:wrap;}
.setting-row input[type="range"]{width:150px;accent-color:var(--accent);}
.settings-close{width:100%;padding:12px;border-radius:10px;background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--line);color:var(--text);font-weight:800;font-size:14px;cursor:pointer;transition:.2s;font-family:inherit;}
.settings-close:hover{background:var(--line);transform:translateY(-1px)}
#notif-container{position:fixed;top:80px;right:20px;z-index:200;display:flex;flex-direction:column;gap:10px;max-width:340px;pointer-events:none;}
.notif{background:linear-gradient(135deg,var(--panel2),var(--panel));border:1px solid var(--line);border-left:4px solid var(--accent);border-radius:10px;padding:12px 16px;box-shadow:0 10px 30px -10px #000;animation:notifIn 0.4s ease-out;transition:opacity 0.3s, transform 0.3s;}
.notif-title{font-weight:800;font-size:13px;margin-bottom:3px;color:var(--accent);}
.notif-text{font-size:12px;color:var(--muted);}
.notif-success{border-left-color:#39ff14;}
.notif-success .notif-title{color:#39ff14;}
.notif-error{border-left-color:#ff3b3b;}
.notif-error .notif-title{color:#ff3b3b;}
.notif-achievement{border-left-color:#ffd700;box-shadow:0 0 25px -5px #ffd700;}
.notif-achievement .notif-title{color:#ffd700;}
.notif-level{border-left-color:#00e5ff;box-shadow:0 0 25px -5px #00e5ff;}
.notif-level .notif-title{color:#00e5ff;}
.notif-rare{border-left-color:#eb4b4b;box-shadow:0 0 25px -5px #eb4b4b;}
.notif-rare .notif-title{color:#eb4b4b;}
@keyframes notifIn{from{opacity:0;transform:translateX(100%);}to{opacity:1;transform:translateX(0);}}
.ach-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px;margin-top:15px;}
.ach-card{background:linear-gradient(135deg,var(--panel2),var(--panel));border:1px solid var(--line);border-radius:10px;padding:12px;display:flex;align-items:center;gap:10px;opacity:0.45;transition:0.25s;}
.ach-card.unlocked{opacity:1;border-color:#ffd700;box-shadow:0 0 20px -5px #ffd700;background:var(--panel);}
.ach-icon{font-size:26px;filter:grayscale(1);transition:.3s}
.ach-card.unlocked .ach-icon{filter:none;transform:scale(1.1)}
.ach-info{flex:1;min-width:0;text-align:left;}
.ach-name{font-size:12px;font-weight:800;}
.ach-desc{font-size:10px;color:var(--muted);margin-top:2px;}
.ach-reward{font-size:11px;color:#ffd166;font-weight:800;margin-top:3px;}
.contract-layout{display:grid;grid-template-columns:1fr 320px;gap:20px;max-width:1100px;}
@media(max-width:800px){.contract-layout{grid-template-columns:1fr;}}
.contract-selected{background:linear-gradient(165deg,var(--panel2),var(--panel));border:1px solid var(--line);border-radius:14px;padding:18px;}
.contract-slots{display:grid;grid-template-columns:repeat(5,1fr);gap:8px;margin-bottom:15px;}
.contract-slot{aspect-ratio:1;background:var(--panel2);border:2px dashed var(--line);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:24px;position:relative;overflow:hidden;transition:.2s;cursor:pointer;}
.contract-slot.filled{border-style:solid;border-color:var(--rc);background:var(--panel);box-shadow:0 0 15px -5px var(--rc)}
.contract-slot .art{width:100%;height:100%;padding:4px;filter:drop-shadow(0 2px 4px rgba(0,0,0,0.5));}
.contract-slot .remove{position:absolute;top:2px;right:2px;background:rgba(255,59,59,0.9);border:none;color:#fff;border-radius:50%;width:18px;height:18px;font-size:11px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-weight:900;}
.contract-slot .remove:hover{background:#ff3b3b;transform:scale(1.1)}
.contract-preview{text-align:center;padding:16px;background:var(--panel2);border-radius:10px;margin-bottom:15px;min-height:100px;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:13px;color:var(--muted);}
.contract-preview .preview-rarity{font-weight:900;font-size:15px;margin-top:6px;}
.contract-preview .preview-value{font-size:22px;font-weight:900;color:#ffd166;margin-top:4px;text-shadow:0 0 15px rgba(255,209,102,.4)}
.contract-inv-side{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:14px;padding:18px;max-height:600px;overflow-y:auto;}
.contract-inv-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:8px;margin-top:12px;}
.contract-inv-item{background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--line);border-bottom:2px solid var(--rc);border-radius:8px;padding:6px;cursor:pointer;transition:.2s;text-align:center;position:relative;}
.contract-inv-item:hover{transform:translateY(-3px);border-color:var(--rc);box-shadow:0 8px 15px -6px var(--rc)}
.contract-inv-item.selected{border-color:var(--rc);box-shadow:0 0 0 1px var(--rc),0 0 15px -3px var(--rc);}
.contract-inv-item .art{height:36px;margin-bottom:3px;}
.contract-inv-item .s{font-size:9px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--muted);}
#audio-prompt{position:fixed;bottom:20px;right:20px;background:linear-gradient(135deg,var(--panel2),var(--panel));border:1px solid var(--accent);border-radius:12px;padding:12px 18px;font-size:13px;font-weight:600;color:var(--text);z-index:150;box-shadow:0 8px 30px -10px var(--accent),0 0 0 1px var(--accent) inset;display:none;align-items:center;gap:10px;cursor:pointer;transition:.2s;animation:promptPulse 2s infinite}
#audio-prompt.show{display:flex}
#audio-prompt:hover{transform:scale(1.05);box-shadow:0 8px 40px -8px var(--accent),0 0 0 1px var(--accent) inset}
@keyframes promptPulse{0%,100%{box-shadow:0 8px 30px -10px var(--accent),0 0 0 1px var(--accent) inset;}50%{box-shadow:0 8px 40px -5px var(--accent),0 0 0 2px var(--accent) inset;}}
</style>
</head>
<body>
<div class="bg-anim"></div>
<header>
<div class="brand"><span class="dot"></span>NEON CASES</div>
<nav class="tabs">
<button class="tab active" data-view="cases">Кейсы</button>
<button class="tab" data-view="clicker">Кликер</button>
<button class="tab" data-view="crash">Краш</button>
<button class="tab" data-view="contract">Контракты</button>
<button class="tab" data-view="inv">Инвентарь <span class="badge" id="invBadge">0</span></button>
<button class="tab" data-view="upgrade">Апгрейдер <span class="badge upg" id="upgBadge">0</span></button>
<button class="tab" data-view="quests">Квесты <span class="badge q" id="questBadge">0</span></button>
<button class="tab" data-view="profile">Профиль</button>
<button class="tab" data-view="donate" onclick="window.open('https://www.donationalerts.com/r/artemuxxzz', '_blank')">Донат ❤️</button>
</nav>
<div class="wallet">
<div class="level-badge" id="levelBadge" title="Опыт">
<span class="lvl-num">Ур. 1</span>
<div class="lvl-progress"><div class="lvl-fill" style="width:0%"></div></div>
</div>
<div class="money"><span id="balance">1000</span> ₽</div>
<div class="music-controls">
<select id="musicSelect">
<option value="synthwave">Synthwave</option>
<option value="chiptune">Chiptune</option>
<option value="ambient">Ambient</option>
<option value="cyberpunk">Cyberpunk</option>
<option value="lofi">Lo-Fi</option>
<option value="dark_synth">Dark Synth</option>
<option value="arcade">Retro Arcade</option>
<option value="epic">Epic</option>
<option value="techno">Techno</option>
<option value="trap">Trap</option>
</select>
<button class="snd" id="musicBtn" title="Музыка">🎵</button>
</div>
<button class="snd" id="sndBtn" title="Звук">🔊</button>
<button class="snd" id="settingsBtn" title="Настройки">⚙️</button>
</div>
</header>
<main>
<section class="view active" id="view-cases">
<h1 class="page-title">Выбери кейс</h1>
<div class="cat-tabs" id="catTabs">
<button class="cat-tab active" data-cat="Рассвет">Рассвет</button>
<button class="cat-tab" data-cat="Сумерки">Сумерки</button>
<button class="cat-tab" data-cat="Полночь">Полночь</button>
<button class="cat-tab" data-cat="Киберпанк 77">Киберпанк 77</button>
<button class="cat-tab" data-cat="Легенды">Легенды</button>
</div>
<div class="case-grid" id="caseGrid"></div>
</section>
<section class="view" id="view-clicker">
<div class="clicker-layout">
<div class="clicker-stats">
<div class="shop-title">Статистика</div>
<div class="stat-row"><span class="stat-label">Всего кликов:</span><span class="stat-val" id="statClicks">0</span></div>
<div class="stat-row"><span class="stat-label">Сила клика:</span><span class="stat-val gold" id="statClickPower">10 ₽</span></div>
<div class="stat-row"><span class="stat-label">Автоклик:</span><span class="stat-val" id="statAutoClicks">0 / сек</span></div>
<div class="stat-row"><span class="stat-label">Крит. шанс:</span><span class="stat-val" id="statCrit">0%</span></div>
<div style="margin-top:20px;font-size:12px;color:var(--muted);line-height:1.4;text-align:center;border-top:1px solid var(--line);padding-top:15px">Кликай по реактору, чтобы заработать деньги. Улучшай параметры в магазине справа!</div>
<button class="prestige-btn" id="prestigeBtn" onclick="doPrestige()">ПРЕСТИЖ (Сброс прокачки за бонус)</button>
</div>
<div class="clicker-center">
<h1 class="clicker-title">Кибер-Ферма</h1>
<p class="clicker-desc">Удерживай темп и зарабатывай!</p>
<div class="clicker-btn" id="clickerBtn">
<div class="core">
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<path d="M50 15 L85 35 L85 65 L50 85 L15 65 L15 35 Z" fill="none" stroke="#fff" stroke-width="4" opacity="0.5"/>
<circle cx="50" cy="50" r="20" fill="#fff" opacity="0.8"/>
<path d="M50 30 L50 70 M30 50 L70 50" stroke="#ff8a00" stroke-width="6" stroke-linecap="round"/>
</svg>
</div>
<div class="click-val" id="clickBtnValue">+10 ₽</div>
</div>
<div class="robot-zone" id="robotZone">
<div class="robot-platform"></div>
</div>
</div>
<div class="clicker-shop">
<div class="shop-title">Улучшения</div>
<div class="shop-item" id="shopClick">
<div class="shop-icon">⚡</div>
<div class="shop-info"><div class="shop-name">Мощный клик</div><div class="shop-desc">+5 к силе клика</div></div>
<button class="shop-buy" onclick="buyUpgrade('click')">10 000 ₽</button>
<div class="shop-lvl" id="lvlClick">Ур. 0</div>
</div>
<div class="shop-item" id="shopAuto">
<div class="shop-icon">🤖</div>
<div class="shop-info"><div class="shop-name">Дрон-кликер</div><div class="shop-desc">+2 клика в секунду</div></div>
<button class="shop-buy" onclick="buyUpgrade('auto')">50 000 ₽</button>
<div class="shop-lvl" id="lvlAuto">Ур. 0</div>
</div>
<div class="shop-item" id="shopCrit">
<div class="shop-icon">💥</div>
<div class="shop-info"><div class="shop-name">Крит. модуль</div><div class="shop-desc">+5% шанс крита (x10)</div></div>
<button class="shop-buy" onclick="buyUpgrade('crit')">100 000 ₽</button>
<div class="shop-lvl" id="lvlCrit">Ур. 0</div>
</div>
</div>
</div>
</section>
<section class="view" id="view-crash">
<div class="crash-layout">
<div class="crash-container">
<div class="crash-header">
<div class="sub-title" style="margin:0;color:#8496ab;">📈 Терминал (Candlestick)</div>
<div class="crash-history" id="crashHistory"></div>
</div>
<div class="crash-canvas-wrap">
<canvas id="crashCanvas"></canvas>
<div class="crash-multiplier" id="crashMultiplier">1.00x</div>
<div class="crash-pnl" id="crashPnl">—</div>
</div>
<div class="crash-controls">
<input type="number" class="crash-input" id="crashBet" value="100" min="10">
<button class="crash-btn play" id="crashPlayBtn">Войти в сделку</button>
<button class="crash-btn cashout" id="crashCashoutBtn" style="display:none;">Забрать</button>
</div>
<div class="crash-auto" id="crashAutoBox">
<span>Авто-выход:</span>
<input type="number" id="crashAuto" value="2.00" step="0.1" min="1.05" placeholder="напр. 2.00">
<button class="btn ghost" id="crashAutoBtn" style="padding:4px 10px;font-size:11px">Вкл</button>
</div>
</div>
<div class="crash-players">
<div class="sub-title" style="margin:0 0 10px 0">Трейдеры онлайн</div>
<div id="crashPlayersList"></div>
</div>
</div>
</section>
<section class="view" id="view-contract">
<h1 class="page-title">Контракты</h1>
<p style="color:var(--muted);font-size:14px;margin-bottom:20px;max-width:700px">Обменяй от 3 до 5 предметов на один более ценный. Стоимость результата — <b style="color:#ffd166">~85% от суммы вложенных</b>, но редкость может быть выше.</p>
<div class="contract-layout">
<div class="contract-selected">
<h3 class="sub-title" style="margin-bottom:12px">Выбрано: <span id="contractCount" style="color:#ffd166">0</span> / 5</h3>
<div class="contract-slots" id="contractSlots"></div>
<div class="contract-preview" id="contractPreview">Выберите предметы из инвентаря справа</div>
<button class="btn primary" id="contractBtn" disabled style="width:100%">Создать контракт</button>
</div>
<div class="contract-inv-side">
<h3 class="sub-title" style="margin-bottom:8px">Твой инвентарь</h3>
<div class="contract-inv-grid" id="contractInv"></div>
</div>
</div>
</section>
<section class="view" id="view-open">
<div class="open-top">
<button class="btn ghost" id="backBtn">← Назад</button>
<div class="open-title">
<h2 id="openTitle"></h2>
<span id="openSub"></span>
<div class="case-stats" id="openCaseStats"></div>
</div>
</div>
<div class="multi-open">
<span>Открыть:</span>
<button class="multi-btn active" data-multi="1">×1</button>
<button class="multi-btn" data-multi="2">×2</button>
<button class="multi-btn" data-multi="3">×3</button>
<button class="multi-btn" data-multi="5">×5</button>
</div>
<div class="roulette" id="roulette">
<div class="strip" id="strip"></div>
<div class="roulette-line"></div>
</div>
<div class="open-actions">
<button class="btn primary" id="openBtn">Открыть</button>
<button class="btn" id="fastBtn">⚡ Быстрое открытие</button>
</div>
<div class="result" id="result"></div>
<h3 class="sub-title">Содержимое кейса</h3>
<div class="contents" id="contents"></div>
</section>
<section class="view" id="view-inv">
<div class="inv-top">
<h1 class="page-title" style="margin:0">Инвентарь</h1>
<div class="inv-info">Предметов: <b id="invCount">0</b> · Стоимость: <b id="invValue">0</b> ₽
<button class="btn" id="sellAllBtn">Продать всё</button>
</div>
</div>
<div class="inv-filters">
<select id="sortInv">
<option value="price_desc">По цене (сначала дорогие)</option>
<option value="price_asc">По цене (сначала дешёвые)</option>
<option value="rarity_desc">По редкости (сначала топ)</option>
</select>
<select id="filterInv">
<option value="all">Все редкости</option>
<option value="rare">Исключительно редкие</option>
<option value="covert">Тайные</option>
<option value="classified">Засекреченные</option>
<option value="restricted">Запрещённые</option>
<option value="milspec">Армейские</option>
</select>
</div>
<div class="inv-grid" id="invGrid"></div>
</section>
<section class="view" id="view-upgrade">
<div class="upg-layout">
<div class="upg-left">
<div class="sub-title">Твой инвентарь</div>
<div class="upg-inv" id="upgInv"></div>
</div>
<div class="upg-right">
<div class="upg-panels">
<div class="upg-card" id="betCard">
<div class="upg-card-head"><span>Твой предмет</span><span class="chip" id="betChip">—</span></div>
<div class="upg-card-body" id="upgBetContent"><div class="upg-empty">Выбери скин слева</div></div>
</div>
<div class="upg-wheel-wrap">
<div class="upg-wheel">
<div class="wheel-ticks"></div>
<div class="wheel-outer-glow" id="wheelGlow"></div>
<div class="wheel-face" id="wheelFace"></div>
<div class="wheel-inner-ring"></div>
<div class="wheel-center">
<div class="wheel-chance" id="wheelChance">?</div>
<div class="wheel-chance-sub">шанс</div>
</div>
<div class="needle" id="needle">
<div class="needle-body"></div>
<div class="needle-head"></div>
<div class="needle-base"></div>
</div>
</div>
</div>
<div class="upg-card" id="targetCard">
<div class="upg-card-head"><span>Цель</span><span class="chip" id="targetChip">—</span></div>
<div class="upg-card-body" id="upgTargetContent"><div class="upg-empty">Выбери цель ниже</div></div>
</div>
</div>
<div class="upg-result" id="upgResult"></div>
<div>
<div class="sub-title">Выбери цель · шанс от 1% до 75%</div>
<div class="upg-targets" id="upgTargets"></div>
</div>
<div style="display:flex;gap:10px">
<button class="btn primary" id="upgConfirm" style="flex:1" disabled>Выбери предмет и цель</button>
<button class="btn ghost" id="upgReset" disabled>Сбросить</button>
</div>
</div>
</div>
</section>
<section class="view" id="view-quests">
<div class="inv-top">
<h1 class="page-title" style="margin:0">Ежедневные задания</h1>
<div class="inv-info">Обновление через <b id="questTimer">--:--:--</b></div>
</div>
<div class="daily-card" id="dailyBonus"></div>
<h3 class="sub-title">Задания на сегодня</h3>
<div class="quest-list" id="questList"></div>
</section>
<section class="view" id="view-profile">
<div class="profile-card">
<div class="profile-avatar" id="profAvatar" title="Сменить аватарку (пока в разработке)">👤</div>
<input type="text" class="prof-input" id="profName" value="Игрок" maxlength="15">
<div class="profile-stats">
<div class="prof-stat-box"><div class="prof-stat-label">Уровень</div><div class="prof-stat-val" id="profLevel">1</div></div>
<div class="prof-stat-box"><div class="prof-stat-label">Опыт</div><div class="prof-stat-val" id="profXp">0</div></div>
<div class="prof-stat-box"><div class="prof-stat-label">Открыто кейсов</div><div class="prof-stat-val" id="profOpened">0</div></div>
<div class="prof-stat-box"><div class="prof-stat-label">Всего потрачено</div><div class="prof-stat-val" id="profSpent">0 ₽</div></div>
<div class="prof-stat-box"><div class="prof-stat-label">Апгрейдов сделано</div><div class="prof-stat-val" id="profUpg">0</div></div>
<div class="prof-stat-box"><div class="prof-stat-label">Выиграно в апгрейде</div><div class="prof-stat-val" id="profUpgWins">0</div></div>
</div>
<div style="margin-top:30px;text-align:left">
<h3 class="sub-title">Достижения (<span id="achCount">0</span>/<span id="achTotal">0</span>)</h3>
<div class="ach-grid" id="achGrid"></div>
</div>
</div>
</section>
</main>
<div class="settings-modal" id="settingsModal">
<div class="settings-content">
<div class="settings-title">Настройки</div>
<div class="setting-row"><span>Громкость музыки</span><input type="range" id="volMusic" min="0" max="1" step="0.05" value="0.5"></div>
<div class="setting-row"><span>Громкость звуков</span><input type="range" id="volSfx" min="0" max="1" step="0.05" value="0.5"></div>
<div class="setting-row"><span>Скорость анимаций</span><input type="range" id="animSpeed" min="0.5" max="2" step="0.1" value="1"></div>
<div class="setting-row"><span>Тема</span><button class="btn" id="themeToggle" style="padding:6px 12px;font-size:12px">Тёмная</button></div>
<button class="settings-close" id="settingsClose">Закрыть</button>
</div>
</div>
<div id="audio-prompt">🔊 Кликни для включения звука</div>
<div id="live-feed"></div>
<div id="notif-container"></div>
<div id="toast"></div>
<script>
/* =====================================================================
1. STORAGE
===================================================================== */
const storage = {
get(key){ try { return localStorage.getItem(key); } catch(e){ return null; } },
set(key, val){ try { localStorage.setItem(key, val); } catch(e){} }
};
const SAVE_KEY = 'neon_cases_save_v13';
/* =====================================================================
2. DATA
===================================================================== */
const RARITIES = {
milspec: { name:'Армейское', color:'#4b69ff', price:[5,30] },
restricted: { name:'Запрещённое', color:'#8847ff', price:[25,120] },
classified: { name:'Засекреченное', color:'#d32ce6', price:[100,450] },
covert: { name:'Тайное', color:'#eb4b4b', price:[400,1800] },
rare: { name:'Исключительно редкое', color:'#ffd700', price:[2000,12000]}
};
const RARITY_WEIGHT = { milspec:6950, restricted:2050, classified:600, covert:300, rare:100 };
const TIER_ORDER = ['milspec','restricted','classified','covert','rare'];
const MODELS = {
'AK-47':'rifle', 'M4A4':'rifle', 'M4A1-S':'rifle', 'Galil AR':'rifle', 'FAMAS':'rifle',
'AUG':'rifle', 'SG 553':'rifle', 'M249':'lmg', 'Negev':'lmg',
'AWP':'sniper', 'SSG 08':'sniper', 'SCAR-20':'sniper', 'G3SG1':'sniper',
'Desert Eagle':'pistol', 'USP-S':'pistol', 'Glock-18':'pistol', 'P250':'pistol',
'Five-SeveN':'pistol', 'Tec-9':'pistol', 'CZ75-Auto':'pistol',
'Dual Berettas':'pistol', 'P2000':'pistol', 'R8 Revolver':'revolver',
'MAC-10':'smg', 'MP9':'smg', 'MP7':'smg', 'MP5-SD':'smg', 'UMP-45':'smg',
'P90':'smg', 'PP-Bizon':'smg',
'Nova':'shotgun', 'XM1014':'shotgun', 'Sawed-Off':'shotgun', 'MAG-7':'shotgun',
'Керамбит':'knife', 'Бабочка':'knife', 'Штык-нож':'knife', 'Талон':'knife',
'Скелет':'knife', 'Кунай':'knife', 'Кукри':'knife', 'Боуи':'knife', 'Охотничий':'knife',
'Штык-нож М9':'knife', 'Гвоздь':'knife', 'Урсус':'knife', 'Стилетто':'knife'
};
const PALETTES = [
['#ff7b00','#ffd000','#2a1600'], ['#00e5ff','#0066ff','#001a33'],
['#ff2d55','#ff9ec4','#2a0010'], ['#39ff14','#0a6b00','#001a00'],
['#ffffff','#c0c0c0','#3a3a3a'], ['#8a2be2','#00ffff','#12002b'],
['#ffd700','#8b6914','#3d2b00'], ['#111111','#ff0000','#8b0000'],
['#00ffa3','#005544','#001a14'], ['#ff00ff','#00ffff','#1a001a'],
['#ffb3c6','#ffffff','#8a5a6a'], ['#2b2b2b','#6b6b6b','#ff4500'],
['#00bfff','#ffffff','#004466'], ['#8b4513','#d2b48c','#2b1a0a'],
['#1a1a2e','#16213e','#e94560'], ['#adff2f','#006400','#1a1a1a'],
['#e0e0e0','#9e9e9e','#ff3b3b'], ['#4b0082','#ff1493','#000000'],
['#00ced1','#20b2aa','#003333'], ['#f5f5dc','#daa520','#4a3000'],
['#7b68ee','#c39bff','#1a0a3a'], ['#ff1493','#ffd700','#2a001a'],
['#2f4f4f','#708090','#0a1218'], ['#ff4500','#ffd700','#3a0a00'],
['#9400d3','#ffd700','#1a0033'], ['#ff0000','#ffffff','#330000']
];
const PATTERNS = ['solid','stripes','dots','grid','camo','waves','hex','solid'];
const SHAPES = {
rifle: {
vb:'0 0 320 130', shadow:{cx:160,cy:126,rx:135,ry:4},
parts:['M6 58 L20 50 L24 74 L8 80 Z','M18 50 L30 46 V72 L18 76 Z','M30 44 H140 V80 H30 Z','M30 40 H140 V46 H30 Z','M140 48 H240 V76 H140 Z','M140 40 H240 V48 H140 Z','M240 44 H280 V72 H240 Z','M280 46 H300 V70 H280 Z','M170 76 L160 118 L192 118 L202 76 Z','M140 76 L132 118 L160 118 L170 76 Z','M130 76 H150 V80 H130 Z','M180 30 H210 V40 H180 Z','M185 26 H205 V30 H185 Z','M90 30 H130 V38 H90 Z','M2 54 L8 50 V80 L2 78 Z','M158 114 H194 V118 H158 Z'],
highlights:['M32 46 H138 V50 H32 Z','M142 42 H238 V46 H142 Z','M242 46 H278 V50 H242 Z','M12 52 L22 50 V54 L12 56 Z','M30 66 H140 V70 H30 Z','M188 26 H202 V30 H188 Z'],
details:['M44 52 H56 V58 H44 Z','M60 52 H72 V58 H60 Z','M76 52 H88 V58 H76 Z','M92 52 H104 V58 H92 Z','M108 52 H120 V58 H108 Z','M44 62 H56 V68 H44 Z','M60 62 H72 V68 H60 Z','M76 62 H88 V68 H76 Z','M92 62 H104 V68 H92 Z','M108 62 H120 V68 H108 Z','M150 52 H190 V58 H150 Z','M150 62 H190 V68 H150 Z','M162 82 H196 V86 H162 Z','M162 92 H196 V96 H162 Z','M162 102 H196 V106 H162 Z','M138 80 L134 116','M144 80 L140 116','M150 80 L146 116','M156 80 L152 116','M240 46 H280 V48 H240 Z','M280 48 H300 V50 H280 Z','M145 42 H238 V44 H145 Z'],
lines:['M130 82 Q136 90 148 88','M220 48 L220 76'],
circles:[{cx:150,cy:68,r:2.2},{cx:160,cy:68,r:2.2},{cx:170,cy:68,r:2.2},{cx:28,cy:60,r:1.5},{cx:225,cy:60,r:1.8},{cx:290,cy:58,r:1.8}]
},
sniper: {
vb:'0 0 340 130', shadow:{cx:170,cy:126,rx:145,ry:4},
parts:['M4 56 L16 48 L20 72 L8 80 Z','M2 52 L4 50 V78 L2 76 Z','M16 46 H28 V74 H16 Z','M28 44 H150 V72 H28 Z','M28 38 H150 V44 H28 Z','M150 46 H250 V74 H150 Z','M150 40 H250 V46 H150 Z','M250 44 H295 V76 H250 Z','M295 46 H315 V74 H295 Z','M180 74 L172 106 L200 106 L208 74 Z','M150 74 L144 116 L172 116 L180 74 Z','M140 74 H162 V78 H140 Z','M155 20 H230 V36 H155 Z','M162 34 H172 V42 H162 Z','M212 34 H222 V42 H212 Z','M228 22 H236 V34 H228 Z','M150 22 H158 V34 H150 Z','M110 72 L108 96 H114 L116 72 Z','M240 40 H252 V46 H240 Z','M165 16 H220 V20 H165 Z'],
highlights:['M30 40 H148 V44 H30 Z','M152 42 H248 V46 H152 Z','M252 46 H293 V50 H252 Z','M10 50 L18 48 V52 L10 54 Z','M158 22 H228 V28 H158 Z'],
details:['M158 26 H228 V32 H158 Z','M170 26 V32','M180 26 V32','M190 26 V32','M200 26 V32','M210 26 V32','M220 26 V32','M48 50 H60 V56 H48 Z','M64 50 H76 V56 H64 Z','M80 50 H92 V56 H80 Z','M96 50 H108 V56 H96 Z','M48 60 H60 V66 H48 Z','M64 60 H76 V66 H64 Z','M80 60 H92 V66 H80 Z','M96 60 H108 V66 H96 Z','M200 50 H240 V62 H200 Z','M240 46 H252 V50 H240 Z','M178 80 H204 V86 H178 Z','M178 92 H204 V98 H178 Z','M108 88 H116 V92 H108 Z','M258 52 H288 V56 H258 Z','M258 60 H288 V64 H258 Z','M258 68 H288 V72 H258 Z'],
lines:['M140 82 Q148 92 162 90','M200 20 V16','M210 20 V16'],
circles:[{cx:230,cy:28,r:2.5},{cx:156,cy:28,r:2.5},{cx:100,cy:46,r:1.5},{cx:275,cy:60,r:1.8},{cx:305,cy:60,r:1.8}]
},
pistol: {
vb:'0 0 220 130', shadow:{cx:110,cy:126,rx:95,ry:4},
parts:['M20 36 H160 V60 H20 Z','M20 30 H160 V36 H20 Z','M160 38 H186 V58 H160 Z','M168 30 H174 V38 H168 Z','M24 26 H40 V32 H24 Z','M20 60 H100 V70 H20 Z','M50 70 L44 92 Q44 100 58 100 L82 100 L90 70 Z','M58 70 L52 122 L96 122 L104 70 Z','M70 78 H94 V116 H70 Z','M52 122 H96 V128 H52 Z','M16 40 H28 V58 H16 Z','M30 26 H38 V32 H30 Z'],
highlights:['M22 32 H158 V36 H22 Z','M162 40 H184 V44 H162 Z','M22 40 H158 V44 H22 Z','M72 80 H92 V114 H72 Z'],
details:['M144 40 V58','M148 40 V58','M152 40 V58','M156 40 V58','M40 40 V58','M44 40 V58','M48 40 V58','M52 40 V58','M72 84 H92 V88 H72 Z','M72 92 H92 V96 H72 Z','M72 100 H92 V104 H72 Z','M54 88 H62 V92 H54 Z','M168 26 H174 V30 H168 Z','M18 44 H26 V54 H18 Z'],
lines:['M58 96 Q70 90 82 90'],
circles:[{cx:170,cy:32,r:2},{cx:32,cy:29,r:1.5},{cx:32,cy:60,r:1.8}]
},
smg: {
vb:'0 0 280 130', shadow:{cx:140,cy:126,rx:120,ry:4},
parts:['M4 52 L14 46 L18 70 L6 76 Z','M14 46 H24 V70 H14 Z','M24 44 H180 V72 H24 Z','M24 40 H180 V46 H24 Z','M180 46 H240 V70 H180 Z','M240 48 H260 V68 H240 Z','M120 72 H160 V122 H120 Z','M80 72 L74 118 L100 118 L108 72 Z','M68 72 H88 V76 H68 Z','M40 30 H130 V38 H40 Z','M130 26 H138 V32 H130 Z','M42 26 H50 V30 H42 Z','M180 70 H220 V76 H180 Z'],
highlights:['M26 46 H178 V50 H26 Z','M182 48 H238 V52 H182 Z','M126 76 H156 V82 H126 Z','M14 48 H22 V52 H14 Z','M42 32 H128 V36 H42 Z'],
details:['M42 56 H60 V62 H42 Z','M64 56 H82 V62 H64 Z','M86 56 H104 V62 H86 Z','M108 56 H126 V62 H108 Z','M140 46 H172 V68 H140 Z','M124 82 H156 V86 H124 Z','M124 92 H156 V96 H124 Z','M124 102 H156 V106 H124 Z','M82 80 L78 114','M88 80 L84 114','M94 80 L90 114','M100 80 L96 114','M188 52 H232 V56 H188 Z','M188 60 H232 V64 H188 Z','M180 66 H220 V70 H180 Z'],
lines:[], circles:[{cx:152,cy:58,r:2},{cx:162,cy:58,r:2},{cx:246,cy:58,r:1.8},{cx:32,cy:58,r:1.5}]
},
shotgun: {
vb:'0 0 320 130', shadow:{cx:160,cy:126,rx:135,ry:4},
parts:['M4 50 L16 44 L20 68 L8 76 Z','M10 42 H16 V46 H10 Z','M16 44 H120 V68 H16 Z','M16 68 H260 V76 H16 Z','M120 42 H240 V80 H120 Z','M120 38 H240 V44 H120 Z','M240 42 H295 V80 H240 Z','M295 46 H315 V76 H295 Z','M160 80 L154 120 L180 120 L186 80 Z','M148 80 H170 V84 H148 Z','M40 34 H100 V42 H40 Z'],
highlights:['M18 46 H118 V50 H18 Z','M122 40 H238 V44 H122 Z','M242 44 H293 V48 H242 Z','M12 46 H16 V50 H12 Z','M42 36 H98 V40 H42 Z'],
details:['M40 52 H60 V58 H40 Z','M64 52 H84 V58 H64 Z','M88 52 H108 V58 H88 Z','M130 50 H230 V54 H130 Z','M250 50 H288 V54 H250 Z','M250 58 H288 V62 H250 Z','M250 66 H288 V70 H250 Z','M44 38 H56 V42 H44 Z','M60 38 H72 V42 H60 Z','M76 38 H88 V42 H76 Z','M24 72 H252 V74 H24 Z'],
lines:['M148 88 Q156 96 168 94'],
circles:[{cx:13,cy:44,r:2.2},{cx:280,cy:60,r:1.8},{cx:300,cy:60,r:1.5}]
},
knife: {
vb:'0 0 300 130', shadow:{cx:150,cy:126,rx:120,ry:4},
parts:['M28 50 H72 V88 H28 Z','M28 44 H72 V50 H28 Z','M28 88 H72 V94 H28 Z','M72 42 H90 V96 H72 Z','M90 66 Q130 38 190 30 Q250 24 280 40 L280 54 Q250 44 190 50 Q140 58 100 74 Z','M90 66 Q130 38 190 30 Q250 24 280 40 L278 42 Q250 26 190 32 Q130 40 92 68 Z','M90 66 L100 74 L92 76 Z','M22 46 H30 V92 H22 Z','M32 96 Q30 106 44 108 Q58 110 60 100 Q62 94 56 92'],
highlights:['M90 66 Q130 42 190 36 Q250 30 278 44 L276 46 Q250 32 190 38 Q130 44 92 68 Z','M34 52 H66 V56 H34 Z'],
details:['M36 56 V84','M44 56 V84','M52 56 V84','M60 56 V84','M34 60 H66 V62 H34 Z','M100 70 Q140 50 200 42','M92 70 Q130 46 190 38'],
lines:['M96 68 Q140 44 200 36 Q240 32 276 44'],
circles:[{cx:48,cy:70,r:3},{cx:48,cy:82,r:3},{cx:150,cy:60,r:1.5},{cx:220,cy:50,r:1.5}]
},
revolver: {
vb:'0 0 220 130', shadow:{cx:110,cy:126,rx:95,ry:4},
parts:['M20 44 H140 V56 H20 Z','M20 38 H140 V44 H20 Z','M124 28 H132 V38 H124 Z','M22 26 H34 V32 H22 Z','M20 56 H100 V76 H20 Z','M60 54 H100 V82 H60 Z','M60 78 L52 124 L96 124 L104 78 Z','M72 88 H94 V120 H72 Z','M44 78 L38 96 Q38 104 52 104 L72 104 L80 78 Z','M20 26 H32 V38 H20 Z','M22 20 H28 V26 H22 Z','M24 60 H32 V70 H24 Z'],
highlights:['M22 40 H138 V44 H22 Z','M62 58 H98 V62 H62 Z','M74 92 H92 V118 H74 Z'],
details:['M26 62 H34 V68 H26 Z','M42 62 H50 V68 H42 Z','M58 62 H66 V68 H58 Z','M74 62 H82 V68 H74 Z','M90 62 H98 V68 H90 Z','M26 70 H34 V76 H26 Z','M42 70 H50 V76 H42 Z','M58 70 H66 V76 H58 Z','M74 70 H82 V76 H74 Z','M90 70 H98 V76 H90 Z','M66 56 V80','M74 56 V80','M82 56 V80','M90 56 V80','M74 96 H92 V100 H74 Z','M74 106 H92 V110 H74 Z','M50 92 H58 V96 H50 Z'],
lines:['M56 100 Q68 94 80 94'],
circles:[{cx:128,cy:32,r:2},{cx:28,cy:24,r:1.8},{cx:110,cy:50,r:1.5}]
},
lmg: {
vb:'0 0 340 130', shadow:{cx:170,cy:126,rx:150,ry:4},
parts:['M4 54 L18 46 L22 74 L8 82 Z','M2 50 L4 48 V80 L2 78 Z','M18 44 H160 V76 H18 Z','M18 38 H160 V44 H18 Z','M160 44 H260 V80 H160 Z','M160 38 H260 V44 H160 Z','M260 44 H310 V80 H260 Z','M310 48 H330 V76 H310 Z','M100 76 H160 V120 H100 Z','M180 80 L174 124 L200 124 L206 80 Z','M170 80 H192 V84 H170 Z','M200 26 H240 V38 H200 Z','M210 32 H230 V38 H210 Z','M140 76 L136 116 H144 L148 76 Z','M18 30 H28 V44 H18 Z','M54 26 H64 V38 H54 Z'],
highlights:['M20 40 H158 V44 H20 Z','M162 40 H258 V44 H162 Z','M262 46 H308 V50 H262 Z','M12 52 H20 V56 H12 Z','M202 28 H238 V32 H202 Z'],
details:['M32 50 H52 V56 H32 Z','M56 50 H76 V56 H56 Z','M80 50 H100 V56 H80 Z','M104 50 H124 V56 H104 Z','M128 50 H148 V56 H128 Z','M32 62 H52 V68 H32 Z','M56 62 H76 V68 H56 Z','M80 62 H100 V68 H80 Z','M104 62 H124 V68 H104 Z','M128 62 H148 V68 H128 Z','M106 86 H154 V90 H106 Z','M106 96 H154 V100 H106 Z','M106 106 H154 V110 H106 Z','M270 50 H302 V54 H270 Z','M270 62 H302 V66 H270 Z','M270 72 H302 V76 H270 Z','M220 46 H250 V72 H220 Z','M214 28 H226 V32 H214 Z','M136 110 H146 V114 H136 Z'],
lines:['M180 88 Q188 100 202 98'],
circles:[{cx:230,cy:32,r:2.2},{cx:22,cy:36,r:1.8},{cx:320,cy:60,r:1.5}]
}
};
const CASE_DATA = [
{ name:'Утренний кофе', price:50, mult:1, color:'#4b69ff', icon:'crate', category:'Рассвет', defs:['M4A4|Городской камуфляж|milspec','Glock-18|Песчаная буря|milspec','MP9|Ледяная крошка|milspec','Nova|Ржавый лом|milspec','P250|Сухой закон|milspec','FAMAS|Серый патруль|milspec','USP-S|Стальной жук|restricted','MAC-10|Токсичный паук|restricted','AK-47|Красный дракон|classified','AWP|Ледяной удар|covert','Керамбит|Градиент|rare']},
{ name:'Роса', price:100, mult:2, color:'#00bfff', icon:'capsule', category:'Рассвет', defs:['MP7|Арктика|milspec','Five-SeveN|Иней|milspec','XM1014|Мороз|milspec','UMP-45|Северный ветер|milspec','SG 553|Снежная буря|milspec','M4A1-S|Синий лёд|restricted','P90|Айсберг|restricted','AWP|Полярное сияние|classified','Desert Eagle|Кристалл|classified','M4A4|Ледяной дракон|covert','Бабочка|Морозный узор|rare']},
{ name:'Рассветный бриз', price:150, mult:3, color:'#ffaa00', icon:'pod', category:'Рассвет', defs:['MP5-SD|Утренний туман|milspec','Glock-18|Рассвет|milspec','Nova|Заря|milspec','P2000|Первый луч|milspec','USP-S|Золотой час|restricted','AK-47|Солнечный удар|restricted','M4A4|Горизонт|classified','AWP|Восход|covert','Штык-нож|Утренняя звезда|rare']},
{ name:'Первый снег', price:75, mult:1.5, color:'#a0d8ef', icon:'capsule', category:'Рассвет', defs:['SSG 08|Снежинка|milspec','P2000|Иней утра|milspec','XM1014|Первый снег|milspec','Nova|Белый пух|milspec','MP7|Северное сияние|restricted','Glock-18|Снегирь|restricted','M4A1-S|Ледяная роза|classified','AWP|Снежная королева|covert','Скелет|Ледяной узор|rare']},
{ name:'Дождь', price:120, mult:2.5, color:'#4a90e2', icon:'pod', category:'Рассвет', defs:['MP9|Капля|milspec','P250|Морось|milspec','UMP-45|Ливень|milspec','MAG-7|Гроза близко|milspec','Tec-9|Лужа|restricted','Galil AR|Шторм|restricted','FAMAS|Молния|classified','AWP|Гроза|covert','Кунай|Дождевой клинок|rare']},
{ name:'Мшистый лес', price:180, mult:3, color:'#2d5016', icon:'crate', category:'Рассвет', defs:['Nova|Мох|milspec','Sawed-Off|Плющ|milspec','MP5-SD|Лесной дух|milspec','P250|Кора|milspec','MP7|Ядовитый мох|restricted','UMP-45|Дуб|restricted','AK-47|Лесной охотник|classified','AWP|Хищник леса|covert','Боуи|Лесной клинок|rare']},
{ name:'Закат', price:220, mult:3.5, color:'#ff6b35', icon:'orb', category:'Рассвет', defs:['Glock-18|Апельсин|milspec','P250|Мандарин|milspec','SSG 08|Огненный шар|milspec','XM1014|Тёплый ветер|milspec','Five-SeveN|Оранжевый закат|restricted','MAC-10|Пылающее небо|restricted','M4A4|Закат над морем|classified','AWP|Багровый закат|covert','Талон|Солнечный градиент|rare']},
{ name:'Туман', price:260, mult:4, color:'#c0c0c0', icon:'barrel', category:'Рассвет', defs:['MP5-SD|Туман|milspec','FAMAS|Дымка|milspec','P2000|Пелена|milspec','MP9|Пары|milspec','USP-S|Туманный призрак|restricted','AK-47|Серая зона|restricted','Desert Eagle|Ночной туман|classified','AWP|Мгла|covert','Скелет|Призрачный клинок|rare']},
{ name:'Неоновый квартал', price:300, mult:4, color:'#ff00ff', icon:'briefcase', category:'Сумерки', defs:['MP9|Неоновая вспышка|milspec','Glock-18|Кибер-код|milspec','MAC-10|Розовый шум|milspec','P250|Синтвейв|milspec','USP-S|Неоновый призрак|restricted','M4A1-S|Синтвейв|restricted','AK-47|Неоновый разлом|classified','AWP|Электрический шторм|classified','M4A4|Гипердвигатель|covert','Талон|Неоновый градиент|rare']},
{ name:'Токсичный прилив', price:500, mult:6, color:'#39ff14', icon:'barrel', category:'Сумерки', defs:['Sawed-Off|Токсик|milspec','Negev|Химикат|milspec','MP7|Ядовитый плющ|milspec','Tec-9|Кислота|milspec','MAC-10|Мутаген|restricted','Galil AR|Биохазард|restricted','AK-47|Изумрудный яд|classified','XM1014|Кислотный дождь|classified','AWP|Токсичный дракон|covert','Скелет|Изумруд|rare']},
{ name:'Городские легенды', price:750, mult:8, color:'#888888', icon:'crate', category:'Сумерки', defs:['Galil AR|Бетон|milspec','FAMAS|Асфальт|milspec','MP5-SD|Тоннель|milspec','UMP-45|Переулок|milspec','Dual Berettas|Стрит-арт|restricted','M4A1-S|Граффити|restricted','AK-47|Неон-сити|classified','AWP|Асфальтовый хищник|covert','Боуи|Городской камуфляж|rare']},
{ name:'Золотая лихорадка', price:1000, mult:10, color:'#ffd700', icon:'chest', category:'Сумерки', defs:['Glock-18|Позолота|milspec','P250|Песок|milspec','Five-SeveN|Латунь|milspec','Desert Eagle|Золотой орёл|restricted','AK-47|Позолоченный|restricted','AWP|Золотой фараон|classified','M4A4|Пирамида|classified','USP-S|Король золота|covert','Штык-нож|Золото|rare']},
{ name:'Красный квартал', price:350, mult:5, color:'#c1121f', icon:'briefcase', category:'Сумерки', defs:['MP7|Асфальт|milspec','P250|Красный кирпич|milspec','UMP-45|Неоновая вывеска|milspec','Tec-9|Бар|milspec','MAC-10|Пьяный мастер|restricted','Glock-18|Красный фонарь|restricted','AK-47|Квартальный|classified','AWP|Кровавый квартал|covert','Кукри|Уличный клинок|rare']},
{ name:'Электрик', price:450, mult:5.5, color:'#ffd60a', icon:'pod', category:'Сумерки', defs:['Dual Berettas|Разряд|milspec','MAG-7|Вольт|milspec','SSG 08|Искра|milspec','MP9|Провод|milspec','USP-S|Электрик|restricted','P90|Электросеть|restricted','M4A4|Молния Зевса|classified','AWP|Гром|covert','Керамбит|Электрический|rare']},
{ name:'Тёмная вода', price:600, mult:7, color:'#003554', icon:'orb', category:'Сумерки', defs:['P2000|Брызги|milspec','Nova|Водоворот|milspec','P250|Глубина|milspec','MP7|Океан|milspec','Glock-18|Морской волк|restricted','MP5-SD|Тёмная вода|restricted','AK-47|Глубокое море|classified','AWP|Морской дьявол|covert','Штык-нож|Морской клинок|rare']},
{ name:'Зеркало', price:850, mult:9, color:'#d4d4d4', icon:'crystal', category:'Сумерки', defs:['Five-SeveN|Стекло|milspec','MP9|Отражение|milspec','UMP-45|Хром|milspec','Tec-9|Полировка|milspec','USP-S|Зеркало|restricted','Galil AR|Стальное зеркало|restricted','AUG|Кристальная гладь|classified','Desert Eagle|Зеркальный|covert','Бабочка|Хромированный|rare']},
{ name:'Вулкан', price:1200, mult:12, color:'#d00000', icon:'chest', category:'Сумерки', defs:['P250|Пепел|milspec','Sawed-Off|Лава|milspec','Negev|Расплавленный|milspec','MP5-SD|Пыль|milspec','MAC-10|Магма|restricted','FAMAS|Вулканический|restricted','AK-47|Извержение|classified','AWP|Вулкан|covert','Керамбит|Магма|rare']},
{ name:'Кибер-набор', price:1500, mult:15, color:'#00e5ff', icon:'pod', category:'Полночь', defs:['P90|Киберспорт|milspec','SG 553|Матрица|milspec','M4A1-S|Цифровой камуфляж|restricted','AUG|Нейросеть|restricted','AK-47|Кибер-дракон|classified','AWP|Асимметрия|classified','M4A4|Квантовый разлом|covert','Бабочка|Пиксельный градиент|rare']},
{ name:'Драконья сокровищница', price:2000, mult:22, color:'#eb4b4b', icon:'chest', variant:'dragon', category:'Полночь', defs:['MAC-10|Чешуя|milspec','Nova|Драконье дыхание|milspec','USP-S|Драконья кровь|restricted','Galil AR|Чешуйчатый|restricted','AWP|Драконье пламя|classified','Desert Eagle|Коготь дракона|classified','AK-47|Огнедышащий|covert','Керамбит|Драконий коготь|rare']},
{ name:'Легенды пустоши', price:3000, mult:35, color:'#ff8a00', icon:'crate', variant:'rust', category:'Полночь', defs:['M249|Ржавчина|milspec','Negev|Пустошь|milspec','M4A4|Пустынный шторм|restricted','SG 553|Ржавый металл|restricted','AWP|Пустошь|classified','AK-47|Выжженная земля|classified','M4A1-S|Ядерный рассвет|covert','Скелет|Пустынный градиент|rare']},
{ name:'Космический рейд', price:4500, mult:55, color:'#7b68ee', icon:'orb', category:'Полночь', defs:['P2000|Туманность|milspec','Dual Berettas|Космос|milspec','USP-S|Галактика|restricted','AK-47|Космический дракон|restricted','AWP|Сверхновая|classified','Desert Eagle|Планета|classified','M4A4|Квазар|covert','Кунай|Космический градиент|rare']},
{ name:'Королевская битва', price:6000, mult:85, color:'#ffd700', icon:'vault', category:'Полночь', defs:['P250|Корона|milspec','MP9|Рыцарь|milspec','USP-S|Монарх|restricted','AK-47|Император|restricted','AWP|Королевский орёл|classified','Desert Eagle|Скипетр|classified','M4A1-S|Корона империи|covert','Кукри|Королевский коготь|rare']},
{ name:'Замёрзший город', price:1800, mult:18, color:'#00b4d8', icon:'barrel', category:'Полночь', defs:['P2000|Снегопад|milspec','XM1014|Гололёд|milspec','UMP-45|Холодный ветер|milspec','MP9|Снег|milspec','USP-S|Ледяной город|restricted','M4A1-S|Иней|restricted','AK-47|Замёрзший|classified','AWP|Ледяной дракон|covert','Скелет|Замёрзший клинок|rare']},
{ name:'Амазонка', price:2500, mult:28, color:'#007200', icon:'crate', category:'Полночь', defs:['MP7|Джунгли|milspec','Nova|Лиана|milspec','Galil AR|Тропический|milspec','FAMAS|Ядовитая лягушка|milspec','MAC-10|Змея|restricted','M4A1-S|Анаконда|restricted','AK-47|Хищник Амазонки|classified','AWP|Кайман|covert','Кукри|Амазонский|rare']},
{ name:'Гроза', price:3500, mult:45, color:'#8338ec', icon:'pod', category:'Полночь', defs:['SSG 08|Молния|milspec','Tec-9|Вспышка|milspec','MP9|Гром|milspec','P250|Ливень|milspec','USP-S|Грозовой|restricted','AK-47|Шторм|restricted','M4A4|Буря|classified','AWP|Циклон|covert','Талон|Грозовой клинок|rare']},
{ name:'Механик', price:5000, mult:70, color:'#6c757d', icon:'vault', category:'Полночь', defs:['P2000|Шестерёнка|milspec','Glock-18|Болт|milspec','MP7|Штифт|milspec','Dual Berettas|Шестерни|milspec','Five-SeveN|Ключ|restricted','M4A1-S|Механизм|restricted','AK-47|Механик|classified','Desert Eagle|Инженер|covert','Керамбит|Механический|rare']},
{ name:'Кровавая луна', price:10000, mult:130, color:'#c1121f', icon:'coffin', category:'Киберпанк 77', defs:['MP7|Тёмная сторона|milspec','P250|Полнолуние|milspec','USP-S|Кровавая луна|restricted','AK-47|Вампир|restricted','AWP|Кровавая жатва|classified','Desert Eagle|Ночь охоты|classified','M4A1-S|Кровавый ритуал|covert','Керамбит|Кровавая луна|rare']},
{ name:'Легенда феникса', price:15000, mult:200, color:'#ff4500', icon:'crystal', category:'Киберпанк 77', defs:['Glock-18|Уголёк|milspec','P250|Искра|milspec','AK-47|Пламенный дракон|restricted','M4A1-S|Феникс|restricted','AWP|Огненный феникс|classified','Desert Eagle|Пламя феникса|classified','M4A4|Огненное возрождение|covert','Бабочка|Огненный градиент|rare']},
{ name:'Киберпанк 2077', price:20000, mult:300, color:'#fcee0a', icon:'pod', variant:'neon', category:'Киберпанк 77', defs:['P2000|Неон-стрит|milspec','MP7|Чип|milspec','USP-S|Миллениум|restricted','AK-47|Неоновый рейв|restricted','AWP|Оверклок|classified','Desert Eagle|Кибер-психоз|classified','M4A4|Легенда 2077|covert','Штык-нож М9|Неоновый клинок|rare']},
{ name:'Затерянный храм', price:25000, mult:400, color:'#00ff9d', icon:'chest', variant:'ancient', category:'Киберпанк 77', defs:['P250|Прах|milspec','Galil AR|Джунгли|milspec','M4A4|Храм|restricted','AK-47|Золотой идол|restricted','AWP|Гнев богов|classified','Desert Eagle|Изумрудный глаз|classified','M4A1-S|Затерянный город|covert','Кукри|Жертвенный клинок|rare']},
{ name:'Синтвейв Овердрайв', price:35000, mult:600, color:'#ff00ff', icon:'orb', category:'Киберпанк 77', defs:['Glock-18|Ретро-вейв|milspec','MP5-SD|Хром|milspec','USP-S|Неон-найт|restricted','AK-47|Синтвейв|restricted','AWP|Лазерный закат|classified','Desert Eagle|Пурпурный дождь|classified','M4A4|Овердрайв|covert','Талон|Синтвейв|rare']},
{ name:'Ядро Матрицы', price:50000, mult:1000, color:'#00ff00', icon:'pod', category:'Киберпанк 77', defs:['P2000|Зелёный код|milspec','Tec-9|Матрица|milspec','M4A1-S|Нео|restricted','AK-47|Избранный|restricted','AWP|Красная таблетка|classified','Desert Eagle|Агент Смит|classified','M4A4|Пробуждение|covert','Керамбит|Зелёный код|rare']},
{ name:'Неоновый дождь', price:12000, mult:150, color:'#f72585', icon:'capsule', category:'Киберпанк 77', defs:['P250|Неон|milspec','MP9|Дождь|milspec','UMP-45|Мокрый асфальт|milspec','Glock-18|Розовая лужа|milspec','USP-S|Неоновый дождь|restricted','M4A1-S|Цифровой дождь|restricted','AK-47|Розовый шторм|classified','AWP|Неоновый ливень|covert','Кунай|Неоновый|rare']},
{ name:'Смертельный вирус', price:18000, mult:260, color:'#06d6a0', icon:'pod', category:'Киберпанк 77', defs:['MP7|Код|milspec','Sawed-Off|Байт|milspec','Tec-9|Вирус|milspec','FAMAS|Антивирус|milspec','MAC-10|Червь|restricted','M4A1-S|Троян|restricted','AK-47|Смертельный код|classified','Desert Eagle|Инфекция|covert','Бабочка|Вирусный|rare']},
{ name:'Кибер-самурай', price:28000, mult:450, color:'#e63946', icon:'coffin', category:'Киберпанк 77', defs:['Glock-18|Катана|milspec','Five-SeveN|Сёгун|milspec','P2000|Самурай|milspec','MP9|Танто|milspec','USP-S|Ронин|restricted','M4A1-S|Кибер-самурай|restricted','AK-47|Честь самурая|classified','AWP|Сёгун|covert','Керамбит|Кибер-катана|rare']},
{ name:'Хром и сталь', price:40000, mult:700, color:'#adb5bd', icon:'vault', category:'Киберпанк 77', defs:['P250|Сталь|milspec','Nova|Хром|milspec','MP5-SD|Железо|milspec','UMP-45|Сплав|milspec','MAC-10|Титан|restricted','AK-47|Стальной|restricted','AWP|Хромированный|classified','Desert Eagle|Стальной волк|covert','Скелет|Стальной клинок|rare']},
{ name:'Корона мира', price:75000, mult:1400, color:'#ffd700', icon:'chest', variant:'dragon', category:'Легенды', defs:['P250|Регалии|milspec','Glock-18|Скипетр|milspec','MP7|Держава|milspec','FAMAS|Трон|milspec','USP-S|Власть|restricted','M4A1-S|Империя|restricted','AK-47|Повелитель|classified','AWP|Правитель|covert','Бабочка|Венценосная|rare']},
{ name:'Наследие богов', price:150000, mult:3000, color:'#9d4edd', icon:'crystal', category:'Легенды', defs:['P2000|Миф|milspec','MP9|Легенда|milspec','UMP-45|Древний|milspec','Nova|Олимп|milspec','MAC-10|Зевс|restricted','M4A1-S|Афина|restricted','AK-47|Громовержец|classified','AWP|Один|covert','Кукри|Божественный|rare']}
];
/* =====================================================================
3. HELPERS
===================================================================== */
let uidCounter = 0;
function hash(str){ let h = 2166136261; for(let i=0;i<str.length;i++){ h ^= str.charCodeAt(i); h = Math.imul(h,16777619); } return h >>> 0; }
function fmt(n){ return Math.round(n).toLocaleString('ru-RU'); }
function shadeColor(hex, delta){ const num = parseInt(hex.replace('#',''), 16); let r = (num >> 16) + delta, g = ((num >> 8) & 0xFF) + delta, b = (num & 0xFF) + delta; r = Math.max(0, Math.min(255, r)); g = Math.max(0, Math.min(255, g)); b = Math.max(0, Math.min(255, b)); return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); }
function patternDef(pattern, uid, c1, c2, c3){
const id = 'pt' + uid;
switch(pattern){
case 'stripes': return '<pattern id="'+id+'" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="rotate(40)"><rect width="20" height="20" fill="'+c1+'"/><rect width="10" height="20" fill="'+c2+'"/></pattern>';
case 'dots': return '<pattern id="'+id+'" width="16" height="16" patternUnits="userSpaceOnUse"><rect width="16" height="16" fill="'+c1+'"/><circle cx="8" cy="8" r="3.5" fill="'+c2+'"/></pattern>';
case 'grid': return '<pattern id="'+id+'" width="18" height="18" patternUnits="userSpaceOnUse"><rect width="18" height="18" fill="'+c1+'"/><path d="M0 0H18V18" fill="none" stroke="'+c2+'" stroke-width="2.5"/></pattern>';
case 'camo': return '<pattern id="'+id+'" width="44" height="44" patternUnits="userSpaceOnUse"><rect width="44" height="44" fill="'+c1+'"/><circle cx="11" cy="13" r="9" fill="'+c2+'"/><circle cx="33" cy="31" r="11" fill="'+c3+'" opacity="0.7"/><circle cx="35" cy="9" r="6" fill="'+c2+'"/></pattern>';
case 'waves': return '<pattern id="'+id+'" width="26" height="26" patternUnits="userSpaceOnUse"><rect width="26" height="26" fill="'+c1+'"/><path d="M0 9 Q7 2 13 9 T26 9" fill="none" stroke="'+c2+'" stroke-width="3.5"/><path d="M0 20 Q7 13 13 20 T26 20" fill="none" stroke="'+c2+'" stroke-width="3.5"/></pattern>';
case 'hex': return '<pattern id="'+id+'" width="22" height="26" patternUnits="userSpaceOnUse"><rect width="22" height="26" fill="'+c1+'"/><path d="M11 3 L19 8 L19 18 L11 23 L3 18 L3 8 Z" fill="'+c2+'" opacity="0.85"/></pattern>';
default: return '';
}
}
function gunSVG(item){
const s = SHAPES[item.type] || SHAPES.rifle;
const c1 = item.pal[0], c2 = item.pal[1], c3 = item.pal[2];
const uid = 'x' + (++uidCounter);
const dark = shadeColor(c3, -60);
const darker = shadeColor(c3, -100);
const light = shadeColor(c1, 50);
const highlight = shadeColor(c1, 90);
let defs = '<linearGradient id="lg'+uid+'" x1="0.1" y1="0" x2="0.7" y2="1">'
+ '<stop offset="0" stop-color="'+light+'"/>'
+ '<stop offset="0.25" stop-color="'+c2+'"/>'
+ '<stop offset="0.5" stop-color="'+c1+'"/>'
+ '<stop offset="0.75" stop-color="'+c2+'"/>'
+ '<stop offset="1" stop-color="'+shadeColor(c1,-45)+'"/></linearGradient>';
defs += '<linearGradient id="hi'+uid+'" x1="0" y1="0" x2="0" y2="1">'
+ '<stop offset="0" stop-color="'+highlight+'" stop-opacity="0.9"/>'
+ '<stop offset="1" stop-color="'+highlight+'" stop-opacity="0"/></linearGradient>';
defs += '<filter id="f'+uid+'" x="-20%" y="-20%" width="140%" height="140%">'
+ '<feDropShadow dx="0" dy="3" stdDeviation="2" flood-color="'+darker+'" flood-opacity="0.9"/></filter>';
defs += '<filter id="glow'+uid+'">'
+ '<feGaussianBlur stdDeviation="1.5" result="coloredBlur"/>'
+ '<feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge></filter>';
if(item.pattern !== 'solid') defs += patternDef(item.pattern, uid, c1, c2, c3);
let body = '';
if(s.shadow) body += '<ellipse cx="'+s.shadow.cx+'" cy="'+s.shadow.cy+'" rx="'+s.shadow.rx+'" ry="'+s.shadow.ry+'" fill="#000" opacity="0.7" filter="url(#glow'+uid+')"/>';
for(const d of s.parts) body += '<path d="'+d+'" fill="url(#lg'+uid+')" stroke="'+dark+'" stroke-width="1.4" stroke-linejoin="round" filter="url(#f'+uid+')"/>';
if(item.pattern !== 'solid'){ for(const d of s.parts) body += '<path d="'+d+'" fill="url(#pt'+uid+')" opacity="0.5"/>'; }
for(const d of (s.highlights||[])) body += '<path d="'+d+'" fill="url(#hi'+uid+')" opacity="0.6"/>';
for(const d of (s.details||[])) body += '<path d="'+d+'" fill="'+dark+'" opacity="0.85"/>';
for(const d of (s.lines||[])) body += '<path d="'+d+'" fill="none" stroke="'+darker+'" stroke-width="2.5" stroke-linecap="round" opacity="0.95"/>';
for(const c of (s.circles||[])) body += '<circle cx="'+c.cx+'" cy="'+c.cy+'" r="'+c.r+'" fill="'+darker+'" opacity="0.9"/>';
return '<svg class="gun" viewBox="'+s.vb+'" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">'
+ '<defs>'+defs+'</defs>'+body+'</svg>';
}
function caseSVG(c){
const color = c.color;
const l = shadeColor(color, 70);
const l2 = shadeColor(color, 120);
const d = shadeColor(color, -80);
const d2 = shadeColor(color, -110);
const d3 = shadeColor(color, -130);
const uid = 'cs' + (++uidCounter);
const defs = '<defs>'
+ '<linearGradient id="b'+uid+'" x1="0" y1="0" x2="0.3" y2="1"><stop offset="0" stop-color="'+l+'"/><stop offset="0.35" stop-color="'+color+'"/><stop offset="0.7" stop-color="'+d+'"/><stop offset="1" stop-color="'+d2+'"/></linearGradient>'
+ '<linearGradient id="t'+uid+'" x1="0" y1="0" x2="0.3" y2="1"><stop offset="0" stop-color="'+l2+'"/><stop offset="0.5" stop-color="'+l+'"/><stop offset="1" stop-color="'+color+'"/></linearGradient>'
+ '<linearGradient id="m'+uid+'" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#b8c4d4"/><stop offset="0.4" stop-color="#6b7a8d"/><stop offset="0.6" stop-color="#3a4658"/><stop offset="1" stop-color="#1a2028"/></linearGradient>'
+ '<linearGradient id="m2'+uid+'" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#8a95a5"/><stop offset="0.5" stop-color="#4a566a"/><stop offset="1" stop-color="#252b35"/></linearGradient>'
+ '<radialGradient id="g'+uid+'" cx="50%" cy="45%" r="55%"><stop offset="0" stop-color="'+color+'" stop-opacity="0.75"/><stop offset="0.6" stop-color="'+color+'" stop-opacity="0.25"/><stop offset="1" stop-color="'+color+'" stop-opacity="0"/></radialGradient>'
+ '<radialGradient id="hi'+uid+'" cx="32%" cy="18%" r="70%"><stop offset="0" stop-color="#fff" stop-opacity="0.5"/><stop offset="0.5" stop-color="#fff" stop-opacity="0.1"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></radialGradient>'
+ '<radialGradient id="spec'+uid+'" cx="50%" cy="50%" r="50%"><stop offset="0" stop-color="'+l2+'" stop-opacity="1"/><stop offset="1" stop-color="'+color+'" stop-opacity="0"/></radialGradient>'
+ '<filter id="caseGlow'+uid+'"><feGaussianBlur stdDeviation="3" result="coloredBlur"/><feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge></filter>'
+ '</defs>';
const glow = '<ellipse cx="100" cy="90" rx="100" ry="72" fill="url(#g'+uid+')" filter="url(#caseGlow'+uid+')"/><ellipse cx="100" cy="142" rx="70" ry="5" fill="#000" opacity="0.7"/>';
const B = 'url(#b'+uid+')', T = 'url(#t'+uid+')', M = 'url(#m'+uid+')', M2 = 'url(#m2'+uid+')', HI = 'url(#hi'+uid+')', SPEC = 'url(#spec'+uid+')';
let s = '';
switch(c.icon){
case 'crate':
s += '<rect x="30" y="60" width="140" height="76" rx="5" fill="'+B+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<rect x="30" y="60" width="140" height="6" fill="#000" opacity="0.35"/>';
s += '<rect x="30" y="130" width="140" height="6" fill="#000" opacity="0.4"/>';
s += '<rect x="46" y="68" width="32" height="62" rx="2" fill="#000" opacity="0.22"/>';
s += '<rect x="122" y="68" width="32" height="62" rx="2" fill="#000" opacity="0.22"/>';
s += '<rect x="94" y="68" width="12" height="62" fill="#000" opacity="0.28"/>';
s += '<rect x="56" y="62" width="6" height="72" fill="'+M2+'"/>';
s += '<rect x="138" y="62" width="6" height="72" fill="'+M2+'"/>';
s += '<path d="M24 40 L38 32 H162 L176 40 V62 H24 Z" fill="'+T+'" stroke="#05080d" stroke-width="2.5" stroke-linejoin="round"/>';
s += '<path d="M28 42 L40 36 H160 L172 42 V46 L160 42 H40 Z" fill="#fff" opacity="0.28"/>';
s += '<path d="M38 32 L48 28 H152 L162 32 L152 36 H48 Z" fill="'+l2+'" opacity="0.6"/>';
s += '<rect x="78" y="58" width="44" height="30" rx="4" fill="#0e1116" stroke="#05080d" stroke-width="2"/>';
s += '<rect x="81" y="61" width="38" height="24" rx="3" fill="'+M2+'"/>';
s += '<circle cx="100" cy="70" r="5" fill="#05080d" stroke="'+color+'" stroke-width="1.5"/>';
s += '<path d="M97 70 H103 L102 80 H98 Z" fill="#05080d"/>';
s += '<rect x="86" y="22" width="28" height="12" rx="6" fill="'+M2+'" stroke="#05080d" stroke-width="1.5"/>';
s += '<rect x="90" y="24" width="20" height="3" rx="1.5" fill="#fff" opacity="0.4"/>';
s += '<path d="M30 60 V78 H42 V68 H37 V60 Z" fill="'+M2+'"/>';
s += '<path d="M170 60 V78 H158 V68 H163 V60 Z" fill="'+M2+'"/>';
s += '<path d="M30 136 V118 H42 V128 H37 V136 Z" fill="'+M2+'"/>';
s += '<path d="M170 136 V118 H158 V128 H163 V136 Z" fill="'+M2+'"/>';
s += '<circle cx="37" cy="72" r="2" fill="#05080d"/><circle cx="163" cy="72" r="2" fill="#05080d"/>';
s += '<circle cx="37" cy="124" r="2" fill="#05080d"/><circle cx="163" cy="124" r="2" fill="#05080d"/>';
s += '<rect x="34" y="132" width="132" height="2" fill="'+color+'" opacity="0.9"/>';
if(c.variant === 'rust'){ s += '<path d="M50 80 Q60 76 70 82 Q80 78 90 84" fill="none" stroke="#3a2010" stroke-width="2" opacity="0.6"/>'; }
s += '<rect x="30" y="60" width="140" height="76" rx="5" fill="'+HI+'"/>';
break;
case 'capsule':
s += '<rect x="62" y="36" width="76" height="100" rx="38" fill="'+B+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<rect x="62" y="70" width="76" height="32" fill="#000" opacity="0.3"/>';
s += '<rect x="56" y="62" width="88" height="12" rx="6" fill="'+M2+'" stroke="#05080d" stroke-width="2"/>';
s += '<rect x="56" y="96" width="88" height="12" rx="6" fill="'+M2+'" stroke="#05080d" stroke-width="2"/>';
s += '<circle cx="100" cy="86" r="17" fill="#05080d" opacity="0.95"/>';
s += '<circle cx="100" cy="86" r="14" fill="'+color+'" filter="url(#caseGlow'+uid+')"/>';
s += '<circle cx="100" cy="86" r="14" fill="'+SPEC+'"/>';
s += '<circle cx="100" cy="86" r="7" fill="#fff" opacity="0.5"/>';
s += '<rect x="86" y="20" width="28" height="16" rx="6" fill="'+M2+'" stroke="#05080d" stroke-width="2"/>';
s += '<rect x="72" y="134" width="14" height="8" rx="3" fill="'+M2+'" stroke="#05080d" stroke-width="1.5"/>';
s += '<rect x="114" y="134" width="14" height="8" rx="3" fill="'+M2+'" stroke="#05080d" stroke-width="1.5"/>';
s += '<rect x="72" y="44" width="8" height="34" rx="4" fill="#fff" opacity="0.25"/>';
break;
case 'chest':
s += '<path d="M30 78 H170 V134 H30 Z" fill="'+B+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<rect x="30" y="110" width="140" height="24" fill="#000" opacity="0.2"/>';
s += '<path d="M30 78 Q30 30 100 30 Q170 30 170 78 Z" fill="'+T+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<path d="M44 76 Q44 42 100 42 Q156 42 156 76" fill="none" stroke="#fff" opacity="0.25" stroke-width="4"/>';
s += '<rect x="56" y="30" width="12" height="104" fill="'+M2+'"/>';
s += '<rect x="132" y="30" width="12" height="104" fill="'+M2+'"/>';
s += '<rect x="82" y="68" width="36" height="34" rx="5" fill="#0e1116" stroke="#05080d" stroke-width="2"/>';
s += '<rect x="86" y="72" width="28" height="26" rx="3" fill="'+M2+'"/>';
s += '<circle cx="100" cy="82" r="5" fill="#05080d" stroke="'+color+'" stroke-width="1.5"/>';
s += '<path d="M97 84 H103 L102 92 H98 Z" fill="#05080d"/>';
if(c.variant === 'dragon'){ s += '<path d="M60 40 L50 20 L72 32 Z" fill="'+M2+'" stroke="#05080d" stroke-width="1.5"/><circle cx="100" cy="56" r="7" fill="'+color+'" filter="url(#caseGlow'+uid+')"/>'; }
s += '<rect x="34" y="128" width="132" height="3" fill="'+color+'" opacity="0.8"/>';
s += '<path d="M30 78 H170 V134 H30 Z" fill="'+HI+'" opacity="0.5"/>';
break;
case 'pod':
s += '<rect x="30" y="56" width="140" height="62" rx="31" fill="'+B+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<rect x="30" y="84" width="140" height="12" fill="#000" opacity="0.3"/>';
s += '<circle cx="72" cy="87" r="18" fill="#05080d" opacity="0.95"/>';
s += '<circle cx="72" cy="87" r="14" fill="'+color+'" filter="url(#caseGlow'+uid+')"/>';
s += '<circle cx="72" cy="87" r="14" fill="'+SPEC+'"/>';
s += '<circle cx="72" cy="87" r="6" fill="#fff" opacity="0.5"/>';
s += '<rect x="100" y="64" width="54" height="6" rx="3" fill="'+M2+'"/>';
s += '<rect x="100" y="76" width="42" height="6" rx="3" fill="'+M2+'"/>';
s += '<rect x="100" y="88" width="54" height="6" rx="3" fill="'+M2+'"/>';
s += '<rect x="100" y="100" width="42" height="6" rx="3" fill="'+M2+'"/>';
s += '<rect x="40" y="72" width="8" height="30" rx="4" fill="'+color+'" opacity="0.9" filter="url(#caseGlow'+uid+')"/>';
s += '<rect x="152" y="72" width="8" height="30" rx="4" fill="'+color+'" opacity="0.9" filter="url(#caseGlow'+uid+')"/>';
s += '<path d="M170 62 L190 50 V84 L170 72 Z" fill="'+M2+'" stroke="#05080d" stroke-width="1.5"/>';
s += '<rect x="44" y="62" width="8" height="22" rx="4" fill="#fff" opacity="0.3"/>';
s += '<rect x="30" y="56" width="140" height="62" rx="31" fill="'+HI+'"/>';
break;
case 'briefcase':
s += '<path d="M82 54 V44 Q82 34 100 34 Q118 34 118 44 V54" fill="none" stroke="'+M2+'" stroke-width="9" stroke-linecap="round"/>';
s += '<rect x="28" y="52" width="144" height="84" rx="10" fill="'+B+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<rect x="28" y="92" width="144" height="3" fill="#05080d" stroke="#05080d" stroke-width="0.5"/>';
s += '<rect x="32" y="56" width="136" height="2.5" fill="'+color+'" opacity="0.9"/>';
s += '<rect x="32" y="128" width="136" height="2.5" fill="'+color+'" opacity="0.9"/>';
s += '<rect x="48" y="82" width="26" height="14" rx="3" fill="'+M2+'" stroke="#05080d" stroke-width="1.5"/>';
s += '<rect x="126" y="82" width="26" height="14" rx="3" fill="'+M2+'" stroke="#05080d" stroke-width="1.5"/>';
s += '<circle cx="100" cy="93" r="9" fill="#0e1116" stroke="'+color+'" stroke-width="2.5"/>';
s += '<circle cx="100" cy="93" r="4" fill="'+color+'" filter="url(#caseGlow'+uid+')"/>';
s += '<rect x="28" y="52" width="144" height="84" rx="10" fill="'+HI+'"/>';
break;
case 'barrel':
s += '<ellipse cx="100" cy="126" rx="48" ry="14" fill="'+d3+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<rect x="52" y="42" width="96" height="84" fill="'+B+'"/>';
s += '<ellipse cx="100" cy="42" rx="48" ry="14" fill="'+T+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<path d="M52 42 V126 M148 42 V126" stroke="#05080d" stroke-width="2.5" fill="none"/>';
s += '<ellipse cx="100" cy="60" rx="48" ry="12" fill="none" stroke="'+M+'" stroke-width="7"/>';
s += '<ellipse cx="100" cy="108" rx="48" ry="12" fill="none" stroke="'+M+'" stroke-width="7"/>';
s += '<rect x="56" y="72" width="88" height="22" rx="3" fill="'+color+'"/>';
s += '<circle cx="100" cy="83" r="7" fill="#fff" opacity="0.9" stroke="#05080d" stroke-width="1"/>';
s += '<rect x="56" y="44" width="10" height="80" fill="#fff" opacity="0.15"/>';
break;
case 'vault':
s += '<rect x="24" y="52" width="152" height="84" rx="8" fill="'+d+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<rect x="24" y="52" width="152" height="8" rx="4" fill="#000" opacity="0.4"/>';
s += '<circle cx="100" cy="92" r="44" fill="'+B+'" stroke="#05080d" stroke-width="3"/>';
s += '<circle cx="100" cy="92" r="36" fill="none" stroke="'+M+'" stroke-width="6"/>';
s += '<circle cx="100" cy="92" r="24" fill="'+T+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<circle cx="100" cy="92" r="9" fill="'+M2+'" stroke="#05080d" stroke-width="2"/>';
s += '<circle cx="100" cy="92" r="4" fill="'+color+'" filter="url(#caseGlow'+uid+')"/>';
s += '<path d="M62 92 A38 38 0 0 1 138 92" fill="none" stroke="'+color+'" stroke-width="3.5" opacity="0.9"/>';
s += '<circle cx="80" cy="70" r="12" fill="#fff" opacity="0.15"/>';
break;
case 'coffin':
s += '<path d="M100 14 L148 46 L136 136 H64 L52 46 Z" fill="'+B+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<path d="M100 28 L136 52 L126 122 H74 L64 52 Z" fill="'+T+'"/>';
s += '<circle cx="100" cy="78" r="14" fill="'+color+'" filter="url(#caseGlow'+uid+')"/>';
s += '<circle cx="100" cy="78" r="14" fill="'+SPEC+'"/>';
s += '<circle cx="100" cy="78" r="6" fill="#fff" opacity="0.5"/>';
s += '<path d="M100 14 L148 46 L136 136 H64 L52 46 Z" fill="'+HI+'"/>';
s += '<rect x="76" y="130" width="48" height="4" fill="'+color+'" opacity="0.85"/>';
break;
case 'orb':
s += '<path d="M74 126 H126 L120 138 H80 Z" fill="'+M2+'" stroke="#05080d" stroke-width="2"/>';
s += '<circle cx="100" cy="76" r="42" fill="'+B+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<circle cx="100" cy="76" r="42" fill="'+HI+'"/>';
s += '<circle cx="100" cy="76" r="30" fill="'+color+'" opacity="0.4" filter="url(#caseGlow'+uid+')"/>';
s += '<circle cx="100" cy="76" r="20" fill="'+color+'" opacity="0.6"/>';
s += '<circle cx="100" cy="76" r="10" fill="#fff" opacity="0.8"/>';
s += '<ellipse cx="100" cy="76" rx="64" ry="18" fill="none" stroke="'+M+'" stroke-width="5" transform="rotate(-20 100 76)"/>';
s += '<ellipse cx="100" cy="76" rx="56" ry="12" fill="none" stroke="'+color+'" stroke-width="2" transform="rotate(-20 100 76)" opacity="0.9"/>';
s += '<circle cx="62" cy="58" r="2.5" fill="#fff" opacity="0.9"/>';
s += '<circle cx="142" cy="94" r="2" fill="#fff" opacity="0.8"/>';
break;
case 'crystal':
s += '<rect x="72" y="132" width="56" height="6" rx="3" fill="'+M2+'" stroke="#05080d" stroke-width="1.5"/>';
s += '<path d="M100 14 L150 60 L130 132 H70 L50 60 Z" fill="'+B+'" stroke="#05080d" stroke-width="2.5"/>';
s += '<path d="M100 14 L100 132" stroke="#05080d" stroke-width="1.5" opacity="0.5" fill="none"/>';
s += '<path d="M50 60 H150" stroke="#05080d" stroke-width="1.5" opacity="0.45" fill="none"/>';
s += '<ellipse cx="100" cy="78" rx="18" ry="34" fill="'+color+'" opacity="0.7" filter="url(#caseGlow'+uid+')"/>';
s += '<ellipse cx="100" cy="78" rx="10" ry="22" fill="#fff" opacity="0.4"/>';
s += '<path d="M100 14 L150 60 L130 132 H70 L50 60 Z" fill="'+HI+'"/>';
s += '<circle cx="100" cy="14" r="3" fill="'+color+'" filter="url(#caseGlow'+uid+')"/>';
break;
default: s += '<rect x="30" y="60" width="140" height="76" rx="5" fill="'+B+'" stroke="#05080d" stroke-width="2.5"/>';
}
return '<svg class="gun" viewBox="0 0 200 150" xmlns="http://www.w3.org/2000/svg">' + defs + glow + s + '</svg>';
}
/* =====================================================================
4. ITEMS
===================================================================== */
function buildItem(defStr, mult){
const parts = defStr.split('|');
const model = parts[0].trim();
const skin = parts[1].trim();
const rarity = parts[2].trim();
const type = MODELS[model] || 'rifle';
const h = hash(model + '|' + skin);
const pal = PALETTES[h % PALETTES.length];
const pattern = PATTERNS[(h >>> 8) % PATTERNS.length];
const range = RARITIES[rarity].price;
const frac = ((h >>> 16) % 1000) / 1000;
const price = Math.max(1, Math.round((range[0] + (range[1]-range[0]) * frac) * mult));
return { model, skin, rarity, type, pal, pattern, price };
}
const CASES = CASE_DATA.map((c, idx) => {
const items = c.defs.map(d => buildItem(d, c.mult));
const featured = items.find(i => i.rarity === 'rare') || items.find(i => i.rarity === 'covert') || items[0];
return { id:'case'+idx, name:c.name, price:c.price, color:c.color, icon:c.icon, variant:c.variant, category: c.category, items, featured };
});
const skinMap = new Map();
CASES.forEach(c => c.items.forEach(it => {
const key = it.model + '|' + it.skin;
const ex = skinMap.get(key);
if(!ex || it.price > ex.price) skinMap.set(key, it);
}));
const ALL_UNIQUE = Array.from(skinMap.values());
const UPG_NAMES = {
milspec:['Городской','Серый','Олива','Хаки','Пустыня','Песок','Ржавый','Карбон','Свинец','Гравий','Сланцевый','Каменный','Мшистый','Землистый','Тёмный','Бетонный'],
restricted:['Аквамарин','Бирюза','Голубой','Лаванда','Морской','Синий','Небесный','Ледяной','Азурный','Индиго','Волновой','Лазурный','Кобальтовый','Ледниковый','Сумрачный'],
classified:['Пурпурный','Розовый','Фиолетовый','Сиреневый','Малиновый','Аметистовый','Магентовый','Лиловый','Гелиотроп','Орхидейный','Фуксия','Барвинковый'],
covert:['Красный','Алый','Кровавый','Огненный','Багровый','Бордовый','Пламенный','Карминный','Багряный','Рубиновый','Драконий','Пурпурно-красный'],
rare:['Золотой','Изумрудный','Бриллиантовый','Космический','Легендарный','Императорский','Божественный','Мифический','Королевский','Фениксовый']
};
function generateExtraSkins(){
const extras = [];
const modelNames = Object.keys(MODELS);
for(const rarity of TIER_ORDER){
for(const model of modelNames){
const count = (rarity === 'milspec' || rarity === 'restricted') ? 1 : 2;
for(let i=0;i<count;i++){
const list = UPG_NAMES[rarity];
const nameIdx = hash(model + rarity + i + 'n') % list.length;
const suffix = i === 0 ? '' : ' ' + ['II','III'][i-1];
extras.push(buildItem(model + '|' + list[nameIdx] + suffix + '|' + rarity, 1.0));
}
}
}
return extras;
}
const UPG_POOL = ALL_UNIQUE.concat(generateExtraSkins());
/* =====================================================================
5. STATE
===================================================================== */
const state = {
balance: 1000,
inventory: [],
stats: {
opened:0, spent:0, upgrades:0, upgradeWins:0, sold:0, earned:0, rareDrops:0, bigWins:0,
clicks:0, clickPower:10, autoClicks:0, critChance:0,
lvlClick:0, lvlAuto:0, lvlCrit:0, prestigeLevel: 0, profName: 'Игрок'
},
settings: { volMusic: 0.5, volSfx: 0.5, animSpeed: 1, theme: 'dark' },
quests: [], questsDate: '', lastBonus: '', opened: 0,
invSort: 'price_desc', invFilter: 'all', currentCategory: 'Рассвет',
level: { xp: 0, level: 1 },
achievements: {}, caseStats: {}, contractSel: [], contractHistory: []
};
const upgState = { bet: null, target: null, targets: [], spinning: false };
let currentCase = null;
let opening = false;
let openMultiplier = 1;
let soundOn = true;
let musicOn = true;
let audioCtx = null;
let musicTimeout = null;
let currentNoteIndex = 0;
let audioInitialized = false;
let wasPlayingBeforeHidden = false;
function save(){
storage.set(SAVE_KEY, JSON.stringify({
balance: state.balance, inventory: state.inventory, stats: state.stats, settings: state.settings,
quests: state.quests, questsDate: state.questsDate, lastBonus: state.lastBonus, opened: state.opened,
soundOn: soundOn, musicOn: musicOn, level: state.level, achievements: state.achievements, caseStats: state.caseStats
}));
}
function load(){
const raw = storage.get(SAVE_KEY) || storage.get('neon_cases_save_v12') || storage.get('neon_cases_save_v11') || storage.get('neon_cases_save_v10');
if(!raw) return;
try {
const d = JSON.parse(raw);
if(typeof d.balance === 'number') state.balance = d.balance;
if(Array.isArray(d.inventory)){
state.inventory = d.inventory.filter(it => it && it.model && it.pal);
state.inventory.forEach(it => { if(!it.uid) it.uid = ++uidCounter; });
}
if(d.stats && typeof d.stats === 'object'){
Object.keys(state.stats).forEach(k => {
if(typeof d.stats[k] === 'number' || typeof d.stats[k] === 'string') state.stats[k] = d.stats[k];
});
}
if(d.settings && typeof d.settings === 'object'){
Object.keys(state.settings).forEach(k => { if(d.settings[k] !== undefined) state.settings[k] = d.settings[k]; });
}
if(Array.isArray(d.quests)) state.quests = d.quests;
if(typeof d.questsDate === 'string') state.questsDate = d.questsDate;
if(typeof d.lastBonus === 'string') state.lastBonus = d.lastBonus;
if(typeof d.opened === 'number') state.opened = d.opened;
if(typeof d.soundOn === 'boolean') soundOn = d.soundOn;
if(typeof d.musicOn === 'boolean') musicOn = d.musicOn;
if(d.level && typeof d.level === 'object') state.level = d.level;
if(d.achievements && typeof d.achievements === 'object') state.achievements = d.achievements;
if(d.caseStats && typeof d.caseStats === 'object') state.caseStats = d.caseStats;
} catch(e) { console.warn('load error', e); }
const maxUid = state.inventory.reduce((m,i) => Math.max(m, i.uid || 0), 0);
uidCounter = Math.max(uidCounter, maxUid);
}
/* =====================================================================
6. LEVEL / XP / ACHIEVEMENTS
===================================================================== */
const XP_VALUES = { caseOpen: 8, upgrade: 25, upgradeWin: 40, sale: 3, click: 1, contract: 20 };
function xpForLevel(lvl){ return Math.round(100 * Math.pow(lvl, 1.75)); }
function addXP(amount){
state.level.xp += amount;
let leveledUp = false;
while(state.level.xp >= xpForLevel(state.level.level)){
state.level.xp -= xpForLevel(state.level.level);
state.level.level++;
const reward = 500 * state.level.level;
state.balance += reward;
showNotification('level', 'Новый уровень!', 'Ур. ' + state.level.level + ' — +' + fmt(reward) + ' ₽');
blip(1600, 0.15, 0.08, 'sine');
setTimeout(() => blip(2000, 0.15, 0.08, 'sine'), 100);
leveledUp = true;
}
updateLevelBar();
if(leveledUp) checkAchievements();
}
function updateLevelBar(){
const badge = document.getElementById('levelBadge');
if(!badge) return;
const needed = xpForLevel(state.level.level);
const pct = Math.min(100, Math.round(state.level.xp / needed * 100));
badge.querySelector('.lvl-num').textContent = 'Ур. ' + state.level.level;
badge.querySelector('.lvl-fill').style.width = pct + '%';
badge.title = state.level.xp + ' / ' + needed + ' XP';
}
const ACHIEVEMENTS = [
{ id:'first_case', name:'Первые шаги', desc:'Открой первый кейс', icon:'📦', check: s => s.opened >= 1, reward: 500 },
{ id:'ten_cases', name:'Кейсовый маньяк', desc:'Открой 10 кейсов', icon:'🎁', check: s => s.opened >= 10, reward: 2000 },
{ id:'hundred_cases', name:'Кейсовый король', desc:'Открой 100 кейсов', icon:'👑', check: s => s.opened >= 100, reward: 20000 },
{ id:'first_rare', name:'Джекпот', desc:'Выбей «Исключительно редкое»', icon:'💎', check: s => s.rareDrops >= 1, reward: 5000 },
{ id:'ten_rare', name:'Охотник за золотом', desc:'Выбей 10 редчайших', icon:'🏆', check: s => s.rareDrops >= 10, reward: 50000 },
{ id:'first_upgrade', name:'Апгрейдер', desc:'Сделай первый апгрейд', icon:'🎲', check: s => s.upgrades >= 1, reward: 300 },
{ id:'ten_upgrades', name:'Мастер апгрейда', desc:'Сделай 10 апгрейдов', icon:'⚡', check: s => s.upgrades >= 10, reward: 3000 },
{ id:'big_win', name:'Большая победа', desc:'Выиграй ×5+', icon:'🚀', check: s => s.bigWins >= 1, reward: 2500 },
{ id:'first_click', name:'Кликер', desc:'Первый клик', icon:'👆', check: s => s.clicks >= 1, reward: 100 },
{ id:'hundred_clicks', name:'Клик-машина', desc:'100 кликов', icon:'🖱️', check: s => s.clicks >= 100, reward: 1000 },
{ id:'thousand_clicks', name:'Клик-гуру', desc:'1000 кликов', icon:'🎮', check: s => s.clicks >= 1000, reward: 10000 },
{ id:'rich', name:'Богач', desc:'100 000 ₽ на балансе', icon:'💰', check: () => state.balance >= 100000, reward: 10000 },
{ id:'millionaire', name:'Миллионер', desc:'1 000 000 ₽ на балансе', icon:'💵', check: () => state.balance >= 1000000, reward: 100000 },
{ id:'prestige', name:'Престиж', desc:'Сделай престиж', icon:'⭐', check: s => s.prestigeLevel >= 1, reward: 5000 },
{ id:'inventory_10', name:'Коллекционер', desc:'10 предметов в инвентаре', icon:'📚', check: () => state.inventory.length >= 10, reward: 500 },
{ id:'inventory_50', name:'Собрание', desc:'50 предметов в инвентаре', icon:'🏛️', check: () => state.inventory.length >= 50, reward: 5000 },
{ id:'inv_value_100k', name:'Драгоценности', desc:'Инвентарь на 100 000 ₽', icon:'🔮', check: () => state.inventory.reduce((a,i) => a+i.price, 0) >= 100000, reward: 5000 },
{ id:'inv_value_1m', name:'Сокровищница', desc:'Инвентарь на 1 000 000 ₽', icon:'🏰', check: () => state.inventory.reduce((a,i) => a+i.price, 0) >= 1000000, reward: 50000 },
{ id:'level_5', name:'Опытный', desc:'Достигни 5 уровня', icon:'🌱', check: () => state.level.level >= 5, reward: 3000 },
{ id:'level_10', name:'Ветеран', desc:'Достигни 10 уровня', icon:'🎖️', check: () => state.level.level >= 10, reward: 15000 },
{ id:'contract_first', name:'Контрактник', desc:'Создай первый контракт', icon:'📜', check: s => (s.contracts || 0) >= 1, reward: 500 },
{ id:'contract_ten', name:'Торговец', desc:'Создай 10 контрактов', icon:'🤝', check: s => (s.contracts || 0) >= 10, reward: 5000 }
];
function checkAchievements(){
let newOnes = false;
for(const a of ACHIEVEMENTS){
if(state.achievements[a.id]) continue;
if(a.check(state.stats)){
state.achievements[a.id] = true;
state.balance += a.reward;
showNotification('achievement', '🏆 Достижение!', a.name + ' — +' + fmt(a.reward) + ' ₽');
blip(1600, 0.15, 0.08, 'sine');
newOnes = true;
}
}
if(newOnes){ renderAchievements(); updateWallet(); save(); }
}
/* =====================================================================
7. NOTIFICATIONS
===================================================================== */
const notifContainer = document.getElementById('notif-container');
function showNotification(type, title, text){
if(!notifContainer) return;
const el = document.createElement('div');
el.className = 'notif notif-' + type;
el.innerHTML = '<div class="notif-title">' + title + '</div><div class="notif-text">' + text + '</div>';
notifContainer.appendChild(el);
setTimeout(() => { el.style.opacity = '0'; el.style.transform = 'translateX(100%)'; setTimeout(() => el.remove(), 350); }, 4200);
while(notifContainer.children.length > 5) notifContainer.removeChild(notifContainer.firstChild);
}
/* =====================================================================
8. QUESTS
===================================================================== */
function pad2(n){ return String(n).padStart(2,'0'); }
function todayStr(){ const d = new Date(); return d.getFullYear()+'-'+pad2(d.getMonth()+1)+'-'+pad2(d.getDate()); }
function mulberry32(a){ return function(){ a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }
const QUEST_POOL = [
{ id:'open', stat:'opened', targets:[3,4,5,6,8], reward:[250,900], title:n => 'Открой '+n+' кейсов' },
{ id:'spend', stat:'spent', targets:[500,1000,1500,2500,4000], reward:[300,1000], title:n => 'Потрать '+fmt(n)+' ₽ на кейсы' },
{ id:'upg', stat:'upgrades', targets:[2,3,4,5,7], reward:[200,800], title:n => 'Сделай '+n+' апгрейдов' },
{ id:'upgwin', stat:'upgradeWins', targets:[1,2,3], reward:[400,1200], title:n => 'Выиграй '+n+' апгрейд(а)' },
{ id:'sell', stat:'sold', targets:[3,4,6,8], reward:[200,700], title:n => 'Продай '+n+' предметов' },
{ id:'rare', stat:'rareDrops', targets:[1,2], reward:[700,2000], title:n => 'Выбей '+n+' редчайших' },
{ id:'bigwin', stat:'bigWins', targets:[1,2], reward:[600,1800], title:n => 'Выиграй '+n+' апгрейд(а) ×5+' },
{ id:'earn', stat:'earned', targets:[800,1500,3000,6000], reward:[300,1100], title:n => 'Заработай '+fmt(n)+' ₽' },
{ id:'click', stat:'clicks', targets:[50,100,150,200], reward:[200,600], title:n => 'Сделай '+n+' кликов' }
];
function generateQuests(dateStr){
const rnd = mulberry32(hash(dateStr));
const pool = QUEST_POOL.slice();
const result = [];
for(let i=0;i<3 && pool.length;i++){
const idx = Math.floor(rnd() * pool.length);
const tpl = pool.splice(idx,1)[0];
const target = tpl.targets[Math.floor(rnd() * tpl.targets.length)];
const rmin = tpl.reward[0], rmax = tpl.reward[1];
let reward = Math.round((rmin + rnd() * (rmax - rmin)) / 50) * 50;
if(reward < 100) reward = 100;
result.push({ id: tpl.id, stat: tpl.stat, target: target, reward: reward, title: tpl.title(target), base: state.stats[tpl.stat] || 0, claimed: false });
}
return result;
}
function ensureDailyQuests(){
const today = todayStr();
if(state.questsDate !== today || !Array.isArray(state.quests) || !state.quests.length){
state.questsDate = today;
state.quests = generateQuests(today);
save();
}
}
function questProgress(q){
const cur = (state.stats[q.stat] || 0) - (q.base || 0);
return Math.max(0, Math.min(q.target, cur));
}
/* =====================================================================
9. AUDIO
===================================================================== */
function ensureAudio(){ if(!audioCtx){ try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e){} } if(audioCtx && audioCtx.state === 'suspended') audioCtx.resume(); }
function blip(freq, dur, vol, type){
if(!soundOn || !audioCtx) return;
const t = audioCtx.currentTime;
const o = audioCtx.createOscillator(); const g = audioCtx.createGain();
o.type = type || 'square'; o.frequency.setValueAtTime(freq, t);
g.gain.setValueAtTime((vol || 0.05) * state.settings.volSfx, t);
g.gain.exponentialRampToValueAtTime(0.0001, t + (dur || 0.05));
o.connect(g).connect(audioCtx.destination); o.start(t); o.stop(t + (dur || 0.05) + 0.02);
}
function tickSound(){ blip(1500, 0.03, 0.03); }
function winSound(rarity){
const seq = { milspec:[440,520], restricted:[520,620,760], classified:[620,760,900], covert:[700,880,1050,1300], rare:[660,880,1100,1320,1600,1980] }[rarity] || [500];
seq.forEach((f,i) => setTimeout(() => blip(f, 0.16, 0.06, 'triangle'), i*90));
}
function loseSound(){ [400,300,220].forEach((f,i) => setTimeout(() => blip(f, 0.2, 0.06, 'sawtooth'), i*120)); }
const MUSIC_TRACKS = {
synthwave: { notes:[261.63,311.13,349.23,392.00,466.16,523.25,392.00,349.23,261.63,311.13,349.23,392.00,466.16,523.25,392.00,349.23], type:'sawtooth', interval:250 },
chiptune: { notes:[523.25,659.25,783.99,1046.50,783.99,659.25,523.25,392.00,523.25,659.25,783.99,1046.50,783.99,659.25,523.25,392.00], type:'square', interval:150 },
ambient: { notes:[220.00,246.94,261.63,293.66,329.63,349.23,392.00,440.00,220.00,246.94,261.63,293.66,329.63,349.23,392.00,440.00], type:'sine', interval:600 },
cyberpunk: { notes:[130.81,130.81,155.56,130.81,130.81,174.61,130.81,130.81,155.56,130.81,130.81,174.61,130.81,130.81,155.56,130.81,130.81,196.00,130.81,130.81,174.61,130.81,130.81,155.56,130.81,130.81,196.00], type:'sawtooth', interval:120 },
lofi: { notes:[261.63,329.63,392.00,493.88,392.00,329.63,261.63,220.00,261.63,329.63,392.00,493.88,523.25,493.88,392.00,329.63], type:'sine', interval:450 },
dark_synth: { notes:[110.00,130.81,146.83,164.81,174.61,196.00,220.00,261.63,110.00,130.81,146.83,164.81,174.61,196.00,220.00,261.63], type:'sawtooth', interval:400 },
arcade: { notes:[523.25,587.33,659.25,698.46,783.99,880.00,987.77,1046.50,523.25,587.33,659.25,698.46,783.99,880.00,987.77,1046.50], type:'square', interval:100 },
epic: { notes:[220.00,261.63,329.63,440.00,523.25,659.25,440.00,329.63,220.00,261.63,329.63,440.00,523.25,659.25,440.00,329.63], type:'triangle', interval:500 },
techno: { notes:[146.83,146.83,146.83,174.61,146.83,146.83,164.81,146.83,146.83,146.83,146.83,174.61,146.83,146.83,164.81,146.83], type:'square', interval:200 },
trap: { notes:[98.00,98.00,116.54,98.00,130.81,98.00,116.54,98.00,98.00,98.00,116.54,98.00,130.81,98.00,116.54,98.00], type:'sine', interval:300 }
};
function playNextNote(){
if(!musicOn || !audioCtx) return;
const trackId = document.getElementById('musicSelect').value;
const track = MUSIC_TRACKS[trackId];
const t = audioCtx.currentTime;
const o = audioCtx.createOscillator(); const g = audioCtx.createGain();
o.type = track.type;
o.frequency.setValueAtTime(track.notes[currentNoteIndex], t);
g.gain.setValueAtTime(0.04 * state.settings.volMusic, t);
g.gain.exponentialRampToValueAtTime(0.001, t + 0.3);
o.connect(g).connect(audioCtx.destination);
o.start(t); o.stop(t + 0.35);
currentNoteIndex = (currentNoteIndex + 1) % track.notes.length;
musicTimeout = setTimeout(playNextNote, track.interval);
}
function startMusic(){ if(!musicOn || !audioCtx) return; clearTimeout(musicTimeout); currentNoteIndex = 0; playNextNote(); }
function toggleMusic(){
ensureAudio(); if(!audioCtx) return;
if(musicOn){ clearTimeout(musicTimeout); musicOn = false; document.getElementById('musicBtn').textContent = '🎵'; toast('Музыка выключена'); }
else { musicOn = true; document.getElementById('musicBtn').textContent = '🔇'; toast('Музыка включена'); startMusic(); }
save();
}
function initAudioOnGesture(){
if(audioInitialized) return;
audioInitialized = true;
ensureAudio();
if(audioCtx && audioCtx.state === 'running'){ if(musicOn) startMusic(); hideAudioPrompt(); }
document.removeEventListener('click', initAudioOnGesture);
document.removeEventListener('keydown', initAudioOnGesture);
document.removeEventListener('touchstart', initAudioOnGesture);
}
function hideAudioPrompt(){ const p = document.getElementById('audio-prompt'); if(p) p.classList.remove('show'); }
function showAudioPrompt(){ const p = document.getElementById('audio-prompt'); if(p) p.classList.add('show'); }
document.addEventListener('visibilitychange', () => {
if(document.hidden){
if(audioCtx && audioCtx.state === 'running'){ wasPlayingBeforeHidden = musicOn; audioCtx.suspend(); }
clearTimeout(musicTimeout);
if(crashState.running){ pauseCrashForVisibility(); }
} else {
if(audioCtx && audioCtx.state === 'suspended' && audioInitialized){
audioCtx.resume().then(() => { if(musicOn && wasPlayingBeforeHidden) startMusic(); });
}
}
});
/* =====================================================================
10. DOM
===================================================================== */
const $ = id => document.getElementById(id);
const caseGrid = $('caseGrid');
const contentsEl = $('contents');
const strip = $('strip');
const roulette = $('roulette');
const resultEl = $('result');
const openBtn = $('openBtn');
const fastBtn = $('fastBtn');
const backBtn = $('backBtn');
const balanceEl = $('balance');
const invBadge = $('invBadge');
const upgBadge = $('upgBadge');
const questBadge = $('questBadge');
const invGrid = $('invGrid');
const invCount = $('invCount');
const invValue = $('invValue');
const openTitle = $('openTitle');
const openSub = $('openSub');
const openCaseStats = $('openCaseStats');
const toastEl = $('toast');
const upgInv = $('upgInv');
const upgBetContent = $('upgBetContent');
const upgTargetContent = $('upgTargetContent');
const upgTargets = $('upgTargets');
const upgConfirm = $('upgConfirm');
const upgReset = $('upgReset');
const upgResult = $('upgResult');
const wheelFace = $('wheelFace');
const wheelChance = $('wheelChance');
const wheelGlow = $('wheelGlow');
const needle = $('needle');
const betCard = $('betCard');
const targetCard = $('targetCard');
const betChip = $('betChip');
const targetChip = $('targetChip');
const questList = $('questList');
const dailyBonus = $('dailyBonus');
const questTimer = $('questTimer');
/* =====================================================================
11. RENDER
===================================================================== */
function renderCaseGrid(){
const filtered = CASES.filter(c => c.category === state.currentCategory);
caseGrid.innerHTML = filtered.map((c) => {
const originalIdx = CASES.findIndex(orig => orig.id === c.id);
const stats = state.caseStats[c.id] || { opens: 0, best: null };
let statLine = '';
if(stats.opens > 0){
const bestTxt = stats.best ? (stats.best.model + ' | ' + stats.best.skin) : '—';
statLine = '<div class="case-stat-mini"><span>Открыто: <b>' + stats.opens + '</b></span><span title="' + bestTxt + '">Лучший: <b>' + (stats.best ? fmt(stats.best.price) + ' ₽' : '—') + '</b></span></div>';
}
return '<div class="case-card" data-idx="'+originalIdx+'" style="--cc:'+c.color+'">'
+ '<div class="case-art">' + caseSVG(c) + '</div>'
+ '<div class="case-name">' + c.name + '</div>'
+ '<div class="case-meta"><span class="case-price">' + fmt(c.price) + ' ₽</span><span>' + c.items.length + ' предметов</span></div>'
+ statLine + '</div>';
}).join('');
}
document.querySelectorAll('.cat-tab').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.cat-tab').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
state.currentCategory = btn.dataset.cat;
renderCaseGrid();
});
});
function rlItemHTML(item){
const r = RARITIES[item.rarity];
return '<div class="rl-item" style="--rc:'+r.color+'"><div class="art">' + gunSVG(item) + '</div><div class="rl-name">' + item.model + '</div><div class="rl-skin" style="color:'+r.color+'">' + item.skin + '</div></div>';
}
function miniHTML(item){
const r = RARITIES[item.rarity];
return '<div class="mini" style="--rc:'+r.color+'"><div class="art">' + gunSVG(item) + '</div><div class="m-model">' + item.model + '</div><div class="m-skin">' + item.skin + '</div><div class="m-price">' + fmt(item.price) + ' ₽</div></div>';
}
function renderContents(c){
const sorted = c.items.slice().sort((a,b) => TIER_ORDER.indexOf(b.rarity) - TIER_ORDER.indexOf(a.rarity));
contentsEl.innerHTML = sorted.map(miniHTML).join('');
}
function itemCardHTML(item){
const r = RARITIES[item.rarity];
return '<div class="item-card" style="--rc:'+r.color+'">'
+ '<div class="item-art">' + gunSVG(item) + '</div>'
+ '<div class="item-model">' + item.model + '</div>'
+ '<div class="item-skin">' + item.skin + '</div>'
+ '<div class="item-price">' + fmt(item.price) + ' ₽</div>'
+ '<div class="item-rar">' + r.name + '</div>'
+ '<div class="item-btns"><button class="sell-btn" data-act="sell" data-uid="'+item.uid+'">Продать</button><button class="sell-btn upg" data-act="upgrade" data-uid="'+item.uid+'">Апгрейд →</button></div></div>';
}
function renderInventory(){
invCount.textContent = state.inventory.length;
invBadge.textContent = state.inventory.length;
upgBadge.textContent = state.inventory.length;
const total = state.inventory.reduce((s,i) => s + i.price, 0);
invValue.textContent = fmt(total);
if(!state.inventory.length){ invGrid.innerHTML = '<div class="empty">Инвентарь пуст. Открой первый кейс!</div>'; return; }
let filtered = state.inventory.slice();
const filterVal = $('filterInv').value;
if(filterVal !== 'all') filtered = filtered.filter(i => i.rarity === filterVal);
const sortVal = $('sortInv').value;
if(sortVal === 'price_desc') filtered.sort((a,b) => b.price - a.price);
else if(sortVal === 'price_asc') filtered.sort((a,b) => a.price - b.price);
else if(sortVal === 'rarity_desc') filtered.sort((a,b) => TIER_ORDER.indexOf(b.rarity) - TIER_ORDER.indexOf(a.rarity));
if(!filtered.length){ invGrid.innerHTML = '<div class="empty">Нет предметов с такими фильтрами</div>'; return; }
invGrid.innerHTML = filtered.map(itemCardHTML).join('');
}
function renderAchievements(){
const el = $('achGrid');
if(!el) return;
const total = ACHIEVEMENTS.length;
const unlocked = ACHIEVEMENTS.filter(a => state.achievements[a.id]).length;
$('achCount').textContent = unlocked;
$('achTotal').textContent = total;
el.innerHTML = ACHIEVEMENTS.map(a => {
const got = !!state.achievements[a.id];
return '<div class="ach-card' + (got ? ' unlocked' : '') + '"><div class="ach-icon">' + a.icon + '</div><div class="ach-info"><div class="ach-name">' + a.name + '</div><div class="ach-desc">' + a.desc + '</div><div class="ach-reward">+' + fmt(a.reward) + ' ₽</div></div></div>';
}).join('');
}
function updateWallet(){ balanceEl.textContent = fmt(state.balance); }
const MULTIPLIERS = [1.2, 1.5, 2, 2.5, 3, 4, 5, 7, 10, 15, 25, 40, 60, 90];
const HOUSE_EDGE = 0.9;
const MIN_CHANCE = 1;
const MAX_CHANCE = 75;
function tierForTarget(value, multiplier){
let tier;
if(value >= 2000) tier = 'rare';
else if(value >= 400) tier = 'covert';
else if(value >= 100) tier = 'classified';
else if(value >= 25) tier = 'restricted';
else tier = 'milspec';
let minIdx = 0;
if(multiplier >= 60) minIdx = 4;
else if(multiplier >= 25) minIdx = 3;
else if(multiplier >= 10) minIdx = 2;
else if(multiplier >= 5) minIdx = 1;
return TIER_ORDER[Math.max(TIER_ORDER.indexOf(tier), minIdx)];
}
function pickSkinForValue(value, multiplier){
const tier = tierForTarget(value, multiplier);
let pool = UPG_POOL.filter(s => s.rarity === tier);
if(!pool.length) pool = UPG_POOL;
const sorted = pool.slice().sort((a,b) => Math.abs(a.price - value) - Math.abs(b.price - value));
const top = sorted.slice(0, Math.min(15, sorted.length));
return top[Math.floor(Math.random() * top.length)];
}
function generateTargets(bet){
const targets = []; const used = new Set();
for(const m of MULTIPLIERS){
const price = Math.round(bet.price * m);
let skin = null, attempts = 0;
do { skin = pickSkinForValue(price, m); attempts++; } while(used.has(skin.model + '|' + skin.skin) && attempts < 50);
if(!skin) skin = UPG_POOL[0];
used.add(skin.model + '|' + skin.skin);
const chance = Math.max(MIN_CHANCE, Math.min(MAX_CHANCE, (1 / m) * HOUSE_EDGE * 100));
targets.push({ model: skin.model, skin: skin.skin, rarity: skin.rarity, type: skin.type, pal: skin.pal, pattern: skin.pattern, price: price, multiplier: m, chance: Math.round(chance * 10) / 10, uid: ++uidCounter });
}
return targets;
}
function findBetInInventory(){ if(!upgState.bet) return null; return state.inventory.find(i => i.uid === upgState.bet.uid) || null; }
function renderUpgradeLeft(){
if(!state.inventory.length){ upgInv.innerHTML = '<div class="empty">Инвентарь пуст</div>'; return; }
const sorted = state.inventory.slice().sort((a,b) => b.price - a.price);
upgInv.innerHTML = sorted.map(item => {
const r = RARITIES[item.rarity];
const sel = upgState.bet && upgState.bet.uid === item.uid ? ' selected' : '';
return '<div class="upg-inv-item'+sel+'" data-uid="'+item.uid+'" style="--rc:'+r.color+'"><div class="art">' + gunSVG(item) + '</div><div class="info"><div class="m">' + item.model + '</div><div class="s">' + item.skin + '</div><div class="p">' + fmt(item.price) + ' ₽</div></div></div>';
}).join('');
}
function renderUpgradeBetSlot(){
if(!upgState.bet){ upgBetContent.innerHTML = '<div class="upg-empty">Выбери скин слева</div>'; betCard.style.setProperty('--rc', '#2a3a52'); betCard.classList.remove('selected-card'); betChip.textContent = '—'; return; }
const it = upgState.bet; const r = RARITIES[it.rarity];
betCard.style.setProperty('--rc', r.color); betCard.classList.add('selected-card'); betChip.textContent = r.name;
upgBetContent.innerHTML = '<div class="upg-slot-item" style="--rc:'+r.color+'"><div class="art">' + gunSVG(it) + '</div><div class="m">' + it.model + '</div><div class="s">' + it.skin + '</div><div class="p">' + fmt(it.price) + ' ₽</div></div>';
}
function renderUpgradeTargetSlot(){
if(!upgState.target){ upgTargetContent.innerHTML = '<div class="upg-empty">Выбери цель ниже</div>'; targetCard.style.setProperty('--rc', '#2a3a52'); targetCard.classList.remove('selected-card'); targetChip.textContent = '—'; return; }
const it = upgState.target; const r = RARITIES[it.rarity];
targetCard.style.setProperty('--rc', r.color); targetCard.classList.add('selected-card'); targetChip.textContent = '×' + it.multiplier;
upgTargetContent.innerHTML = '<div class="upg-slot-item" style="--rc:'+r.color+'"><div class="art">' + gunSVG(it) + '</div><div class="m">' + it.model + '</div><div class="s">' + it.skin + '</div><div class="p">' + fmt(it.price) + ' ₽</div><div class="ch" style="color:'+r.color+'">Шанс: ' + it.chance + '%</div></div>';
}
function renderUpgradeTargets(){
if(!upgState.bet){ upgTargets.innerHTML = '<div class="empty">Сначала выбери свой предмет слева</div>'; return; }
upgTargets.innerHTML = upgState.targets.map(t => {
const r = RARITIES[t.rarity];
const sel = upgState.target && upgState.target.uid === t.uid ? ' selected' : '';
return '<div class="upg-target'+sel+'" data-uid="'+t.uid+'" style="--rc:'+r.color+'"><div class="ml">×' + t.multiplier + '</div><div class="art">' + gunSVG(t) + '</div><div class="m">' + t.model + '</div><div class="s">' + t.skin + '</div><div class="p">' + fmt(t.price) + ' ₽</div><div class="ch">' + t.chance + '%</div></div>';
}).join('');
}
function renderWheel(){
if(upgState.target){
const r = RARITIES[upgState.target.rarity];
wheelFace.style.setProperty('--chance', upgState.target.chance);
wheelFace.style.setProperty('--win-color', r.color);
wheelGlow.style.boxShadow = `0 0 30px 5px ${r.color}`;
wheelChance.textContent = upgState.target.chance + '%';
wheelChance.style.color = r.color;
} else {
wheelFace.style.setProperty('--chance', 0);
wheelFace.style.setProperty('--win-color', '#2a3a52');
wheelGlow.style.boxShadow = 'none';
wheelChance.textContent = '?';
wheelChance.style.color = 'var(--muted)';
}
}
function renderUpgradeConfirm(){
if(upgState.spinning){ upgConfirm.disabled = true; upgConfirm.textContent = 'Крутим...'; upgReset.disabled = true; return; }
if(!upgState.bet){ upgConfirm.disabled = true; upgConfirm.textContent = 'Выбери предмет слева'; upgReset.disabled = true; return; }
if(!upgState.target){ upgConfirm.disabled = true; upgConfirm.textContent = 'Выбери цель ниже'; upgReset.disabled = false; return; }
upgConfirm.disabled = false;
upgConfirm.textContent = 'Апгрейд · шанс ' + upgState.target.chance + '%';
upgReset.disabled = false;
}
function renderUpgrader(){
if(upgState.bet && !findBetInInventory()){ upgState.bet = null; upgState.target = null; upgState.targets = []; }
renderUpgradeLeft(); renderUpgradeBetSlot(); renderUpgradeTargetSlot(); renderUpgradeTargets(); renderWheel(); renderUpgradeConfirm();
}
function renderDailyBonus(){
const claimed = state.lastBonus === todayStr();
dailyBonus.className = 'daily-card' + (claimed ? ' claimed' : '');
dailyBonus.innerHTML = '<div><div class="daily-title">Ежедневная награда</div><div class="daily-desc">' + (claimed ? 'Возвращайся завтра за новым бонусом' : 'Заходи каждый день и получай бонус') + '</div></div>'
+ '<button class="btn primary" id="dailyBtn" ' + (claimed ? 'disabled' : '') + '>' + (claimed ? 'Получено ✓' : 'Забрать +1 000 ₽') + '</button>';
const b = $('dailyBtn');
if(b && !claimed) b.onclick = claimDaily;
}
function claimDaily(){
if(state.lastBonus === todayStr()) return;
state.lastBonus = todayStr(); state.balance += 1000; addXP(50);
ensureAudio(); blip(1000, 0.09, 0.05, 'sine'); setTimeout(() => blip(1400, 0.09, 0.05, 'sine'), 90);
showNotification('success', 'Ежедневная награда', '+1 000 ₽ · +50 XP');
commit();
}
function renderQuests(){
ensureDailyQuests(); renderDailyBonus();
let claimable = 0;
state.quests.forEach(q => { if(!q.claimed && questProgress(q) >= q.target) claimable++; });
questBadge.textContent = claimable;
if(!state.quests.length){ questList.innerHTML = '<div class="empty">Нет заданий на сегодня</div>'; return; }
questList.innerHTML = state.quests.map((q,i) => {
const prog = questProgress(q);
const pct = Math.min(100, Math.round(prog / q.target * 100));
const done = prog >= q.target;
return '<div class="quest-card' + (q.claimed ? ' claimed' : (done ? ' done' : '')) + '">'
+ '<div class="quest-top"><div><div class="quest-title">' + q.title + '</div><div class="quest-desc">' + (q.claimed ? 'Награда получена' : 'Прогресс: ' + fmt(prog) + ' / ' + fmt(q.target)) + '</div></div><div class="quest-reward">+' + fmt(q.reward) + ' ₽</div></div>'
+ '<div class="qbar"><i style="width:' + pct + '%"></i></div>'
+ '<div class="qmeta"><span>' + pct + '%</span><span>' + (done ? 'Готово' : 'В процессе') + '</span></div>'
+ '<div class="quest-claim">' + (q.claimed ? '<button class="btn ghost" disabled>Награда получена</button>' : '<button class="btn ' + (done ? 'primary' : '') + '" data-q="' + i + '" ' + (done ? '' : 'disabled') + '>' + (done ? 'Забрать награду' : 'Выполняется') + '</button>') + '</div></div>';
}).join('');
}
function claimQuest(idx){
const q = state.quests[idx];
if(!q || q.claimed) return;
if(questProgress(q) < q.target) return;
q.claimed = true; state.balance += q.reward; addXP(30);
ensureAudio(); blip(880, 0.12, 0.06, 'sine'); setTimeout(() => blip(1200, 0.12, 0.06, 'sine'), 100);
showNotification('success', 'Награда получена', '+' + fmt(q.reward) + ' ₽ · +30 XP');
commit();
}
function commit(){ save(); updateWallet(); renderInventory(); renderQuests(); checkAchievements(); }
/* =====================================================================
12. CASE OPENING
===================================================================== */
const ITEM_W = 156;
const WIN_INDEX = 42;
const STRIP_LEN = 50;
let idleX = 0, idleAnimId = null, isIdle = false;
function buildIdleStrip(c){ let items = []; for(let i=0; i<40; i++) items.push(rlItemHTML(pickDisplayItem(c))); strip.innerHTML = items.join('') + items.join(''); idleX = 0; strip.style.transform = `translate3d(0,0,0)`; }
function idleLoop(){ if(!isIdle) return; idleX -= 1; if(idleX <= -ITEM_W * 40) idleX = 0; strip.style.transform = `translate3d(${idleX}px,0,0)`; idleAnimId = requestAnimationFrame(idleLoop); }
function startIdleRoulette(c){ isIdle = true; buildIdleStrip(c); if(idleAnimId) cancelAnimationFrame(idleAnimId); idleAnimId = requestAnimationFrame(idleLoop); }
function stopIdleRoulette(){ isIdle = false; if(idleAnimId) cancelAnimationFrame(idleAnimId); strip.innerHTML = ''; strip.style.transform = 'translate3d(0,0,0)'; }
function pickDisplayItem(c){
const r = Math.random(); let tier;
if(r < 0.62) tier = 'milspec'; else if(r < 0.84) tier = 'restricted'; else if(r < 0.94) tier = 'classified'; else if(r < 0.985) tier = 'covert'; else tier = 'rare';
let pool = c.items.filter(i => i.rarity === tier); if(!pool.length) pool = c.items;
return pool[Math.floor(Math.random() * pool.length)];
}
function rollItem(c){
const r = Math.random() * 10000; let acc = 0, tier = 'milspec';
for(const k of TIER_ORDER){ acc += RARITY_WEIGHT[k]; if(r < acc){ tier = k; break; } }
let pool = c.items.filter(i => i.rarity === tier); if(!pool.length) pool = c.items.filter(i => i.rarity === 'milspec'); if(!pool.length) pool = c.items;
const base = pool[Math.floor(Math.random() * pool.length)];
return Object.assign({}, base, { uid: ++uidCounter });
}
function buildStrip(c, winner){ let html = ''; for(let i=0;i<STRIP_LEN;i++){ const item = (i === WIN_INDEX) ? winner : pickDisplayItem(c); html += rlItemHTML(item); } strip.innerHTML = html; }
function spin(){
return new Promise(resolve => {
const W = roulette.clientWidth || 800; const center = W / 2;
const targetCenter = WIN_INDEX * ITEM_W + ITEM_W / 2;
const jitter = (Math.random() - 0.5) * 90;
const endX = center - targetCenter + jitter;
const startX = endX + 3400 + Math.random() * 700;
strip.style.transform = 'translate3d(' + startX + 'px,0,0)';
const dur = 6200 / state.settings.animSpeed;
const t0 = performance.now(); let lastIdx = -1;
function frame(now){
const t = Math.min(1, (now - t0) / dur); const p = 1 - Math.pow(1 - t, 4);
const x = startX + (endX - startX) * p; strip.style.transform = 'translate3d(' + x + 'px,0,0)';
const idx = Math.round((center - x - ITEM_W/2) / ITEM_W);
if(idx !== lastIdx){ lastIdx = idx; tickSound(); }
if(t < 1) requestAnimationFrame(frame); else resolve();
}
requestAnimationFrame(frame);
});
}
function showResult(item){
const r = RARITIES[item.rarity];
resultEl.innerHTML = '<div class="res-card" style="--rc:'+r.color+'"><div class="res-art" style="--rc:'+r.color+'">' + gunSVG(item) + '</div><div class="res-rar">' + r.name + '</div><div class="res-name">' + item.model + ' <span>| ' + item.skin + '</span></div><div class="res-price">' + fmt(item.price) + ' ₽</div><div class="res-actions"><button class="btn primary" id="resSell">Продать за ' + fmt(item.price) + ' ₽</button><button class="btn" id="resUpgrade">В апгрейдер →</button><button class="btn ghost" id="resKeep">Оставить</button></div></div>';
resultEl.classList.add('show');
$('resSell').onclick = () => { sellItem(item.uid); resultEl.classList.remove('show'); };
$('resUpgrade').onclick = () => { resultEl.classList.remove('show'); selectBet(item.uid); showView('upgrade'); };
$('resKeep').onclick = () => resultEl.classList.remove('show');
}
function showMultiResult(winners){
const total = winners.reduce((a,w) => a + w.price, 0);
const rows = winners.map(w => { const r = RARITIES[w.rarity]; return '<div class="multi-res-card" style="--rc:'+r.color+'"><div class="art">' + gunSVG(w) + '</div><div class="m">' + w.model + '</div><div class="s" style="color:'+r.color+'">' + w.skin + '</div><div class="p">' + fmt(w.price) + ' ₽</div></div>'; }).join('');
resultEl.innerHTML = '<div class="res-card" style="--rc:#ffd166;padding:18px"><div class="res-name" style="font-size:18px;margin-bottom:8px">Открыто ' + winners.length + ' кейсов</div><div class="res-price" style="font-size:20px">Всего: ' + fmt(total) + ' ₽</div><div class="multi-results">' + rows + '</div></div>';
resultEl.classList.add('show');
}
function showConfetti(){
const colors = ['#ff8a00', '#ffd166', '#39ff14', '#ff1493', '#00e5ff'];
for(let i=0;i<60;i++){
const p = document.createElement('div'); p.className = 'confetti-piece';
p.style.left = Math.random()*100+'vw'; p.style.top = '-10px'; p.style.background = colors[Math.floor(Math.random()*colors.length)];
p.style.animationDelay = Math.random()*0.5+'s'; p.style.animationDuration = Math.random()*1+1.5+'s';
p.style.transform = `rotate(${Math.random()*360}deg)`;
document.body.appendChild(p); setTimeout(()=>p.remove(), 3000);
}
}
function recordCaseStat(caseObj, winner){
if(!state.caseStats[caseObj.id]) state.caseStats[caseObj.id] = { opens:0, best:null };
const s = state.caseStats[caseObj.id]; s.opens++;
if(!s.best || winner.price > s.best.price) s.best = { model: winner.model, skin: winner.skin, price: winner.price, rarity: winner.rarity };
}
function renderOpenCaseStats(c){
if(!c){ openCaseStats.innerHTML = ''; return; }
const s = state.caseStats[c.id];
if(!s || !s.opens){ openCaseStats.innerHTML = '<span>Ещё не открывался</span>'; return; }
const bestTxt = s.best ? (s.best.model + ' | ' + s.best.skin + ' (' + fmt(s.best.price) + ' ₽)') : '—';
openCaseStats.innerHTML = '<span>Открыто: <b>' + s.opens + '</b> раз</span><span>Лучший дроп: <span class="stat-good">' + bestTxt + '</span></span>';
}
function finishOpen(winner, caseObj){
state.inventory.push(winner); state.opened++; state.stats.opened++; state.stats.spent += caseObj.price;
if(winner.rarity === 'covert' || winner.rarity === 'rare') state.stats.rareDrops++;
recordCaseStat(caseObj, winner);
addXP(XP_VALUES.caseOpen + Math.round(caseObj.price / 50));
if(winner.rarity === 'rare' || winner.rarity === 'covert') showConfetti();
renderInventory(); showResult(winner); winSound(winner.rarity); renderOpenCaseStats(caseObj); commit();
}
async function doMultiOpen(n){
const c = currentCase; if(!c) return;
const cost = c.price * n;
if(state.balance < cost){ toast('Недостаточно средств: нужно ' + fmt(cost) + ' ₽'); return; }
ensureAudio(); state.balance -= cost; updateWallet();
const winners = [];
for(let i=0;i<n;i++){
const w = rollItem(c);
state.inventory.push(w); state.opened++; state.stats.opened++; state.stats.spent += c.price;
if(w.rarity === 'covert' || w.rarity === 'rare') state.stats.rareDrops++;
recordCaseStat(c, w); winners.push(w);
}
addXP(XP_VALUES.caseOpen * n + Math.round(cost / 50));
const best = winners.reduce((a,b) => TIER_ORDER.indexOf(b.rarity) > TIER_ORDER.indexOf(a.rarity) ? b : a, winners[0]);
if(best.rarity === 'rare' || best.rarity === 'covert') showConfetti();
winSound(best.rarity);
renderInventory(); showMultiResult(winners); renderOpenCaseStats(c); commit();
}
async function doOpen(fast){
if(opening || !currentCase) return;
const c = currentCase;
if(openMultiplier > 1){ await doMultiOpen(openMultiplier); return; }
if(state.balance < c.price){ toast('Недостаточно средств! Пополни в кликере'); return; }
ensureAudio(); state.balance -= c.price; updateWallet();
const winner = rollItem(c);
if(fast){ resultEl.classList.remove('show'); stopIdleRoulette(); startIdleRoulette(c); finishOpen(winner, c); return; }
opening = true; openBtn.disabled = true; fastBtn.disabled = true;
resultEl.classList.remove('show'); stopIdleRoulette(); buildStrip(c, winner);
await spin();
finishOpen(winner, c);
opening = false; openBtn.disabled = false; fastBtn.disabled = false;
startIdleRoulette(c);
}
function sellItem(uid){
const i = state.inventory.findIndex(x => x.uid === uid); if(i < 0) return;
const it = state.inventory[i];
state.balance += it.price; state.inventory.splice(i, 1);
state.stats.sold++; state.stats.earned += it.price;
addXP(XP_VALUES.sale + Math.round(it.price / 200));
blip(880, 0.1, 0.05, 'sine');
toast('Продано: ' + it.model + ' | ' + it.skin + ' — +' + fmt(it.price) + ' ₽');
renderUpgrader(); commit();
}
function selectBet(uid){
const it = state.inventory.find(i => i.uid === uid); if(!it) return;
upgState.bet = it; upgState.target = null; upgState.targets = generateTargets(it); upgResult.classList.remove('show'); renderUpgrader();
}
function selectTarget(uid){ const t = upgState.targets.find(x => x.uid === uid); if(!t) return; upgState.target = t; renderUpgrader(); }
function resetUpgrade(){ if(upgState.spinning) return; upgState.target = null; upgResult.classList.remove('show'); renderUpgrader(); }
function spinWheel(chance, isWin){
return new Promise(resolve => {
const chanceDeg = chance * 3.6; let finalDeg;
if(isWin) finalDeg = chanceDeg < 10 ? chanceDeg / 2 : 5 + Math.random() * (chanceDeg - 10);
else finalDeg = (360 - chanceDeg) < 10 ? chanceDeg + (360 - chanceDeg) / 2 : chanceDeg + 5 + Math.random() * (360 - chanceDeg - 10);
const spins = 5 + Math.floor(Math.random() * 2); const totalDeg = spins * 360 + finalDeg;
const dur = 4500 / state.settings.animSpeed; const t0 = performance.now(); let lastTick = 0;
function frame(now){
const t = Math.min(1, (now - t0) / dur); const eased = 1 - Math.pow(1 - t, 4);
const curDeg = totalDeg * eased;
needle.style.transform = 'translate(-50%,-100%) rotate(' + curDeg + 'deg)';
const interval = 30 + Math.pow(t, 3) * 400;
if(now - lastTick > interval){ lastTick = now; tickSound(); }
if(t < 1) requestAnimationFrame(frame); else resolve();
}
requestAnimationFrame(frame);
});
}
function showUpgResult(isWin, betItem, targetItem){
const color = isWin ? '#39ff14' : '#eb4b4b'; const label = isWin ? 'ПОБЕДА!' : 'ПРОИГРЫШ';
const showItem = isWin ? targetItem : betItem;
upgResult.innerHTML = '<div class="upg-result-card" style="--rc:'+color+'"><div class="upg-result-title">' + label + '</div><div class="upg-result-item" style="--rc:'+color+'">' + gunSVG(showItem) + '</div><div class="upg-result-name">' + showItem.model + ' | ' + showItem.skin + '</div><div class="upg-result-price" style="color:' + color + '">' + (isWin ? '+' + fmt(targetItem.price) + ' ₽' : '-' + fmt(betItem.price) + ' ₽') + '</div></div>';
upgResult.classList.add('show');
}
async function doUpgrade(){
if(upgState.spinning) return;
if(!upgState.bet || !upgState.target) return;
const betInInv = findBetInInventory();
if(!betInInv){ toast('Предмет уже не в инвентаре'); renderUpgrader(); return; }
ensureAudio(); upgState.spinning = true; upgResult.classList.remove('show'); renderUpgradeConfirm();
const bet = betInInv; const target = upgState.target;
const isWin = Math.random() * 100 < target.chance;
await spinWheel(target.chance, isWin);
const idx = state.inventory.findIndex(i => i.uid === bet.uid); if(idx >= 0) state.inventory.splice(idx, 1);
state.stats.upgrades++; addXP(XP_VALUES.upgrade);
if(isWin){
state.stats.upgradeWins++; if(target.multiplier >= 5) state.stats.bigWins++;
state.inventory.push({ model: target.model, skin: target.skin, rarity: target.rarity, type: target.type, pal: target.pal, pattern: target.pattern, price: target.price, uid: target.uid });
addXP(XP_VALUES.upgradeWin); winSound(target.rarity);
} else { loseSound(); }
renderInventory(); showUpgResult(isWin, bet, target);
upgState.spinning = false; upgState.bet = null; upgState.target = null; upgState.targets = [];
renderUpgrader(); commit();
}
/* =====================================================================
13. CONTRACT
===================================================================== */
function renderContract(){
const slotsEl = $('contractSlots'); const invEl = $('contractInv');
const previewEl = $('contractPreview'); const countEl = $('contractCount'); const btn = $('contractBtn');
countEl.textContent = state.contractSel.length;
let slotsHtml = '';
for(let i=0;i<5;i++){
if(i < state.contractSel.length){
const item = state.contractSel[i]; const r = RARITIES[item.rarity];
slotsHtml += '<div class="contract-slot filled" style="--rc:'+r.color+'"><div class="art">' + gunSVG(item) + '</div><button class="remove" data-remove="'+i+'">×</button></div>';
} else if(i < 5){ slotsHtml += '<div class="contract-slot">+</div>'; }
}
slotsEl.innerHTML = slotsHtml;
if(state.contractSel.length >= 3){
const total = state.contractSel.reduce((a,i) => a + i.price, 0);
const resultValue = Math.round(total * 0.85);
const tier = tierForTarget(resultValue, 3); const r = RARITIES[tier];
previewEl.innerHTML = 'Награда (примерно)<div class="preview-rarity" style="color:'+r.color+'">' + r.name + '</div><div class="preview-value">~' + fmt(resultValue) + ' ₽</div>';
btn.disabled = false; btn.textContent = 'Создать контракт (' + state.contractSel.length + ' шт.)';
} else {
previewEl.innerHTML = state.contractSel.length === 0 ? 'Выберите предметы из инвентаря справа' : 'Нужно ещё ' + (3 - state.contractSel.length) + ' предмет(а)';
btn.disabled = true; btn.textContent = 'Минимум 3 предмета';
}
const invSorted = state.inventory.slice().sort((a,b) => b.price - a.price);
if(!invSorted.length){ invEl.innerHTML = '<div class="empty" style="padding:10px;font-size:12px">Пусто</div>'; return; }
invEl.innerHTML = invSorted.map(item => {
const r = RARITIES[item.rarity];
const sel = state.contractSel.some(x => x.uid === item.uid) ? ' selected' : '';
return '<div class="contract-inv-item'+sel+'" data-uid="'+item.uid+'" style="--rc:'+r.color+'" title="' + item.model + ' | ' + item.skin + ' — ' + fmt(item.price) + ' ₽"><div class="art">' + gunSVG(item) + '</div><div class="s">' + item.skin + '</div></div>';
}).join('');
}
function toggleContractItem(uid){
const idx = state.contractSel.findIndex(i => i.uid === uid);
if(idx >= 0){ state.contractSel.splice(idx, 1); }
else {
if(state.contractSel.length >= 5){ toast('Максимум 5 предметов'); return; }
const item = state.inventory.find(i => i.uid === uid); if(!item) return;
state.contractSel.push(item);
}
renderContract();
}
function createContract(){
if(state.contractSel.length < 3){ toast('Нужно минимум 3 предмета'); return; }
const total = state.contractSel.reduce((a,i) => a + i.price, 0);
const resultValue = Math.round(total * 0.85);
const tier = tierForTarget(resultValue, 3);
const pool = UPG_POOL.filter(s => s.rarity === tier);
if(!pool.length){ toast('Ошибка: пул пуст'); return; }
const sorted = pool.slice().sort((a,b) => Math.abs(a.price - resultValue) - Math.abs(b.price - resultValue));
const pick = sorted[Math.floor(Math.random() * Math.min(5, sorted.length))];
const newItem = { model: pick.model, skin: pick.skin, rarity: pick.rarity, type: pick.type, pal: pick.pal, pattern: pick.pattern, price: Math.round(resultValue), uid: ++uidCounter };
const selectedUids = new Set(state.contractSel.map(i => i.uid));
state.inventory = state.inventory.filter(i => !selectedUids.has(i.uid));
state.inventory.push(newItem);
state.stats.contracts = (state.stats.contracts || 0) + 1;
addXP(XP_VALUES.contract);
state.contractSel = [];
renderContract(); renderInventory();
showNotification('rare', 'Контракт создан!', newItem.model + ' | ' + newItem.skin + ' — ' + fmt(newItem.price) + ' ₽');
ensureAudio(); blip(1200, 0.15, 0.07, 'sine'); setTimeout(() => blip(1600, 0.15, 0.07, 'sine'), 100);
commit();
}
/* =====================================================================
14. VIEWS
===================================================================== */
function showView(name){
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
const el = $('view-' + name); if(el) el.classList.add('active');
document.querySelectorAll('.tab').forEach(t => { t.classList.toggle('active', t.dataset.view === name); });
if(name === 'inv') renderInventory();
if(name === 'upgrade') renderUpgrader();
if(name === 'quests') renderQuests();
if(name === 'clicker') renderClicker();
if(name === 'profile'){ renderProfile(); renderAchievements(); }
if(name === 'cases') renderCaseGrid();
if(name === 'contract') renderContract();
if(name !== 'open') stopIdleRoulette();
if(name !== 'crash' && crashState.running) endCrash(false, true);
if(name === 'crash') {
setTimeout(() => {
resizeCanvas();
drawCrashGraph();
}, 50);
}
window.scrollTo({top:0, behavior:'smooth'});
}
document.querySelectorAll('.tab').forEach(tab => { tab.addEventListener('click', () => showView(tab.dataset.view)); });
let toastTimer = null;
function toast(msg){ toastEl.textContent = msg; toastEl.classList.add('show'); clearTimeout(toastTimer); toastTimer = setTimeout(() => toastEl.classList.remove('show'), 2200); }
function tickQuestTimer(){
const now = new Date();
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
const diff = Math.max(0, end - now);
const h = Math.floor(diff / 3600000); const m = Math.floor(diff / 60000) % 60; const s = Math.floor(diff / 1000) % 60;
if(questTimer) questTimer.textContent = pad2(h) + ':' + pad2(m) + ':' + pad2(s);
if(state.questsDate && state.questsDate !== todayStr()){ state.quests = []; ensureDailyQuests(); renderQuests(); }
}
/* =====================================================================
15. CLICKER
===================================================================== */
const clickerBtn = $('clickerBtn');
const statClicks = $('statClicks');
const statClickPower = $('statClickPower');
const statAutoClicks = $('statAutoClicks');
const statCrit = $('statCrit');
const clickBtnValue = $('clickBtnValue');
const robotZone = $('robotZone');
const lvlClick = $('lvlClick');
const lvlAuto = $('lvlAuto');
const lvlCrit = $('lvlCrit');
const shopClick = $('shopClick');
const shopAuto = $('shopAuto');
const shopCrit = $('shopCrit');
let lastClickTime = 0;
function renderRobots(){
const lvl = state.stats.lvlAuto;
const platform = '<div class="robot-platform"></div>';
if(lvl === 0){ robotZone.innerHTML = platform; return; }
let html = platform;
const count = Math.min(lvl, 5);
for(let i = 0; i < count; i++){
let type = 'scout';
if(lvl >= 3 && i === 0) type = 'heavy';
if(lvl >= 5 && i === 1) type = 'mech';
if(lvl >= 7 && i === 2) type = 'overlord';
if(type === 'scout' && lvl >= 2) type = 'soldier';
let svg = '';
if(type === 'scout'){ svg = `<svg width="80" height="80" viewBox="0 0 100 100"><circle cx="50" cy="50" r="30" fill="#18212e" stroke="#00e5ff" stroke-width="3"/><ellipse cx="50" cy="55" rx="15" ry="8" fill="#00e5ff" class="eye-glow"/><rect x="20" y="50" width="10" height="15" rx="3" fill="#2c3b4d"/><rect x="70" y="50" width="10" height="15" rx="3" fill="#2c3b4d"/><path d="M35 80 L50 85 L65 80" fill="none" stroke="#00e5ff" stroke-width="2"/></svg>`; }
else if(type === 'soldier'){ svg = `<svg width="90" height="90" viewBox="0 0 100 100"><rect x="30" y="30" width="40" height="40" rx="8" fill="#1c2836" stroke="#ff8a00" stroke-width="3"/><circle cx="50" cy="50" r="12" fill="#ff8a00" class="eye-glow"/><path d="M30 40 L15 50 L15 60 L30 55" fill="#ff8a00"/><path d="M70 40 L85 50 L85 60 L70 55" fill="#ff8a00"/><rect x="40" y="70" width="20" height="20" fill="#2c3b4d" stroke="#ff8a00" stroke-width="2"/></svg>`; }
else if(type === 'heavy'){ svg = `<svg width="100" height="100" viewBox="0 0 100 100"><path d="M25 50 L10 20 L40 30 Z" fill="#2d1a2a" stroke="#ff1493" stroke-width="2"/><path d="M75 50 L90 20 L60 30 Z" fill="#2d1a2a" stroke="#ff1493" stroke-width="2"/><rect x="25" y="30" width="50" height="45" rx="10" fill="#2d1a2a" stroke="#ff1493" stroke-width="4"/><circle cx="50" cy="50" r="15" fill="#ff1493" class="eye-glow"/><rect x="20" y="60" width="15" height="25" fill="#2d1a2a" stroke="#ff1493" stroke-width="2"/><rect x="65" y="60" width="15" height="25" fill="#2d1a2a" stroke="#ff1493" stroke-width="2"/><path d="M35 75 L50 85 L65 75" fill="none" stroke="#ff1493" stroke-width="3"/></svg>`; }
else if(type === 'mech'){ svg = `<svg width="110" height="110" viewBox="0 0 100 100"><path d="M30 40 L15 10 L45 25 Z" fill="#3d2b00" stroke="#ffd700" stroke-width="2"/><path d="M70 40 L85 10 L55 25 Z" fill="#3d2b00" stroke="#ffd700" stroke-width="2"/><rect x="25" y="25" width="50" height="50" rx="10" fill="#3d2b00" stroke="#ffd700" stroke-width="4"/><circle cx="50" cy="50" r="18" fill="#ffd700" class="eye-glow"/><path d="M50 50 L50 75" stroke="#ffd700" stroke-width="5"/><rect x="20" y="75" width="15" height="20" fill="#3d2b00" stroke="#ffd700" stroke-width="2"/><rect x="65" y="75" width="15" height="20" fill="#3d2b00" stroke="#ffd700" stroke-width="2"/><path d="M50 25 L50 10" stroke="#ffd700" stroke-width="3"/><circle cx="50" cy="10" r="5" fill="#ffd700"/></svg>`; }
else { svg = `<svg width="120" height="120" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="#0a0e14" stroke="#39ff14" stroke-width="4"/><path d="M50 10 L60 40 L90 50 L60 60 L50 90 L40 60 L10 50 L40 40 Z" fill="#39ff14"/><circle cx="50" cy="50" r="15" fill="#fff" class="eye-glow"/><circle cx="50" cy="50" r="8" fill="#39ff14"/><path d="M20 20 L30 30 M80 20 L70 30 M20 80 L30 70 M80 80 L70 70" stroke="#39ff14" stroke-width="3"/></svg>`; }
html += `<div class="robot" data-type="${type}"><div class="laser-beam"></div>${svg}</div>`;
}
robotZone.innerHTML = html;
}
function renderClicker(){
statClicks.textContent = fmt(state.stats.clicks);
statClickPower.textContent = fmt(state.stats.clickPower) + ' ₽';
statAutoClicks.textContent = fmt(state.stats.autoClicks) + ' / сек';
statCrit.textContent = state.stats.critChance + '%';
clickBtnValue.textContent = '+' + fmt(state.stats.clickPower) + ' ₽';
lvlClick.textContent = 'Ур. ' + state.stats.lvlClick;
lvlAuto.textContent = 'Ур. ' + state.stats.lvlAuto;
lvlCrit.textContent = 'Ур. ' + state.stats.lvlCrit;
const cClick = Math.round(10000 * Math.pow(1.5, state.stats.lvlClick));
const cAuto = Math.round(50000 * Math.pow(1.6, state.stats.lvlAuto));
const cCrit = Math.round(100000 * Math.pow(2.0, state.stats.lvlCrit));
shopClick.querySelector('.shop-buy').textContent = fmt(cClick) + ' ₽';
shopAuto.querySelector('.shop-buy').textContent = fmt(cAuto) + ' ₽';
shopCrit.querySelector('.shop-buy').textContent = fmt(cCrit) + ' ₽';
shopClick.querySelector('.shop-buy').disabled = state.balance < cClick;
shopAuto.querySelector('.shop-buy').disabled = state.balance < cAuto;
shopCrit.querySelector('.shop-buy').disabled = state.balance < cCrit;
const pBtn = document.getElementById('prestigeBtn');
if(state.stats.prestigeLevel > 0) pBtn.textContent = `ПРЕСТИЖ ${state.stats.prestigeLevel} УР. (Сбросить)`;
else pBtn.textContent = 'ПРЕСТИЖ (Требуется 10 ур. клика)';
pBtn.disabled = state.stats.lvlClick < 10;
renderRobots();
}
function buyUpgrade(type){
ensureAudio(); blip(800, 0.05, 0.03, 'square');
if(type === 'click'){
const cost = Math.round(10000 * Math.pow(1.5, state.stats.lvlClick));
if(state.balance >= cost){ state.balance -= cost; state.stats.lvlClick++; state.stats.clickPower += 5; blip(1200, 0.1, 0.05, 'triangle'); toast('Улучшение куплено: Мощный клик!'); commit(); renderClicker(); }
} else if(type === 'auto'){
const cost = Math.round(50000 * Math.pow(1.6, state.stats.lvlAuto));
if(state.balance >= cost){ state.balance -= cost; state.stats.lvlAuto++; state.stats.autoClicks += 2; blip(1200, 0.1, 0.05, 'triangle'); toast('Куплен Дрон-кликер! +2 клика/сек'); commit(); renderClicker(); }
} else if(type === 'crit'){
const cost = Math.round(100000 * Math.pow(2.0, state.stats.lvlCrit));
if(state.balance >= cost){ state.balance -= cost; state.stats.lvlCrit++; state.stats.critChance += 5; blip(1200, 0.1, 0.05, 'triangle'); toast('Улучшение куплено: Крит. модуль!'); commit(); renderClicker(); }
}
}
function doPrestige(){
if(state.stats.lvlClick < 10) return;
if(!confirm('Вы уверены? Все улучшения кликера сбросятся, но вы получите +5 к базовой силе клика навсегда.')) return;
state.stats.prestigeLevel++;
state.stats.lvlClick = 0; state.stats.lvlAuto = 0; state.stats.lvlCrit = 0;
state.stats.clickPower = 10 + (state.stats.prestigeLevel * 5);
state.stats.autoClicks = 0; state.stats.critChance = 0;
ensureAudio(); blip(600, 0.2, 0.08, 'sawtooth'); setTimeout(() => blip(900, 0.2, 0.08, 'sawtooth'), 100); setTimeout(() => blip(1200, 0.3, 0.08, 'sawtooth'), 200);
toast(`Престиж ${state.stats.prestigeLevel}! Базовая сила клика ${state.stats.clickPower} ₽`);
commit(); renderClicker();
}
clickerBtn.addEventListener('click', (e) => {
ensureAudio();
const now = Date.now(); if(now - lastClickTime < 100) return; lastClickTime = now;
let amount = state.stats.clickPower; let isCrit = false;
if(Math.random() * 100 < state.stats.critChance){ amount *= 10; isCrit = true; }
state.balance += amount; state.stats.clicks++; updateWallet(); addXP(XP_VALUES.click); save();
blip(isCrit ? 1500 : 800, isCrit ? 0.15 : 0.05, 0.04, isCrit ? 'sawtooth' : 'sine');
clickerBtn.classList.add('pressed');
setTimeout(() => clickerBtn.classList.remove('pressed'), 100);
const text = document.createElement('div'); text.className = 'floating-text';
text.textContent = '+' + fmt(amount);
if(isCrit){ text.style.color = '#ff8a00'; text.style.fontSize = '40px'; text.textContent = 'КРИТ! +' + fmt(amount); }
text.style.left = e.clientX + 'px'; text.style.top = e.clientY + 'px';
document.body.appendChild(text); setTimeout(() => text.remove(), 1000);
renderClicker();
});
setInterval(() => {
if(state.stats.autoClicks > 0){
state.balance += state.stats.autoClicks; updateWallet();
if(Math.random() < 0.2) save();
if(document.getElementById('view-clicker').classList.contains('active')){
renderClicker();
clickerBtn.classList.add('pressed');
setTimeout(() => clickerBtn.classList.remove('pressed'), 100);
const robots = document.querySelectorAll('.robot');
robots.forEach(r => { r.classList.add('shooting'); setTimeout(() => r.classList.remove('shooting'), 250); });
}
}
}, 1000);
/* =====================================================================
16. PROFILE
===================================================================== */
function renderProfile(){
$('profName').value = state.stats.profName || 'Игрок';
$('profOpened').textContent = fmt(state.stats.opened || 0);
$('profSpent').textContent = fmt(state.stats.spent || 0) + ' ₽';
$('profUpg').textContent = fmt(state.stats.upgrades || 0);
$('profUpgWins').textContent = fmt(state.stats.upgradeWins || 0);
$('profLevel').textContent = state.level.level;
$('profXp').textContent = state.level.xp + ' / ' + xpForLevel(state.level.level);
}
$('profName').addEventListener('input', (e) => { state.stats.profName = e.target.value; save(); });
/* =====================================================================
17. CRASH — ТОРГОВЫЙ ТЕРМИНАЛ (Candlestick + MA)
===================================================================== */
const canvas = document.getElementById('crashCanvas');
const ctx = canvas.getContext('2d');
const crashMultiplierEl = document.getElementById('crashMultiplier');
const crashPnlEl = document.getElementById('crashPnl');
const crashPlayBtn = document.getElementById('crashPlayBtn');
const crashCashoutBtn = document.getElementById('crashCashoutBtn');
const crashBetInput = document.getElementById('crashBet');
const crashHistoryEl = document.getElementById('crashHistory');
const crashPlayersList = document.getElementById('crashPlayersList');
const crashAutoBox = document.getElementById('crashAutoBox');
const crashAutoInput = document.getElementById('crashAuto');
const crashAutoBtn = document.getElementById('crashAutoBtn');
const CANDLE_DURATION = 0.5;
const MAX_CANDLES = 40;
const Y_MIN = 0.4;
const Y_MAX = 4.0;
const PAD_LEFT = 10;
const PAD_RIGHT = 60;
const PAD_TOP = 20;
const PAD_BOTTOM = 30;
let crashState = {
running: false,
bet: 0,
cashedOut: false,
animId: null,
startTime: 0,
rawTrajectory: [],
candles: [],
elapsed: 0,
currentValue: 1.0,
peakValue: 1.0,
history: [],
autoCashout: 0,
autoEnabled: false,
pausedAt: 0,
wasPaused: false
};
function generateRawTrajectory() {
const duration = 12 + Math.random() * 10;
const steps = Math.floor(duration / 0.05);
const trajectory = [];
let val = 1.0;
let trend = 0.002 + Math.random() * 0.003;
let volatility = 0.01;
const crashTime = duration * (0.4 + Math.random() * 0.4);
for (let i = 0; i <= steps; i++) {
const t = i * 0.05;
if (t < crashTime) {
const progress = t / crashTime;
if (progress > 0.6) trend -= 0.0001;
if (progress > 0.8) trend -= 0.0005;
trend = Math.max(0.0005, trend);
volatility = 0.008 + progress * 0.025;
const step = (Math.random() - 0.5) * volatility + trend;
val += step;
if (Math.random() < 0.05) val += (Math.random() - 0.7) * volatility * 3;
} else {
const progress = (t - crashTime) / (duration - crashTime);
const crashTrend = -0.03 - (progress * 0.05);
const step = (Math.random() - 0.5) * 0.02 + crashTrend;
val += step;
if (val < 0.55) val = 0.55 + (Math.random() * 0.02);
}
if (t < crashTime && val < 0.8) val = 0.8 + Math.random() * 0.1;
if (val > 4.5) val = 4.5;
if (isNaN(val)) val = 1.0;
trajectory.push({ t, value: val });
}
return trajectory;
}
function buildCandles(rawTraj) {
const candles = [];
let candleStartVal = rawTraj[0].value;
let candleHigh = candleStartVal;
let candleLow = candleStartVal;
let candleStartTime = 0;
for (let i = 0; i < rawTraj.length; i++) {
const pt = rawTraj[i];
candleHigh = Math.max(candleHigh, pt.value);
candleLow = Math.min(candleLow, pt.value);
if (pt.t - candleStartTime >= CANDLE_DURATION || i === rawTraj.length - 1) {
candles.push({
time: candleStartTime,
open: candleStartVal,
close: pt.value,
high: candleHigh,
low: candleLow
});
candleStartTime = pt.t;
candleStartVal = pt.value;
candleHigh = pt.value;
candleLow = pt.value;
}
}
return candles;
}
function getRawValue(t) {
const traj = crashState.rawTrajectory;
if (!traj.length) return 1.0;
if (t <= traj[0].t) return traj[0].value;
if (t >= traj[traj.length - 1].t) return traj[traj.length - 1].value;
const idx = Math.floor(t / 0.05);
if (idx >= traj.length - 1) return traj[traj.length - 1].value;
const p0 = traj[idx];
const p1 = traj[idx + 1];
const ratio = (t - p0.t) / 0.05;
return p0.value + (p1.value - p0.value) * ratio;
}
function resizeCanvas(){
const rect = canvas.parentElement.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
}
window.addEventListener('resize', () => { resizeCanvas(); drawCrashGraph(); });
function drawCrashGraph() {
const w = canvas.width;
const h = canvas.height;
ctx.clearRect(0, 0, w, h);
const plotW = w - PAD_LEFT - PAD_RIGHT;
const plotH = h - PAD_TOP - PAD_BOTTOM;
if (plotW <= 0 || plotH <= 0) return;
ctx.save();
ctx.translate(PAD_LEFT, PAD_TOP);
ctx.font = '11px monospace';
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
for (let v = 0.5; v <= 4.0; v += 0.5) {
const y = plotH - ((v - Y_MIN) / (Y_MAX - Y_MIN)) * plotH;
if (y < 0 || y > plotH) continue;
ctx.strokeStyle = 'rgba(100,120,150,0.1)';
ctx.setLineDash([3, 3]);
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(plotW, y);
ctx.stroke();
ctx.fillStyle = 'rgba(130,150,180,0.5)';
ctx.fillText(v.toFixed(1) + 'x', -5, y);
}
const y10 = plotH - ((1.0 - Y_MIN) / (Y_MAX - Y_MIN)) * plotH;
ctx.strokeStyle = 'rgba(255,200,50,0.3)';
ctx.setLineDash([5, 5]);
ctx.beginPath();
ctx.moveTo(0, y10);
ctx.lineTo(plotW, y10);
ctx.stroke();
const y05 = plotH - ((0.5 - Y_MIN) / (Y_MAX - Y_MIN)) * plotH;
ctx.strokeStyle = 'rgba(255,59,59,0.4)';
ctx.setLineDash([5, 5]);
ctx.beginPath();
ctx.moveTo(0, y05);
ctx.lineTo(plotW, y05);
ctx.stroke();
ctx.setLineDash([]);
if (!crashState.candles.length && !crashState.running) {
ctx.fillStyle = 'rgba(150,170,200,0.35)';
ctx.font = '14px "Segoe UI"';
ctx.textAlign = 'center';
ctx.fillText('Войдите в сделку, чтобы начать торги', plotW / 2, plotH / 2);
ctx.restore();
return;
}
let visibleCandles = crashState.candles.slice(-MAX_CANDLES);
if (crashState.running) {
const currentVal = getRawValue(crashState.elapsed);
const lastCandle = visibleCandles[visibleCandles.length - 1];
if (lastCandle) {
lastCandle.close = currentVal;
lastCandle.high = Math.max(lastCandle.high, currentVal);
lastCandle.low = Math.min(lastCandle.low, currentVal);
}
}
const candleW = plotW / MAX_CANDLES;
const candleSpacing = candleW * 0.2;
const actualW = candleW - candleSpacing;
const ma5Data = [];
for (let i = 0; i < visibleCandles.length; i++) {
if (i < 4) continue;
let sum = 0;
for (let j = 0; j < 5; j++) sum += visibleCandles[i - j].close;
ma5Data.push({ idx: i, val: sum / 5 });
}
const ma15Data = [];
for (let i = 0; i < visibleCandles.length; i++) {
if (i < 14) continue;
let sum = 0;
for (let j = 0; j < 15; j++) sum += visibleCandles[i - j].close;
ma15Data.push({ idx: i, val: sum / 15 });
}
for (let i = 0; i < visibleCandles.length; i++) {
const c = visibleCandles[i];
const x = i * candleW + candleSpacing / 2;
const yOpen = plotH - ((c.open - Y_MIN) / (Y_MAX - Y_MIN)) * plotH;
const yClose = plotH - ((c.close - Y_MIN) / (Y_MAX - Y_MIN)) * plotH;
const yHigh = plotH - ((c.high - Y_MIN) / (Y_MAX - Y_MIN)) * plotH;
const yLow = plotH - ((c.low - Y_MIN) / (Y_MAX - Y_MIN)) * plotH;
const isGreen = c.close >= c.open;
const color = isGreen ? '#26a69a' : '#ef5350';
ctx.strokeStyle = color;
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(x + actualW / 2, yHigh);
ctx.lineTo(x + actualW / 2, yLow);
ctx.stroke();
ctx.fillStyle = color;
const bodyTop = Math.min(yOpen, yClose);
const bodyHeight = Math.max(2, Math.abs(yOpen - yClose));
ctx.fillRect(x, bodyTop, actualW, bodyHeight);
}
if (ma5Data.length > 1) {
ctx.beginPath();
ctx.strokeStyle = '#ffd166';
ctx.lineWidth = 2;
ctx.shadowBlur = 5;
ctx.shadowColor = '#ffd166';
for (let i = 0; i < ma5Data.length; i++) {
const pt = ma5Data[i];
const x = pt.idx * candleW + actualW / 2 + candleSpacing / 2;
const y = plotH - ((pt.val - Y_MIN) / (Y_MAX - Y_MIN)) * plotH;
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
}
ctx.stroke();
ctx.shadowBlur = 0;
}
if (ma15Data.length > 1) {
ctx.beginPath();
ctx.strokeStyle = '#ff5c8a';
ctx.lineWidth = 2;
ctx.shadowBlur = 5;
ctx.shadowColor = '#ff5c8a';
for (let i = 0; i < ma15Data.length; i++) {
const pt = ma15Data[i];
const x = pt.idx * candleW + actualW / 2 + candleSpacing / 2;
const y = plotH - ((pt.val - Y_MIN) / (Y_MAX - Y_MIN)) * plotH;
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
}
ctx.stroke();
ctx.shadowBlur = 0;
}
if (crashState.running) {
const curVal = getRawValue(crashState.elapsed);
const curY = plotH - ((curVal - Y_MIN) / (Y_MAX - Y_MIN)) * plotH;
const isCrashed = curVal <= 0.55;
ctx.strokeStyle = isCrashed ? '#ff3b3b' : 'rgba(255,255,255,0.4)';
ctx.lineWidth = 1;
ctx.setLineDash([5, 5]);
ctx.beginPath();
ctx.moveTo(0, curY);
ctx.lineTo(plotW, curY);
ctx.stroke();
ctx.setLineDash([]);
const labelText = curVal.toFixed(2) + 'x';
ctx.font = 'bold 12px monospace';
const textWidth = ctx.measureText(labelText).width;
const boxW = textWidth + 12;
const boxH = 20;
ctx.fillStyle = isCrashed ? '#ff3b3b' : '#26a69a';
ctx.fillRect(plotW + 4, curY - boxH / 2, boxW, boxH);
ctx.fillStyle = '#fff';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText(labelText, plotW + 10, curY);
}
ctx.restore();
}
function updateCrashUI() {
const val = crashState.currentValue;
crashMultiplierEl.textContent = val.toFixed(2) + 'x';
if (val <= 0.55) {
crashMultiplierEl.style.color = '#ff3b3b';
crashMultiplierEl.classList.add('crashed');
} else if (val >= 1.0) {
crashMultiplierEl.style.color = '#26a69a';
crashMultiplierEl.classList.remove('crashed');
} else {
crashMultiplierEl.style.color = '#ffd166';
crashMultiplierEl.classList.remove('crashed');
}
if (crashState.bet > 0 && crashState.running && !crashState.cashedOut) {
const payout = Math.round(crashState.bet * val);
const diff = payout - crashState.bet;
if (diff >= 0) {
crashPnlEl.textContent = '📈 +' + fmt(diff) + ' ₽';
crashPnlEl.className = 'crash-pnl positive';
} else {
crashPnlEl.textContent = '📉 ' + fmt(diff) + ' ₽';
crashPnlEl.className = 'crash-pnl negative';
}
} else if (crashState.bet > 0 && val <= 0.55) {
crashPnlEl.textContent = '💀 -' + fmt(crashState.bet) + ' ₽';
crashPnlEl.className = 'crash-pnl negative';
} else {
crashPnlEl.textContent = '—';
crashPnlEl.className = 'crash-pnl';
}
if (crashState.running && !crashState.cashedOut) {
const payout = Math.round(crashState.bet * val);
const diff = payout - crashState.bet;
const sign = diff >= 0 ? '+' : '';
crashCashoutBtn.textContent = 'Забрать ' + fmt(payout) + ' ₽ (' + sign + fmt(diff) + ')';
if (val >= 1.0) {
crashCashoutBtn.classList.remove('loss-mode');
} else {
crashCashoutBtn.classList.add('loss-mode');
}
}
}
function updateCrashLoop() {
if (!crashState.running) return;
if (crashState.wasPaused) {
crashState.startTime += performance.now() - crashState.pausedAt;
crashState.wasPaused = false;
}
const elapsed = (performance.now() - crashState.startTime) / 1000;
crashState.elapsed = elapsed;
const val = getRawValue(elapsed);
if (isNaN(val)) return;
crashState.currentValue = val;
if (val <= 0.55) {
crashState.currentValue = 0.50;
endCrash(false);
return;
}
if (crashState.autoCashout > 0 && !crashState.cashedOut && val >= crashState.autoCashout) {
crashState.cashedOut = true;
endCrash(true);
return;
}
const candleTime = Math.floor(elapsed / CANDLE_DURATION);
while (crashState.candles.length <= candleTime) {
crashState.candles.push({
time: crashState.candles.length * CANDLE_DURATION,
open: val,
close: val,
high: val,
low: val
});
}
if (crashState.candles.length > 0) {
const current = crashState.candles[crashState.candles.length - 1];
current.close = val;
current.high = Math.max(current.high, val);
current.low = Math.min(current.low, val);
}
updateCrashUI();
drawCrashGraph();
crashState.animId = requestAnimationFrame(updateCrashLoop);
}
function pauseCrashForVisibility() {
if (crashState.running) {
crashState.pausedAt = performance.now();
crashState.wasPaused = true;
cancelAnimationFrame(crashState.animId);
}
}
crashPlayBtn.addEventListener('click', () => {
ensureAudio();
if (crashState.running) return;
const bet = parseInt(crashBetInput.value);
if (isNaN(bet) || bet < 10) { toast('Минимальная ставка 10 ₽'); return; }
if (state.balance < bet) { toast('Недостаточно средств!'); return; }
state.balance -= bet;
updateWallet();
crashState.rawTrajectory = generateRawTrajectory();
crashState.candles = [];
crashState.bet = bet;
crashState.cashedOut = false;
crashState.elapsed = 0;
crashState.currentValue = 1.0;
crashState.startTime = performance.now();
crashState.running = true;
crashState.wasPaused = false;
if (crashState.autoEnabled) {
const autoVal = parseFloat(crashAutoInput.value);
crashState.autoCashout = (!isNaN(autoVal) && autoVal >= 1.05) ? autoVal : 0;
} else {
crashState.autoCashout = 0;
}
crashMultiplierEl.classList.remove('crashed');
crashMultiplierEl.textContent = '1.00x';
crashPlayBtn.style.display = 'none';
crashCashoutBtn.style.display = 'block';
crashCashoutBtn.textContent = 'Забрать ' + fmt(bet) + ' ₽';
blip(440, 0.08, 0.05, 'sine');
updateCrashUI();
crashState.animId = requestAnimationFrame(updateCrashLoop);
});
function endCrash(win, silent) {
crashState.running = false;
cancelAnimationFrame(crashState.animId);
if (win) {
const payout = Math.round(crashState.bet * crashState.currentValue);
state.balance += payout;
updateWallet();
addXP(15);
save();
const profit = payout - crashState.bet;
const sign = profit >= 0 ? '+' : '';
const icon = profit >= 0 ? '📈' : '📉';
showNotification(profit >= 0 ? 'success' : 'error', icon + ' Сделка закрыта',
'x' + crashState.currentValue.toFixed(2) + ' — ' + sign + fmt(profit) + ' ₽');
blip(1200, 0.2, 0.08, 'sine');
} else {
crashState.currentValue = 0.50;
crashMultiplierEl.textContent = '0.50x';
crashMultiplierEl.classList.add('crashed');
crashMultiplierEl.style.color = '#ff3b3b';
if (!silent) {
showNotification('error', '💥 Обвал!', 'Потеряно ' + fmt(crashState.bet) + ' ₽');
loseSound();
}
}
crashState.history.unshift(crashState.peakValue || crashState.currentValue);
if (crashState.history.length > 10) crashState.history.pop();
renderCrashHistory();
crashPlayBtn.style.display = 'block';
crashCashoutBtn.style.display = 'none';
crashPnlEl.textContent = '—';
crashPnlEl.className = 'crash-pnl';
updateCrashUI();
drawCrashGraph();
}
crashCashoutBtn.addEventListener('click', () => {
if (!crashState.running || crashState.cashedOut) return;
crashState.cashedOut = true;
endCrash(true);
});
function renderCrashHistory() {
crashHistoryEl.innerHTML = crashState.history.map(v => {
const cls = v >= 1.5 ? 'win' : 'loss';
return `<div class="crash-hist-item ${cls}">${v.toFixed(2)}x</div>`;
}).join('');
}
crashAutoBtn.addEventListener('click', () => {
crashState.autoEnabled = !crashState.autoEnabled;
crashAutoBtn.textContent = crashState.autoEnabled ? 'Вкл' : 'Выкл';
crashAutoBtn.style.background = crashState.autoEnabled ? '#39ff14' : '';
crashAutoBtn.style.color = crashState.autoEnabled ? '#000' : '';
toast(crashState.autoEnabled ? 'Авто-выход включён (×' + crashAutoInput.value + ')' : 'Авто-выход выключен');
});
const fakeCrashBets = ['100 ₽', '500 ₽', '1000 ₽', '50 ₽', '5000 ₽', '250 ₽', '750 ₽'];
const fakeCrashNames = ['xX_Sniper_Xx', 'CyberWolf', 'NeonRider', 'Vortex', 'B1t', 'ToxicBoy', 'MoonTrader', 'HODL_King'];
function updateCrashPlayers() {
let html = '';
const count = Math.floor(Math.random() * 4) + 4;
for (let i = 0; i < count; i++) {
const name = fakeCrashNames[Math.floor(Math.random() * fakeCrashNames.length)];
const bet = fakeCrashBets[Math.floor(Math.random() * fakeCrashBets.length)];
let multHtml = '<span style="color:var(--muted)">ожидание...</span>';
if (crashState.running) {
const r = Math.random();
if (r > 0.5) {
const m = (Math.random() * Math.min(crashState.currentValue, 3) + 1).toFixed(2);
if (parseFloat(m) < crashState.currentValue) multHtml = `<span class="player-mult win">x${m}</span>`;
else multHtml = `<span class="player-mult">держит</span>`;
} else {
multHtml = `<span class="player-mult">держит</span>`;
}
} else if (crashState.currentValue <= 0.55) {
if (Math.random() > 0.3) multHtml = `<span class="player-mult loss">Обвал</span>`;
else multHtml = `<span class="player-mult win">забрал</span>`;
}
html += `<div class="player-row"><span class="player-name">${name}</span><span class="player-bet">${bet}</span>${multHtml}</div>`;
}
crashPlayersList.innerHTML = html;
}
setInterval(updateCrashPlayers, 2200);
setTimeout(() => { resizeCanvas(); drawCrashGraph(); }, 150);
/* =====================================================================
18. LIVE FEED
===================================================================== */
const liveFeed = document.getElementById('live-feed');
const fakeNames = ['xX_Sniper_Xx', 'CyberWolf', 'NeonRider', 'ShadowBlade', 'Vortex', 'AWP_God', 'B1t', 's1mple_fan', 'ToxicBoy', 'KiberKot', 'DragonSlayer', 'RustyNail'];
const fakeItems = ['AWP | Драконье пламя', 'AK-47 | Огнедышащий', 'Керамбит | Кровавая луна', 'M4A4 | Квантовый разлом', 'Бабочка | Огненный градиент', 'USP-S | Галактика', 'Desert Eagle | Скипетр', 'Кунай | Космический градиент'];
function addLiveDrop() {
if (liveFeed.children.length > 3) liveFeed.removeChild(liveFeed.lastChild);
const name = fakeNames[Math.floor(Math.random() * fakeNames.length)];
const item = fakeItems[Math.floor(Math.random() * fakeItems.length)];
const price = Math.floor(Math.random() * 90000) + 10000;
const el = document.createElement('div'); el.className = 'feed-item';
el.innerHTML = `<span class="name">${name}</span> выбил <span class="item" style="color:#ffd166">${item}</span> за ${fmt(price)} ₽`;
liveFeed.insertBefore(el, liveFeed.firstChild);
}
setInterval(addLiveDrop, 8000); addLiveDrop();
/* =====================================================================
19. SETTINGS
===================================================================== */
const settingsModal = $('settingsModal');
const settingsBtn = $('settingsBtn');
const settingsClose = $('settingsClose');
const volMusicInput = $('volMusic');
const volSfxInput = $('volSfx');
const animSpeedInput = $('animSpeed');
const themeToggleBtn = $('themeToggle');
function applySettings() {
volMusicInput.value = state.settings.volMusic;
volSfxInput.value = state.settings.volSfx;
animSpeedInput.value = state.settings.animSpeed;
if (state.settings.theme === 'light') {
document.documentElement.setAttribute('data-theme', 'light');
themeToggleBtn.textContent = 'Светлая';
} else {
document.documentElement.removeAttribute('data-theme');
themeToggleBtn.textContent = 'Тёмная';
}
}
settingsBtn.addEventListener('click', () => settingsModal.classList.add('open'));
settingsClose.addEventListener('click', () => settingsModal.classList.remove('open'));
settingsModal.addEventListener('click', (e) => { if (e.target === settingsModal) settingsModal.classList.remove('open'); });
volMusicInput.addEventListener('input', (e) => { state.settings.volMusic = parseFloat(e.target.value); save(); });
volSfxInput.addEventListener('input', (e) => { state.settings.volSfx = parseFloat(e.target.value); save(); });
animSpeedInput.addEventListener('input', (e) => { state.settings.animSpeed = parseFloat(e.target.value); save(); });
themeToggleBtn.addEventListener('click', () => { state.settings.theme = state.settings.theme === 'dark' ? 'light' : 'dark'; applySettings(); save(); });
/* =====================================================================
20. EVENTS
===================================================================== */
caseGrid.addEventListener('click', e => {
const card = e.target.closest('.case-card'); if (!card) return;
const idx = parseInt(card.dataset.idx, 10); const c = CASES[idx]; if (!c) return;
currentCase = c; openMultiplier = 1;
document.querySelectorAll('.multi-btn').forEach(b => b.classList.toggle('active', b.dataset.multi === '1'));
openTitle.textContent = c.name;
openSub.textContent = 'Цена открытия: ' + fmt(c.price) + ' ₽ · ' + c.items.length + ' предметов';
openBtn.textContent = 'Открыть за ' + fmt(c.price) + ' ₽';
resultEl.classList.remove('show');
renderContents(c); showView('open'); renderOpenCaseStats(c); startIdleRoulette(c); ensureAudio();
});
document.querySelectorAll('.multi-btn').forEach(btn => {
btn.addEventListener('click', () => {
if (!currentCase) return;
document.querySelectorAll('.multi-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
openMultiplier = parseInt(btn.dataset.multi, 10);
const total = currentCase.price * openMultiplier;
openBtn.textContent = 'Открыть ×' + openMultiplier + ' за ' + fmt(total) + ' ₽';
});
});
backBtn.addEventListener('click', () => showView('cases'));
openBtn.addEventListener('click', () => doOpen(false));
fastBtn.addEventListener('click', () => doOpen(true));
invGrid.addEventListener('click', e => {
const btn = e.target.closest('.sell-btn'); if (!btn) return;
const uid = parseInt(btn.dataset.uid, 10); const act = btn.dataset.act;
if (act === 'sell') sellItem(uid);
else if (act === 'upgrade') { selectBet(uid); showView('upgrade'); }
});
$('sortInv').addEventListener('change', (e) => { state.invSort = e.target.value; renderInventory(); });
$('filterInv').addEventListener('change', (e) => { state.invFilter = e.target.value; renderInventory(); });
$('sellAllBtn').addEventListener('click', () => {
if (!state.inventory.length) { toast('Инвентарь уже пуст'); return; }
const total = state.inventory.reduce((s, i) => s + i.price, 0); const n = state.inventory.length;
state.balance += total; state.inventory = [];
state.stats.sold += n; state.stats.earned += total;
addXP(n * XP_VALUES.sale);
blip(760, 0.14, 0.06, 'sine');
toast('Продано всё: +' + fmt(total) + ' ₽');
renderUpgrader(); commit();
});
upgInv.addEventListener('click', e => {
const el = e.target.closest('.upg-inv-item'); if (!el || upgState.spinning) return;
selectBet(parseInt(el.dataset.uid, 10));
});
upgTargets.addEventListener('click', e => {
const el = e.target.closest('.upg-target'); if (!el || upgState.spinning) return;
selectTarget(parseInt(el.dataset.uid, 10));
});
upgConfirm.addEventListener('click', doUpgrade);
upgReset.addEventListener('click', resetUpgrade);
questList.addEventListener('click', e => {
const btn = e.target.closest('button[data-q]'); if (!btn) return;
claimQuest(parseInt(btn.dataset.q, 10));
});
document.getElementById('contractSlots').addEventListener('click', e => {
const removeBtn = e.target.closest('[data-remove]');
if (removeBtn) { const idx = parseInt(removeBtn.dataset.remove, 10); state.contractSel.splice(idx, 1); renderContract(); }
});
document.getElementById('contractInv').addEventListener('click', e => {
const el = e.target.closest('.contract-inv-item'); if (!el) return;
toggleContractItem(parseInt(el.dataset.uid, 10));
});
document.getElementById('contractBtn').addEventListener('click', createContract);
const sndBtn = $('sndBtn');
sndBtn.addEventListener('click', function () { soundOn = !soundOn; this.textContent = soundOn ? '🔊' : '🔇'; if (soundOn) { ensureAudio(); blip(900, 0.07, 0.05); } save(); });
const musicBtn = $('musicBtn');
musicBtn.addEventListener('click', toggleMusic);
document.getElementById('musicSelect').addEventListener('change', () => {
if (musicOn) { clearTimeout(musicTimeout); currentNoteIndex = 0; playNextNote(); }
});
document.body.addEventListener('mouseover', e => {
const target = e.target.closest('.case-card, .btn, .tab'); if (!target) return;
const related = e.relatedTarget; if (related && target.contains(related)) return;
if (soundOn && audioInitialized) blip(1200, 0.02, 0.01, 'sine');
});
document.addEventListener('keydown', e => {
if (e.code === 'Space' && $('view-open').classList.contains('active')) { e.preventDefault(); doOpen(false); }
});
/* =====================================================================
21. INIT
===================================================================== */
load();
renderCaseGrid();
updateWallet();
updateLevelBar();
renderInventory();
renderUpgrader();
renderClicker();
renderProfile();
renderAchievements();
ensureDailyQuests();
renderQuests();
applySettings();
sndBtn.textContent = soundOn ? '🔊' : '🔇';
musicBtn.textContent = musicOn ? '🔇' : '🎵';
if (soundOn || musicOn) {
showAudioPrompt();
document.addEventListener('click', initAudioOnGesture);
document.addEventListener('keydown', initAudioOnGesture);
document.addEventListener('touchstart', initAudioOnGesture);
} else {
audioInitialized = true;
}
const audioPromptEl = document.getElementById('audio-prompt');
if (audioPromptEl) {
audioPromptEl.addEventListener('click', (e) => {
e.stopPropagation();
initAudioOnGesture();
});
}
tickQuestTimer();
setInterval(tickQuestTimer, 1000);
updateCrashPlayers();
checkAchievements();
</script>
</body>
</html>Game Source: NEON CASES — кейсы, апгрейдер, кликер
Creator: LuckyBuilder79
Libraries: none
Complexity: complex (2967 lines, 209.2 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: neon-cases-luckybuilder79" to link back to the original. Then publish at arcadelab.ai/publish.