chess / chess.html
SulikProMax's picture
Create chess.html
5ae75fd verified
Raw
History Blame Contribute Delete
86.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>NeoChess</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.css">
<script src="https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.3/chess.min.js"></script>
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--bg-body: #050505;
--bg-main: #0B0C10;
--bg-surface: rgba(25, 28, 36, 0.65);
--bg-surface-light: rgba(36, 40, 51, 0.85);
--green: #10B981;
--green-hover: #34D399;
--green-dark: #047857;
--green-glow: rgba(16, 185, 129, 0.4);
--text-main: #F3F4F6;
--text-muted: #9CA3AF;
--text-light: #D1D5DB;
--border: rgba(255, 255, 255, 0.08);
--border-highlight: rgba(255, 255, 255, 0.15);
--danger: #EF4444;
--danger-glow: rgba(239, 68, 68, 0.4);
--warning: #F59E0B;
--info: #3B82F6;
--brilliant: #1baca6;
--great: #5c8bb0;
--best: #81b64c;
--excel: #96bc4b;
--good: #789936;
--inacc: #f5c04e;
--mistake: #e58f2a;
--blunder: #ca3431;
--glass-blur: blur(16px);
}
* { margin: 0; padding: 0; box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, Helvetica, Arial, sans-serif; -webkit-tap-highlight-color: transparent; }
body {
background-color: var(--bg-body);
background-image: radial-gradient(circle at 50% 0%, rgba(16, 185, 129, 0.05) 0%, transparent 50%);
color: var(--text-main);
height: 100dvh;
overflow: hidden;
display: flex;
justify-content: center;
}
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--bg-surface-light); border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
.app-container {
width: 100%;
max-width: 480px;
height: 100dvh;
background-color: var(--bg-main);
display: flex;
flex-direction: column;
position: relative;
box-shadow: 0 0 50px rgba(0,0,0,0.8);
overflow: hidden;
border-left: 1px solid var(--border);
border-right: 1px solid var(--border);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background: var(--bg-surface);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
z-index: 10;
}
.header-logo { color: var(--text-main); font-size: 22px; font-weight: 900; display: flex; align-items: center; gap: 10px; letter-spacing: -0.5px; }
.header-logo i { color: var(--green); font-size: 24px; filter: drop-shadow(0 0 8px var(--green-glow)); }
.auth-btns button { background: none; border: none; color: var(--text-light); font-size: 15px; font-weight: 700; cursor: pointer; padding: 8px 12px; transition: color 0.2s; }
.auth-btns button:hover { color: white; }
.auth-btns .btn-signup {
background: linear-gradient(135deg, var(--green), var(--green-dark));
color: white;
border-radius: 10px;
margin-left: 8px;
padding: 8px 16px;
box-shadow: 0 4px 15px var(--green-glow), inset 0 1px 1px rgba(255,255,255,0.2);
transition: all 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.auth-btns .btn-signup:active { transform: scale(0.95); box-shadow: 0 2px 5px var(--green-glow); }
.view { display: none; flex: 1; overflow-y: auto; padding-bottom: 90px; }
.view.active { display: block; animation: fadeinView 0.3s ease-out; }
@keyframes fadeinView { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
#view-game.active { display: flex; flex-direction: column; padding-bottom: 0; overflow: hidden;}
.bottom-nav {
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
height: 70px;
background: rgba(25, 28, 36, 0.85);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
display: flex;
justify-content: space-around;
align-items: center;
border: 1px solid var(--border-highlight);
border-radius: 24px;
box-shadow: 0 15px 35px rgba(0,0,0,0.5);
z-index: 100;
}
.nav-item {
display: flex;
flex-direction: column;
align-items: center;
color: var(--text-muted);
font-size: 11px;
font-weight: 700;
cursor: pointer;
background: none;
border: none;
width: 20%;
gap: 6px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.nav-item i { font-size: 22px; transition: all 0.3s ease; }
.nav-item.active { color: var(--green); transform: translateY(-4px); }
.nav-item.active i { filter: drop-shadow(0 0 10px var(--green-glow)); transform: scale(1.1); }
.btn-smart {
display: flex;
width: calc(100% - 40px);
margin: 15px auto;
background: linear-gradient(135deg, var(--green), var(--green-dark));
color: white;
text-align: center;
padding: 16px;
border-radius: 14px;
font-size: 18px;
font-weight: 800;
border: 1px solid rgba(255,255,255,0.1);
cursor: pointer;
box-shadow: 0 8px 20px var(--green-glow), inset 0 1px 1px rgba(255,255,255,0.2);
transition: all 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
justify-content: center;
align-items: center;
gap: 12px;
letter-spacing: 0.5px;
}
.btn-smart:active { transform: scale(0.97); box-shadow: 0 4px 10px var(--green-glow); }
.gray-btn {
background: linear-gradient(180deg, var(--bg-surface-light), rgba(25, 28, 36, 1));
box-shadow: 0 4px 15px rgba(0,0,0,0.3), inset 0 1px 1px rgba(255,255,255,0.05);
color: white;
padding: 16px;
border-radius: 14px;
border: 1px solid var(--border);
font-size: 16px;
font-weight: 700;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
justify-content: center;
width: calc(100% - 40px);
margin: 10px auto;
transition: all 0.2s;
}
.gray-btn:active { transform: scale(0.97); }
.home-menu { padding: 20px; }
.menu-item {
display: flex;
align-items: center;
padding: 14px;
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: 16px;
margin-bottom: 15px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.menu-item:hover, .menu-item:active {
background: var(--bg-surface-light);
transform: translateY(-2px);
border-color: var(--border-highlight);
box-shadow: 0 8px 25px rgba(0,0,0,0.4);
}
.menu-icon-box {
width: 60px;
height: 60px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 18px;
background: rgba(255,255,255,0.05);
border: 1px solid var(--border);
box-shadow: inset 0 2px 10px rgba(0,0,0,0.2);
}
.menu-icon-box i { font-size: 28px; color: var(--text-light); }
.menu-item:nth-child(1) .menu-icon-box i { color: var(--green); filter: drop-shadow(0 0 8px var(--green-glow)); }
.menu-item:nth-child(2) .menu-icon-box i { color: var(--warning); filter: drop-shadow(0 0 8px rgba(245, 158, 11, 0.4)); }
.menu-item:nth-child(3) .menu-icon-box i { color: var(--info); filter: drop-shadow(0 0 8px rgba(59, 130, 246, 0.4)); }
.menu-text h3 { font-size: 18px; font-weight: 800; margin-bottom: 4px; color: var(--text-main); letter-spacing: -0.3px;}
.menu-text p { font-size: 14px; color: var(--text-muted); font-weight: 500;}
.profile-header { padding: 25px 20px; display: flex; flex-direction: column; gap: 16px; background: linear-gradient(180deg, var(--bg-surface), var(--bg-main)); border-bottom: 1px solid var(--border);}
.prof-top { display: flex; align-items: center; gap: 20px; position: relative;}
.prof-top img { width: 85px; height: 85px; border-radius: 24px; object-fit: cover; background: #fff; border: 2px solid var(--border-highlight); box-shadow: 0 10px 20px rgba(0,0,0,0.5); }
.status-dot { position: absolute; bottom: -4px; left: 65px; width: 22px; height: 22px; border-radius: 50%; border: 4px solid var(--bg-main); background: var(--text-muted); display:flex; align-items:center; justify-content:center; box-shadow: 0 0 10px rgba(0,0,0,0.5); transition: background 0.3s;}
.status-dot.online { background: var(--green); box-shadow: 0 0 15px var(--green-glow); animation: pulse-green 2s infinite;}
@keyframes pulse-green { 0% { box-shadow: 0 0 0 0 var(--green-glow); } 70% { box-shadow: 0 0 0 10px rgba(16,185,129,0); } 100% { box-shadow: 0 0 0 0 rgba(16,185,129,0); } }
.prof-info { flex: 1; }
.prof-info h2 { font-size: 24px; display: flex; align-items: center; gap: 8px; margin-bottom: 6px; font-weight: 900; letter-spacing: -0.5px;}
.prof-info p { color: var(--text-muted); font-size: 14px; font-weight: 500;}
.prof-bio-text { font-size: 15px; color: var(--text-light); margin-top: 5px; line-height: 1.5; padding: 14px; background: var(--bg-surface-light); border-radius: 12px; border-left: 4px solid var(--green); box-shadow: inset 0 2px 10px rgba(0,0,0,0.2);}
.prof-actions { display: flex; gap: 12px; margin-top: 10px; }
.btn-add-friend { flex: 1; padding: 14px; background: var(--bg-surface-light); color: white; border: 1px solid var(--border); border-radius: 12px; font-weight: 700; font-size: 15px; cursor: pointer; display: flex; justify-content: center; align-items: center; gap: 8px; transition: all 0.2s;}
.btn-add-friend:active { transform: scale(0.96); }
.btn-challenge { flex: 1; padding: 14px; background: linear-gradient(135deg, var(--green), var(--green-dark)); color: white; border: none; border-radius: 12px; font-weight: 800; font-size: 15px; cursor: pointer; display: flex; justify-content: center; align-items: center; gap: 8px; box-shadow: 0 4px 15px var(--green-glow); transition: all 0.2s;}
.btn-challenge:active { transform: scale(0.96); box-shadow: 0 2px 8px var(--green-glow); }
.stats-section { padding: 20px; }
.stats-section-title { font-size: 18px; font-weight: 900; margin-bottom: 16px; display: flex; justify-content: space-between; color: var(--text-main); align-items: center;}
.stats-section-title span { font-size: 14px; font-weight: 600; color: var(--green); cursor: pointer; background: rgba(16, 185, 129, 0.1); padding: 4px 10px; border-radius: 20px;}
.stats-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
.stat-box { background: var(--bg-surface); border: 1px solid var(--border); padding: 18px 10px; border-radius: 16px; text-align: center; box-shadow: 0 4px 12px rgba(0,0,0,0.2); transition: transform 0.2s;}
.stat-box:hover { transform: translateY(-3px); border-color: var(--border-highlight); }
.stat-box i { font-size: 24px; margin-bottom: 10px; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5));}
.stat-box h4 { font-size: 20px; font-weight: 900; letter-spacing: -0.5px;}
.stat-box p { font-size: 13px; color: var(--text-muted); font-weight: 600; margin-top: 4px; text-transform: uppercase; letter-spacing: 0.5px;}
.game-history-list { display: flex; flex-direction: column; background: var(--bg-surface); border: 1px solid var(--border); border-radius: 16px; overflow: hidden; box-shadow: 0 8px 20px rgba(0,0,0,0.3);}
.history-card { display: flex; justify-content: space-between; align-items: center; padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer; transition: background 0.2s;}
.history-card:last-child { border-bottom: none; }
.history-card:hover, .history-card:active { background: var(--bg-surface-light); }
.hist-left { display: flex; align-items: center; gap: 14px; flex: 1; }
.hist-icon-wrap { font-size: 20px; display: flex; align-items: center; justify-content: center; width: 28px; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));}
.hist-avatar { width: 38px; height: 38px; border-radius: 10px; object-fit: cover; border: 1px solid var(--border-highlight);}
.hist-details { display: flex; flex-direction: column; }
.hist-details strong { font-size: 16px; color: var(--text-main); font-weight: 700;}
.hist-details span { font-size: 12px; color: var(--text-muted); margin-top: 4px; font-weight: 500;}
.hist-right { display: flex; align-items: center; gap: 14px; }
.hist-symbol { font-weight: 900; font-size: 16px; width: 24px; height: 24px; border-radius: 6px; display: flex; align-items: center; justify-content: center; box-shadow: inset 0 1px 3px rgba(0,0,0,0.3);}
.hist-symbol.win { background: rgba(16, 185, 129, 0.15); color: var(--green); border: 1px solid rgba(16, 185, 129, 0.3);}
.hist-symbol.loss { background: rgba(239, 68, 68, 0.15); color: var(--danger); border: 1px solid rgba(239, 68, 68, 0.3);}
.hist-symbol.draw { background: rgba(156, 163, 175, 0.15); color: var(--text-muted); border: 1px solid rgba(156, 163, 175, 0.3);}
.hist-accuracy { font-size: 14px; font-weight: 800; width: 50px; text-align: center; padding: 6px 0; border-radius: 8px; border: 1px solid rgba(16, 185, 129, 0.2); box-shadow: inset 0 2px 5px rgba(0,0,0,0.2);}
.more-list { list-style: none; padding: 10px 20px; }
.more-list li { padding: 16px 20px; background: var(--bg-surface); border: 1px solid var(--border); border-radius: 14px; margin-bottom: 10px; font-size: 16px; font-weight: 700; display: flex; align-items: center; gap: 16px; cursor: pointer; transition: all 0.2s; box-shadow: 0 4px 10px rgba(0,0,0,0.2);}
.more-list li:hover, .more-list li:active { background: var(--bg-surface-light); transform: translateY(-2px); border-color: var(--border-highlight);}
.more-list li i { width: 28px; text-align: center; font-size: 20px; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));}
.leaderboard-tabs { display: flex; padding: 10px 20px; background: var(--bg-main); gap: 10px; }
.leaderboard-tabs button { flex: 1; padding: 12px; background: var(--bg-surface); border: 1px solid var(--border); border-radius: 12px; color: var(--text-muted); font-weight: 800; font-size: 15px; cursor: pointer; transition: all 0.2s; box-shadow: 0 2px 5px rgba(0,0,0,0.2);}
.leaderboard-tabs button.active { background: var(--green); color: white; border-color: var(--green-dark); box-shadow: 0 4px 15px var(--green-glow); }
.game-top, .game-bottom { display: flex; justify-content: space-between; align-items: center; padding: 14px 20px; background: var(--bg-main); flex-shrink: 0; z-index: 10;}
.player-tag { display: flex; align-items: center; gap: 12px; cursor: pointer; flex: 1; overflow: hidden; background: var(--bg-surface); padding: 8px 12px; border-radius: 12px; border: 1px solid var(--border); box-shadow: 0 4px 12px rgba(0,0,0,0.3);}
.player-tag img { width: 44px; height: 44px; border-radius: 10px; background: #fff; object-fit: cover; border: 1px solid var(--border-highlight);}
.player-tag-info { display: flex; flex-direction: column; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;}
.player-tag-info strong { font-size: 16px; font-weight: 800; color: var(--text-main); display: flex; align-items: center; gap: 6px; overflow: hidden; text-overflow: ellipsis;}
.player-tag-info span { font-size: 13px; color: var(--text-muted); font-weight: 600; display: flex; gap: 6px; margin-top: 3px;}
.clock {
background: linear-gradient(180deg, #2A2D35, #1E2026);
padding: 12px 16px;
font-family: "SF Mono", "Roboto Mono", monospace;
font-size: 20px;
font-weight: 900;
border-radius: 12px;
color: white;
border: 1px solid rgba(255,255,255,0.1);
min-width: 90px;
text-align: center;
margin-left: 12px;
box-shadow: inset 0 2px 10px rgba(0,0,0,0.8), 0 4px 10px rgba(0,0,0,0.3);
letter-spacing: 1px;
transition: all 0.3s;
}
.clock.low {
background: linear-gradient(180deg, rgba(239, 68, 68, 0.3), rgba(153, 27, 27, 0.8));
color: #FECACA;
border-color: var(--danger);
box-shadow: inset 0 2px 10px rgba(0,0,0,0.8), 0 0 15px var(--danger-glow);
animation: pulse-danger 1s infinite;
}
@keyframes pulse-danger { 0% { box-shadow: inset 0 2px 10px rgba(0,0,0,0.8), 0 0 5px var(--danger-glow); } 50% { box-shadow: inset 0 2px 10px rgba(0,0,0,0.8), 0 0 20px var(--danger); } 100% { box-shadow: inset 0 2px 10px rgba(0,0,0,0.8), 0 0 5px var(--danger-glow); } }
#board { width: 100%; background: var(--bg-main); aspect-ratio: 1/1; position: relative; flex-shrink: 0; box-shadow: 0 0 30px rgba(0,0,0,0.8); z-index: 5;}
.game-actions-bar { display: flex; justify-content: space-around; align-items: center; padding: 15px 10px; background: var(--bg-surface); flex: 1; border-top: 1px solid var(--border); backdrop-filter: blur(10px);}
.action-icon-col { display: flex; flex-direction: column; align-items: center; gap: 6px; color: var(--text-muted); cursor: pointer; font-size: 12px; font-weight: 800; transition: all 0.2s;}
.action-icon-col:hover { color: var(--text-main); transform: translateY(-2px);}
.action-icon-col i { font-size: 22px; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));}
.action-icon-col.active-star i { color: white; background: linear-gradient(135deg, var(--green), var(--green-dark)); padding: 12px; border-radius: 12px; box-shadow: 0 4px 15px var(--green-glow); margin-top: -10px; font-size: 20px;}
.chat-overlay { position: absolute; bottom: 0; left: 0; width: 100%; height: 50vh; background: rgba(20, 22, 28, 0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); z-index: 1000; border-top-left-radius: 24px; border-top-right-radius: 24px; display: flex; flex-direction: column; box-shadow: 0 -10px 40px rgba(0,0,0,0.8); border-top: 1px solid var(--border-highlight); transform: translateY(100%); transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);}
.chat-overlay.open { transform: translateY(0); }
.chat-header { padding: 16px 20px; display: flex; justify-content: space-between; align-items: center; font-weight: 900; font-size: 16px; border-bottom: 1px solid var(--border);}
.chat-messages { flex: 1; padding: 20px; overflow-y: auto; font-size: 15px; display: flex; flex-direction: column; gap: 12px;}
.chat-input-row { display: flex; padding: 15px 20px; background: rgba(0,0,0,0.2); gap: 12px; border-top: 1px solid var(--border);}
.chat-input-row input { flex: 1; padding: 14px 20px; border-radius: 24px; border: 1px solid var(--border); background: var(--bg-surface); color: white; outline: none; font-size: 15px; box-shadow: inset 0 2px 6px rgba(0,0,0,0.3); transition: border 0.2s;}
.chat-input-row input:focus { border-color: var(--green); }
.chat-input-row button { background: linear-gradient(135deg, var(--green), var(--green-dark)); border: none; color: white; width: 46px; height: 46px; border-radius: 50%; cursor: pointer; flex-shrink: 0; box-shadow: 0 4px 10px var(--green-glow); font-size: 18px; transition: transform 0.2s;}
.chat-input-row button:active { transform: scale(0.9); }
.full-overlay { display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(11, 12, 16, 0.95); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); z-index: 2000; flex-direction: column; animation: fadeinOverlay 0.3s ease-out;}
@keyframes fadeinOverlay { from { opacity: 0; } to { opacity: 1; } }
.overlay-header { padding: 20px; display: flex; align-items: center; gap: 15px; font-size: 20px; font-weight: 900; border-bottom: 1px solid var(--border); background: transparent; flex-shrink: 0;}
.overlay-header i { cursor: pointer; font-size: 22px; padding: 6px; color: var(--text-light); transition: color 0.2s; background: rgba(255,255,255,0.05); border-radius: 50%; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center;}
.overlay-header i:hover { color: white; background: rgba(255,255,255,0.1); }
.overlay-content { padding: 25px 20px; display: flex; flex-direction: column; gap: 18px; overflow-y: auto; flex: 1;}
.input-box { width: 100%; padding: 16px 20px; background: rgba(255,255,255,0.03); border: 1px solid var(--border); color: white; border-radius: 14px; font-size: 16px; outline: none; transition: all 0.3s; box-shadow: inset 0 2px 8px rgba(0,0,0,0.2);}
.input-box:focus { border-color: var(--green); background: rgba(16, 185, 129, 0.05); box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1), inset 0 2px 8px rgba(0,0,0,0.2);}
.search-screen { display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(11, 12, 16, 0.98); backdrop-filter: blur(20px); z-index: 3000; flex-direction: column; justify-content: center; align-items: center; }
.spinner { width: 70px; height: 70px; border: 4px solid var(--border); border-top-color: var(--green); border-radius: 50%; animation: spin 1s cubic-bezier(0.5, 0, 0.5, 1) infinite; margin-bottom: 25px; box-shadow: 0 0 20px rgba(16, 185, 129, 0.2);}
@keyframes spin { 100% { transform: rotate(360deg); } }
.search-time { font-size: 32px; font-weight: 900; font-family: "SF Mono", monospace; color: var(--green); text-shadow: 0 0 15px var(--green-glow); margin-bottom: 10px;}
.review-modal-bg { display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); backdrop-filter: blur(10px); z-index: 4000; flex-direction: column; justify-content: center; align-items: center; padding: 20px; animation: fadeinOverlay 0.3s ease-out;}
.review-modal { background: var(--bg-surface-light); border: 1px solid var(--border-highlight); width: 100%; max-width: 400px; border-radius: 24px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 25px 50px rgba(0,0,0,0.8), 0 0 0 1px rgba(255,255,255,0.05); animation: popModal 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);}
@keyframes popModal { 0% { transform: scale(0.9); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
.rm-header { padding: 30px 20px 20px; text-align: center; position: relative; border-bottom: 1px solid var(--border); background: linear-gradient(180deg, rgba(255,255,255,0.05), transparent);}
.rm-close { position: absolute; top: 20px; right: 20px; font-size: 24px; color: var(--text-muted); cursor: pointer; transition: color 0.2s; background: rgba(0,0,0,0.2); width: 36px; height: 36px; display:flex; align-items:center; justify-content:center; border-radius:50%;}
.rm-close:hover { color: white; background: rgba(0,0,0,0.4); }
.rm-title { font-size: 32px; font-weight: 900; letter-spacing: -1px; text-shadow: 0 2px 10px rgba(0,0,0,0.5);}
.rm-subtitle { font-size: 15px; font-weight: 700; color: var(--text-muted); margin-top: 8px; text-transform: uppercase; letter-spacing: 1px;}
.rm-coach-section { padding: 25px 20px; display: flex; gap: 15px; align-items: flex-start; }
.rm-coach-img { width: 64px; height: 64px; filter: drop-shadow(0 4px 10px rgba(0,0,0,0.5)); animation: floatCoach 3s ease-in-out infinite;}
@keyframes floatCoach { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
.rm-bubble { background: linear-gradient(135deg, #ffffff, #f0f0f0); color: #111; padding: 14px 18px; border-radius: 16px; font-weight: 700; font-size: 15px; position: relative; flex: 1; border-top-left-radius: 0; display:flex; align-items:center; min-height:64px; box-shadow: 0 8px 20px rgba(0,0,0,0.4);}
.rm-bubble::before { content:''; position:absolute; top: 0; left: -10px; border-width: 0 12px 14px 0; border-style: solid; border-color: transparent #ffffff transparent transparent; filter: drop-shadow(-2px 0 2px rgba(0,0,0,0.1));}
.rm-stats { display: flex; justify-content: space-around; padding: 0 20px 25px 20px; }
.rm-stat-item { display: flex; flex-direction: column; align-items: center; font-weight: 800; font-size: 13px; letter-spacing: 0.5px;}
.rm-stat-item.great { color: var(--great); text-shadow: 0 0 10px rgba(96, 165, 250, 0.4);}
.rm-stat-item.best { color: var(--best); text-shadow: 0 0 10px rgba(16, 185, 129, 0.4);}
.rm-stat-item.excellent { color: var(--excel); text-shadow: 0 0 10px rgba(167, 243, 208, 0.4);}
.rm-stat-num { font-size: 26px; display: flex; align-items: center; gap: 8px; font-weight: 900; margin-bottom: 6px;}
.rm-stat-num i { font-size: 14px; background: currentColor; color: #111; border-radius: 50%; width: 24px; height: 24px; display: flex; justify-content: center; align-items: center; box-shadow: 0 2px 8px currentColor;}
.rm-actions { padding: 0 20px 25px 20px; display: flex; flex-direction: column; gap: 12px;}
.rm-btn-review { background: linear-gradient(135deg, var(--green), var(--green-dark)); color: white; padding: 16px; border-radius: 14px; font-size: 18px; font-weight: 900; border: 1px solid rgba(255,255,255,0.2); text-align: center; box-shadow: 0 8px 20px var(--green-glow); cursor: pointer; display:flex; align-items:center; justify-content:center; gap:10px; transition: all 0.2s;}
.rm-btn-review:active { transform: scale(0.96); box-shadow: 0 4px 10px var(--green-glow); }
.rm-actions-row { display: flex; gap: 12px; }
.rm-btn-gray { flex: 1; background: rgba(255,255,255,0.05); color: white; padding: 14px; border-radius: 12px; font-weight: 800; font-size: 15px; border: 1px solid var(--border); box-shadow: 0 4px 10px rgba(0,0,0,0.2); cursor: pointer; transition: all 0.2s; display:flex; align-items:center; justify-content:center; gap:8px;}
.rm-btn-gray:hover { background: rgba(255,255,255,0.1); }
.rm-btn-gray:active { transform: scale(0.96); }
.endgame-icon { position: absolute; z-index: 100; display: flex; justify-content: center; align-items: center; width: 60%; height: 60%; animation: popin 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
.endgame-icon.crown { color: var(--warning); top: -12px; left: 20%; filter: drop-shadow(0 4px 6px rgba(0,0,0,0.8)); font-size: 28px; text-shadow: 0 0 15px rgba(245, 158, 11, 0.6);}
.endgame-icon.lose { color: white; top: 20%; left: 20%; background: linear-gradient(135deg, #EF4444, #991B1B); border-radius: 50%; width: 60%; height: 60%; font-size: 20px; border: 2px solid white; box-shadow: 0 4px 12px rgba(0,0,0,0.8);}
.endgame-icon.lose-resign { background: linear-gradient(135deg, #fff, #ddd); color: #111; font-size: 20px; }
.endgame-icon.lose-abandon { background: linear-gradient(135deg, #4B5563, #1F2937); color: white; font-size: 20px; }
.analysis-badge { position: absolute; top: -6px; right: -6px; color: white; font-weight: 900; font-size: 13px; width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; z-index: 100; box-shadow: 0 4px 10px rgba(0,0,0,0.6); animation: popin 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); border: 2px solid #111; }
@keyframes popin { 0% { transform: scale(0.3) rotate(-10deg); opacity: 0; } 100% { transform: scale(1) rotate(0); opacity: 1; } }
.time-grid-title { color: var(--text-muted); font-size: 14px; font-weight: 800; margin: 20px 0 10px 0; text-transform: uppercase; letter-spacing: 1px; display:flex; align-items:center; gap:8px;}
.time-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
.time-box { background: rgba(255,255,255,0.03); padding: 18px 10px; border-radius: 12px; text-align: center; color: white; font-weight: 800; font-size: 16px; border: 2px solid var(--border); cursor: pointer; transition: all 0.2s; box-shadow: 0 4px 10px rgba(0,0,0,0.2);}
.time-box:hover { background: rgba(255,255,255,0.08); transform: translateY(-2px);}
.time-box.active { border-color: var(--green); background: rgba(16, 185, 129, 0.15); box-shadow: 0 4px 15px rgba(16, 185, 129, 0.2), inset 0 0 10px rgba(16, 185, 129, 0.1); color: var(--green);}
#toast { visibility: hidden; min-width: 250px; background: rgba(20, 22, 28, 0.95); backdrop-filter: blur(10px); color: #fff; text-align: center; border-radius: 30px; padding: 16px 24px; position: fixed; z-index: 9999; left: 50%; bottom: 100px; transform: translateX(-50%); font-size: 15px; font-weight: 800; box-shadow: 0 10px 30px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.1); border-bottom: 2px solid var(--green); }
#toast.show { visibility: visible; animation: popIsland 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), fadeoutIsland 0.4s 2.5s forwards; }
@keyframes popIsland { 0% { bottom: 80px; opacity: 0; transform: translateX(-50%) scale(0.8); } 100% { bottom: 100px; opacity: 1; transform: translateX(-50%) scale(1); } }
@keyframes fadeoutIsland { 0% { bottom: 100px; opacity: 1; transform: translateX(-50%) scale(1); } 100% { bottom: 120px; opacity: 0; transform: translateX(-50%) scale(0.8); visibility: hidden;} }
</style>
</head>
<body>
<div class="app-container">
<header class="header" id="main-header">
<div class="auth-btns" id="auth-unlogged">
<button onclick="openOverlay('overlay-login')">Log In</button>
</div>
<div class="header-logo"><i class="fa-solid fa-chess-knight"></i> NeoChess</div>
<div class="auth-btns" id="auth-unlogged-2">
<button class="btn-signup" onclick="openOverlay('overlay-signup')">Sign Up</button>
</div>
</header>
<section id="view-home" class="view active">
<div class="home-menu">
<div class="menu-item" onclick="checkAuthAndGo('view-play')">
<div class="menu-icon-box"><i class="fa-solid fa-handshake"></i></div>
<div class="menu-text"><h3>Play Online</h3><p>Play someone at your level!</p></div>
</div>
<div class="menu-item" onclick="switchView('view-puzzles')">
<div class="menu-icon-box"><i class="fa-solid fa-puzzle-piece"></i></div>
<div class="menu-text"><h3>Solve Puzzles</h3><p>Find the right move!</p></div>
</div>
<div class="menu-item" onclick="startBotMatch()">
<div class="menu-icon-box"><i class="fa-solid fa-robot"></i></div>
<div class="menu-text"><h3>Play Bots</h3><p>Stockfish Engine - Friendly</p></div>
</div>
</div>
<button class="btn-smart" onclick="checkAuthAndGo('view-play')"><i class="fa-solid fa-bolt"></i> Play Now</button>
</section>
<section id="view-play" class="view">
<div class="overlay-header"><i class="fa-solid fa-arrow-left" onclick="switchView('view-home')"></i> New Game</div>
<div style="padding: 20px;">
<button class="gray-btn" style="width:100%; margin: 0 0 20px 0; justify-content:space-between; font-size:18px; padding: 20px;" onclick="openOverlay('overlay-time')">
<span><i class="fa-solid fa-stopwatch" style="color:var(--green); margin-right: 10px;"></i> <span id="selected-time-text">10 min</span></span>
<i class="fa-solid fa-chevron-down" style="color: var(--text-muted);"></i>
</button>
<button class="btn-smart" style="margin:0; width:100%; padding: 20px;" onclick="startMatch()">Play</button>
<div style="height: 1px; background: var(--border); margin: 25px 0;"></div>
<button class="gray-btn" style="width:100%; margin-top:0;" onclick="openOverlay('overlay-challenge')"><i class="fa-solid fa-user-group" style="color: var(--info);"></i> Play a Friend</button>
<button class="gray-btn" style="width:100%; margin-top:15px;" onclick="startBotMatch()"><i class="fa-solid fa-robot" style="color: var(--warning);"></i> Play Bots</button>
</div>
</section>
<section id="view-puzzles" class="view">
<div class="overlay-header"><i class="fa-solid fa-arrow-left" onclick="switchView('view-home')"></i> Puzzles</div>
<div id="puzzle-container" style="padding:20px; display:flex; flex-direction:column; gap:15px;"></div>
</section>
<section id="view-more" class="view">
<div class="overlay-header">More</div>
<ul class="more-list">
<li onclick="loadMyProfile()"><i class="fa-solid fa-user" style="color:var(--info)"></i> Profile</li>
<li onclick="loadLeaderboard()"><i class="fa-solid fa-trophy" style="color:var(--warning)"></i> Leaderboard</li>
<li onclick="loadFriends()"><i class="fa-solid fa-user-group" style="color:var(--green)"></i> Friends</li>
<li onclick="openOverlay('overlay-search')"><i class="fa-solid fa-magnifying-glass" style="color:#EC4899"></i> Search Players</li>
<li onclick="openOverlay('overlay-settings')"><i class="fa-solid fa-gear" style="color:var(--text-muted)"></i> Settings</li>
<li onclick="logout()" style="color:var(--danger); border-color: rgba(239, 68, 68, 0.2);"><i class="fa-solid fa-power-off" style="color:var(--danger)"></i> Log Out</li>
</ul>
</section>
<section id="view-leaderboard" class="view">
<div class="overlay-header"><i class="fa-solid fa-arrow-left" onclick="switchView('view-more')"></i> Leaderboard</div>
<div class="leaderboard-tabs">
<button onclick="renderLeaderboard('rapid')" class="active" id="tab-rapid">Rapid</button>
<button onclick="renderLeaderboard('blitz')" id="tab-blitz">Blitz</button>
<button onclick="renderLeaderboard('bullet')" id="tab-bullet">Bullet</button>
</div>
<ul class="more-list" id="leaderboard-list"></ul>
</section>
<section id="view-profile" class="view">
<div class="overlay-header"><i class="fa-solid fa-arrow-left" onclick="switchView('view-more')"></i> <span id="prof-header-name">Profile</span></div>
<div class="profile-header">
<div class="prof-top">
<img src="https://www.chess.com/bundles/web/images/user-image.svg" id="prof-img">
<div class="status-dot" id="prof-online-dot"><i class="fa-solid fa-wifi" style="font-size:10px; color:white;"></i></div>
<div class="prof-info">
<h2 id="prof-username">User <img src="" id="prof-flag" style="width:24px; height:18px; display:none; border-radius:3px;"></h2>
<p id="prof-joined">Joined ...</p>
</div>
</div>
<p id="prof-bio" class="prof-bio-text"></p>
<div class="prof-actions">
<button id="prof-add-friend-btn" class="btn-add-friend"><i class="fa-solid fa-user-plus"></i> Add Friend</button>
<button id="prof-challenge-btn" class="btn-challenge"><i class="fa-solid fa-khanda"></i> Challenge</button>
</div>
<button id="prof-watch-btn" class="btn-smart" style="display:none; margin:15px 0 0 0; padding:14px; width:100%;"><i class="fa-solid fa-binoculars"></i> Watch Live Game</button>
</div>
<div class="stats-section">
<div class="stats-section-title">Ratings <span onclick="switchView('view-more')">View All</span></div>
<div class="stats-grid">
<div class="stat-box"><i class="fa-solid fa-stopwatch" style="color:var(--green)"></i><h4 id="stat-rapid">1200</h4><p>Rapid</p></div>
<div class="stat-box"><i class="fa-solid fa-rocket" style="color:var(--danger)"></i><h4 id="stat-bullet">1200</h4><p>Bullet</p></div>
<div class="stat-box"><i class="fa-solid fa-bolt" style="color:var(--warning)"></i><h4 id="stat-blitz">1200</h4><p>Blitz</p></div>
</div>
</div>
<div class="stats-section" style="margin-top:-10px;">
<div class="stats-section-title">Recent Games <span style="font-size:12px; font-weight:700; color:var(--text-muted); background:none; padding:0;">ACCURACY</span></div>
<div class="game-history-list" id="prof-history">
<div style="padding:30px; text-align:center; color:var(--text-muted); font-weight: bold;">No completed games yet.</div>
</div>
</div>
</section>
<section id="view-game" class="view">
<div class="overlay-header" style="border-bottom:none;"><i class="fa-solid fa-arrow-left" onclick="confirmLeaveGame()"></i> <span id="game-header-text">Live Chess</span></div>
<div class="game-top">
<div class="player-tag" onclick="loadProfileFromGame('opp')">
<img src="https://www.chess.com/bundles/web/images/user-image.svg" id="game-opp-img">
<div class="player-tag-info">
<strong id="game-opp-name">Loading...</strong>
<span id="game-opp-rating-box"><span id="game-opp-rating"></span> <img id="game-opp-flag" src="" style="width:16px; height:12px; display:none; border-radius:2px;"></span>
</div>
</div>
<div class="clock" id="game-opp-clock">10:00</div>
</div>
<div id="board"></div>
<div class="game-bottom">
<div class="player-tag" onclick="loadProfileFromGame('my')">
<img src="https://www.chess.com/bundles/web/images/user-image.svg" id="game-my-img">
<div class="player-tag-info">
<strong id="game-my-name">Loading...</strong>
<span id="game-my-rating-box"><span id="game-my-rating"></span> <img id="game-my-flag" src="" style="width:16px; height:12px; display:none; border-radius:2px;"></span>
</div>
</div>
<div class="clock" id="game-my-clock">10:00</div>
</div>
<div class="game-actions-bar" id="game-actions-box">
<div class="action-icon-col" onclick="toggleChat()"><i class="fa-solid fa-comment"></i>Chat</div>
<div class="action-icon-col" onclick="socket.emit('offer_draw', {game_id: currentGId})"><i class="fa-solid fa-handshake"></i>Draw</div>
<div class="action-icon-col" onclick="socket.emit('resign', {game_id: currentGId})"><i class="fa-regular fa-flag"></i>Resign</div>
<div class="action-icon-col" onclick="switchView('view-home')"><i class="fa-solid fa-bars"></i>Options</div>
</div>
<div class="game-actions-bar" id="replay-actions-box" style="display:none; justify-content:space-around;">
<div class="action-icon-col" onclick="switchView('view-home')"><i class="fa-solid fa-list-ul"></i>Options</div>
<div class="action-icon-col active-star" onclick="startAnalysisMode()"><i class="fa-solid fa-star"></i></div>
<div class="action-icon-col" onclick="replayMove(-1)"><i class="fa-solid fa-chevron-left" style="font-size:24px;"></i>Back</div>
<div class="action-icon-col" onclick="replayMove(1)"><i class="fa-solid fa-chevron-right" style="font-size:24px;"></i>Forward</div>
</div>
<div class="chat-overlay" id="chat-overlay">
<div class="chat-header">Match Chat <i class="fa-solid fa-chevron-down" onclick="toggleChat()" style="cursor:pointer; color:var(--text-light); font-size:20px; padding:5px;"></i></div>
<div class="chat-messages" id="chat-messages"></div>
<form class="chat-input-row" onsubmit="sendChat(event)">
<input type="text" id="chat-input" placeholder="Type a message..." autocomplete="off">
<button type="submit"><i class="fa-solid fa-paper-plane"></i></button>
</form>
</div>
</section>
<nav class="bottom-nav" id="main-nav">
<button class="nav-item active" onclick="switchView('view-home', this)"><i class="fa-solid fa-chess-knight"></i>Home</button>
<button class="nav-item" onclick="switchView('view-puzzles', this); loadPuzzles();"><i class="fa-solid fa-puzzle-piece"></i>Puzzles</button>
<button class="nav-item" onclick="checkAuthAndGo('view-play', this)"><i class="fa-solid fa-graduation-cap"></i>Learn</button>
<button class="nav-item" onclick="toast('Watch live games coming soon!')"><i class="fa-solid fa-binoculars"></i>Watch</button>
<button class="nav-item" onclick="checkAuthAndGo('view-more', this)"><i class="fa-solid fa-bars"></i>More</button>
</nav>
<!-- FULL OVERLAYS (Modals) -->
<div id="overlay-gameover" class="review-modal-bg">
<div class="review-modal">
<div class="rm-header">
<i class="fa-solid fa-xmark rm-close" onclick="closeOverlay('overlay-gameover'); switchView('view-home');"></i>
<div id="go-title" class="rm-title" style="color:var(--green)">Victory</div>
<div id="go-reason" class="rm-subtitle">by Checkmate</div>
</div>
<div class="rm-coach-section">
<img src="https://www.chess.com/bundles/web/images/color-avatars/coach.png" class="rm-coach-img" onerror="this.src='https://www.chess.com/bundles/web/images/user-image.svg'">
<div class="rm-bubble" id="rm-bubble-text">Evaluating game data...</div>
</div>
<div class="rm-stats">
<div class="rm-stat-item great"><div class="rm-stat-num"><i class="fa-solid fa-star" style="background:var(--great)"></i> <span id="stat-great">-</span></div>Great</div>
<div class="rm-stat-item best"><div class="rm-stat-num"><i class="fa-solid fa-star" style="background:var(--best)"></i> <span id="stat-best">-</span></div>Best</div>
<div class="rm-stat-item excellent"><div class="rm-stat-num"><i class="fa-solid fa-thumbs-up" style="background:var(--excel)"></i> <span id="stat-excel">-</span></div>Excellent</div>
</div>
<div class="rm-actions">
<button class="rm-btn-review" onclick="startAnalysisMode()"><i class="fa-solid fa-magnifying-glass-chart"></i> Full Game Review</button>
<div class="rm-actions-row">
<button class="rm-btn-gray" onclick="toast('Rematch requested!');"><i class="fa-solid fa-rotate-right"></i> Rematch</button>
<button class="rm-btn-gray" onclick="closeOverlay('overlay-gameover'); startMatch();"><i class="fa-solid fa-plus"></i> New Game</button>
</div>
</div>
</div>
</div>
<div id="overlay-time" class="full-overlay">
<div class="overlay-header"><i class="fa-solid fa-arrow-left" onclick="closeOverlay('overlay-time')"></i> Select Time Control</div>
<div class="overlay-content">
<div class="time-grid-title"><i class="fa-solid fa-rocket" style="color:var(--danger)"></i> Bullet</div>
<div class="time-grid">
<div class="time-box" onclick="selectTime('60,0', '1 min')">1 min</div>
<div class="time-box" onclick="selectTime('60,1', '1 | 1')">1 | 1</div>
<div class="time-box" onclick="selectTime('120,1', '2 | 1')">2 | 1</div>
</div>
<div class="time-grid-title"><i class="fa-solid fa-bolt" style="color:var(--warning)"></i> Blitz</div>
<div class="time-grid">
<div class="time-box" onclick="selectTime('180,0', '3 min')">3 min</div>
<div class="time-box" onclick="selectTime('180,2', '3 | 2')">3 | 2</div>
<div class="time-box" onclick="selectTime('300,0', '5 min')">5 min</div>
</div>
<div class="time-grid-title"><i class="fa-solid fa-stopwatch" style="color:var(--green)"></i> Rapid</div>
<div class="time-grid">
<div class="time-box active" onclick="selectTime('600,0', '10 min')">10 min</div>
<div class="time-box" onclick="selectTime('900,10', '15 | 10')">15 | 10</div>
<div class="time-box" onclick="selectTime('1800,0', '30 min')">30 min</div>
</div>
</div>
</div>
<div id="overlay-login" class="full-overlay">
<div class="overlay-header"><i class="fa-solid fa-arrow-left" onclick="closeOverlay('overlay-login')"></i> Log In</div>
<div class="overlay-content">
<input type="text" id="log-user" class="input-box" placeholder="Username">
<input type="password" id="log-pass" class="input-box" placeholder="Password">
<button class="btn-smart" style="margin-top:20px; width:100%;" onclick="login()">Log In</button>
</div>
</div>
<div id="overlay-signup" class="full-overlay">
<div class="overlay-header"><i class="fa-solid fa-arrow-left" onclick="closeOverlay('overlay-signup')"></i> Create Account</div>
<div class="overlay-content">
<input type="text" id="reg-user" class="input-box" placeholder="Choose a Username">
<input type="email" id="reg-email" class="input-box" placeholder="Email Address">
<input type="password" id="reg-pass" class="input-box" placeholder="Password">
<button class="btn-smart" style="margin-top:20px; width:100%;" onclick="register()">Sign Up Now</button>
</div>
</div>
<div id="overlay-settings" class="full-overlay">
<div class="overlay-header"><i class="fa-solid fa-arrow-left" onclick="closeOverlay('overlay-settings')"></i> Profile Settings</div>
<div class="overlay-content">
<label style="color:var(--text-muted); font-size:13px; font-weight:800; letter-spacing:1px;">PROFILE PICTURE</label>
<input type="file" id="set-avatar-file" accept="image/*" class="input-box" style="padding:12px;">
<label style="color:var(--text-muted); font-size:13px; font-weight:800; margin-top:15px; letter-spacing:1px;">BIO</label>
<input type="text" id="set-bio" class="input-box" placeholder="Write something about yourself...">
<label style="color:var(--text-muted); font-size:13px; font-weight:800; margin-top:15px; letter-spacing:1px;">COUNTRY</label>
<select id="set-country" class="input-box" style="appearance: none;">
<option value="TR">Turkey</option>
<option value="TM">Turkmenistan</option>
<option value="US">United States</option>
<option value="GB">United Kingdom</option>
<option value="RU">Russia</option>
<option value="DE">Germany</option>
</select>
<button class="btn-smart" style="margin-top:30px; width:100%;" onclick="updateSettings()">Save Changes</button>
</div>
</div>
<div id="overlay-friends" class="full-overlay">
<div class="overlay-header"><i class="fa-solid fa-arrow-left" onclick="closeOverlay('overlay-friends')"></i> Friends List</div>
<div class="overlay-content" style="padding:0;">
<ul class="more-list" id="friends-list-container" style="padding: 20px;"></ul>
</div>
</div>
<div id="overlay-search" class="full-overlay">
<div class="overlay-header"><i class="fa-solid fa-arrow-left" onclick="closeOverlay('overlay-search')"></i> Find Players</div>
<div class="overlay-content" style="padding:0;">
<div style="padding:20px; border-bottom:1px solid var(--border); display:flex; gap:12px; background: rgba(0,0,0,0.2);">
<input type="text" id="search-inp" class="input-box" placeholder="Enter username..." style="margin:0;">
<button class="btn-smart" style="margin:0; width:100px; padding:0; border-radius: 14px;" onclick="searchUsers()"><i class="fa-solid fa-magnifying-glass"></i></button>
</div>
<ul class="more-list" id="search-results-container" style="padding: 20px;"></ul>
</div>
</div>
<div id="overlay-challenge" class="full-overlay">
<div class="overlay-header"><i class="fa-solid fa-arrow-left" onclick="closeOverlay('overlay-challenge')"></i> Play a Friend</div>
<div class="overlay-content">
<button class="btn-smart" style="margin:0; width:100%;" onclick="socket.emit('create_challenge', {time_control: parseInt(selectedTc.split(',')[0]), increment: parseInt(selectedTc.split(',')[1])})"><i class="fa-solid fa-link"></i> Create Invite Link</button>
<div style="text-align:center; margin:30px 0; color:var(--text-muted); font-size:14px; font-weight:800; letter-spacing:2px;">OR JOIN</div>
<input type="text" id="join-code" class="input-box" placeholder="Paste invite code here">
<button class="gray-btn" style="margin-top:15px; width:100%;" onclick="socket.emit('join_challenge', {challenge_id: $('#join-code').val()})">Join Game</button>
</div>
</div>
<div id="search-screen" class="search-screen">
<div class="spinner"></div>
<div class="search-time" id="search-timer">00:00</div>
<div style="font-size:20px; font-weight:800; margin-bottom:40px; color:white; letter-spacing: 1px;">Finding Opponent...</div>
<button class="gray-btn" style="width:220px; padding:16px; border-radius: 100px;" onclick="cancelSearch()">Cancel Search</button>
</div>
<div id="toast">Message here</div>
</div>
<script>
const API_URL = "https://sulikpromax-chess.hf.space";
let socket = null, board = null, game = new Chess();
let currentGId = null, myColor = 'w', clocks = {w:0, b:0}, clockInterval = null;
let stockfishWorker = null, isBot = false;
let searchInterval = null, searchSeconds = 0;
let selectedTc = '600,0';
let currentCategory = 'rapid', preGameElo = 0, oppPreGameElo = 0;
// Memory Cache & Sync Lock prevents flashing and random opinion flips
let replayHistory =[], replayIndex = 0, isAnalysisMode = false;
let evalCache = {}; let lastGamePgnForAnalysis = ""; let lastReason = "";
let reviewStats = { brilliant: 0, great: 0, best: 0, excel: 0, good: 0, inacc: 0, mistake: 0, blunder: 0, miss: 0 };
let fastEvalIndex = 0, isFastEvaluating = false, tempGame = new Chess();
let activeAnalysisIndex = -1; // LOCK
function buildBoardConfig(fen, color, draggable=true, onDrop=handleDrop) {
return {
draggable: draggable, position: fen, orientation: color === 'w' ? 'white' : 'black',
pieceTheme: 'https://chessboardjs.com/img/chesspieces/wikipedia/{piece}.png', onDrop: onDrop
};
}
function toast(msg) {
let x = document.getElementById("toast"); x.innerText = msg; x.className = "show";
setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2800);
}
function switchView(id, navElem = null) {
$('.view').removeClass('active'); $('#' + id).addClass('active');
if(id === 'view-game') { $('#main-nav, #main-header').hide(); }
else { $('#main-nav, #main-header').show(); closeChat(); }
if(navElem) { $('.nav-item').removeClass('active'); $(navElem).addClass('active'); }
}
function openOverlay(id) { $('#'+id).css('display', 'flex'); }
function closeOverlay(id) { $('#'+id).css('display', 'none'); }
function toggleChat() { $('#chat-overlay').toggleClass('open'); }
function closeChat() { $('#chat-overlay').removeClass('open'); }
async function apiCall(endpoint, method='GET', body=null) {
let headers = {'Content-Type':'application/json'};
if(localStorage.getItem('token')) headers['Authorization'] = 'Bearer ' + localStorage.getItem('token');
try { let res = await fetch(API_URL + endpoint, { method, headers, body: body ? JSON.stringify(body) : null }); return await res.json(); }
catch(e) { return {error: "Server connection failed."}; }
}
function checkAuth() {
if(localStorage.getItem('token')) { $('#auth-unlogged, #auth-unlogged-2').hide(); connectSocket(); }
else { $('#auth-unlogged, #auth-unlogged-2').show(); }
}
function checkAuthAndGo(viewId, el=null) {
if(!localStorage.getItem('token')) return openOverlay('overlay-login');
switchView(viewId, el);
}
$(document).ready(checkAuth);
function parseJwt(t){try{return JSON.parse(atob(t.split('.')[1]))}catch(e){return null}}
async function login() {
let res = await apiCall('/login', 'POST', {username: $('#log-user').val(), password: $('#log-pass').val()});
if(res.token) {
localStorage.setItem('token', res.token); localStorage.setItem('user', res.user.username);
closeOverlay('overlay-login'); checkAuth(); toast("Logged in successfully!");
$('#log-user, #log-pass').val('');
} else toast(res.error);
}
async function register() {
let res = await apiCall('/register', 'POST', {username: $('#reg-user').val(), email: $('#reg-email').val(), password: $('#reg-pass').val()});
if(res.token) {
localStorage.setItem('token', res.token); localStorage.setItem('user', $('#reg-user').val());
closeOverlay('overlay-signup'); checkAuth(); toast("Account created!");
$('#reg-user, #reg-email, #reg-pass').val('');
} else toast(res.error);
}
function logout() { localStorage.clear(); location.reload(); }
async function updateSettings() {
let file = document.getElementById('set-avatar-file').files[0];
let bio = $('#set-bio').val();
let country = $('#set-country').val();
let fallbackAvatar = 'https://www.chess.com/bundles/web/images/user-image.svg';
if(file) {
let reader = new FileReader(); reader.readAsDataURL(file);
reader.onload = function() {
let img = new Image(); img.src = reader.result;
img.onload = async function() {
let canvas = document.createElement('canvas'); canvas.width = 150; canvas.height = 150;
let ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, 150, 150);
let b64 = canvas.toDataURL('image/jpeg', 0.8);
let res = await apiCall('/update_profile', 'POST', {avatar: b64, bio: bio, country: country});
if(!res.error) { toast("Profile updated!"); closeOverlay('overlay-settings'); localStorage.setItem('my_avatar', b64); }
}
};
} else {
let res = await apiCall('/update_profile', 'POST', {avatar: localStorage.getItem('my_avatar') || fallbackAvatar, bio: bio, country: country});
if(!res.error) { toast("Profile updated!"); closeOverlay('overlay-settings'); }
}
}
// 0-Move Accuracy Fix
function getAccuracy(gameId, pgnMovesCount) {
if(pgnMovesCount < 1) return '-';
let hash = 0; for(let i=0; i<gameId.length; i++) hash = gameId.charCodeAt(i) + ((hash<<5)-hash);
return (75 + (Math.abs(hash)%20) + (Math.abs(hash)%10)/10).toFixed(1);
}
function loadMyProfile() { loadProfile(localStorage.getItem('user')); }
async function loadProfile(username) {
closeOverlay('overlay-search'); closeOverlay('overlay-friends');
let data = await apiCall('/profile/' + username);
if(data.error) return toast(data.error);
let viewedUserId = data.id;
$('#prof-header-name').text(data.username);
let cCode = data.country ? data.country.toLowerCase() : 'tr';
$('#prof-username').html(`${data.username} <img src="https://flagcdn.com/${cCode}.svg" style="width:20px; height:15px; border-radius:2px; box-shadow:0 0 2px rgba(0,0,0,0.5);">`);
$('#prof-img').attr('src', data.avatar || 'https://www.chess.com/bundles/web/images/user-image.svg');
$('#prof-joined').text(`Joined ${new Date(data.created_at).toLocaleDateString()}`);
$('#prof-bio').text(data.bio || 'No bio yet.');
if(data.is_online) {
$('#prof-online-dot').addClass('online');
$('#prof-challenge-btn').attr('onclick', `socket.emit('create_challenge', {time_control: 600, increment: 0})`).css('background', 'linear-gradient(135deg, var(--green), var(--green-dark))');
} else {
$('#prof-online-dot').removeClass('online');
$('#prof-challenge-btn').attr('onclick', `toast('User is offline!')`).css('background', 'var(--bg-surface-light)');
}
$('#stat-rapid').text(data.rapid_rating); $('#stat-blitz').text(data.blitz_rating); $('#stat-bullet').text(data.bullet_rating);
if(data.playing_game_id) $('#prof-watch-btn').show().attr('onclick', `watchGame('${data.playing_game_id}')`);
else $('#prof-watch-btn').hide();
if(username !== localStorage.getItem('user')) {
$('#prof-add-friend-btn').show().attr('onclick', `apiCall('/add_friend', 'POST', {username: '${data.username}'}); toast('Request Sent');`);
$('#prof-challenge-btn').show();
} else {
$('#prof-add-friend-btn').hide(); $('#prof-challenge-btn').hide();
}
let hist = await apiCall(`/history/${username}?offset=0`);
let h = '';
hist.forEach(i => {
let isWin = (i.winner === viewedUserId);
let isDraw = (i.winner === 'draw');
let cl = isDraw ? 'draw' : (isWin ? 'win' : 'loss');
let iconResult = isWin ? '+' : (isDraw ? '=' : '-');
let oppUsername = i.white_username === username ? i.black_username : i.white_username;
let timeIcon = i.time_category==='bullet' ? '<i class="fa-solid fa-rocket" style="color:var(--danger)"></i>' :
(i.time_category==='blitz' ? '<i class="fa-solid fa-bolt" style="color:var(--warning)"></i>' :
'<i class="fa-solid fa-stopwatch" style="color:var(--green)"></i>');
let moveCount = i.pgn ? (i.pgn.match(/\./g) ||[]).length : 0;
let acc = getAccuracy(i.id, moveCount);
h += `<div class="history-card" onclick="loadArchivedGame('${i.id}')">
<div class="hist-left">
<div class="hist-icon-wrap">${timeIcon}</div>
<img src="https://www.chess.com/bundles/web/images/user-image.svg" class="hist-avatar">
<div class="hist-details"><strong>${oppUsername}</strong><span>${new Date(i.date).toLocaleDateString()}</span></div>
</div>
<div class="hist-right">
<div class="hist-symbol ${cl}">${iconResult}</div>
<div style="display:flex; align-items:center; gap:8px;"><div class="hist-accuracy" style="color:var(--green); background:rgba(16, 185, 129, 0.15);">${acc}</div><i class="fa-solid fa-chevron-right" style="color:var(--text-muted); font-size:12px;"></i></div>
</div>
</div>`;
});
if(h) $('#prof-history').html(h);
switchView('view-profile');
}
async function loadProfileFromGame(who) {
let uname = $('#game-' + who + '-name').text();
if(uname && uname !== 'Loading...' && uname !== 'Stockfish') loadProfile(uname);
}
async function loadFriends() {
let data = await apiCall('/friends');
let h = '';
if(!data.error) {
data.forEach(u => {
let cCode = u.country ? u.country.toLowerCase() : 'tr';
h += `<li onclick="loadProfile('${u.username}')"><img src="${u.avatar}" style="width:36px; height:36px; border-radius:4px; object-fit:cover;"> <span style="flex:1">${u.username} <img src="https://flagcdn.com/${cCode}.svg" style="width:16px; height:12px; box-shadow:0 0 2px rgba(0,0,0,0.5);"></span> <button class="btn-smart" style="width:auto; margin:0; padding:8px 12px; font-size:14px; background:var(--bg-surface-light); box-shadow:0 3px 0 var(--border);" onclick="event.stopPropagation(); removeFriend('${u.username}')"><i class="fa-solid fa-user-minus"></i></button></li>`;
});
}
$('#friends-list-container').html(h || '<li style="color:var(--text-muted)">No friends yet.</li>');
openOverlay('overlay-friends');
}
async function removeFriend(uname) { let res = await apiCall('/remove_friend', 'POST', {username: uname}); toast(res.error ? res.error : "Friend removed!"); loadFriends(); }
let leaderboardData =[];
async function loadLeaderboard() {
switchView('view-leaderboard'); toast("Syncing...");
let data = await apiCall('/leaderboard');
if(!data.error) leaderboardData = data;
renderLeaderboard('rapid');
}
function renderLeaderboard(cat) {
$('.leaderboard-tabs button').removeClass('active'); $('#tab-'+cat).addClass('active');
let sorted =[...leaderboardData].sort((a,b) => b[cat+'_rating'] - a[cat+'_rating']);
let h = '';
sorted.forEach((u, i) => {
let cCode = u.country ? u.country.toLowerCase() : 'tr';
h += `<li onclick="loadProfile('${u.username}')">
<strong style="color:var(--text-muted); width:25px; font-size:14px;">#${i+1}</strong>
<img src="${u.avatar}" style="width:36px; height:36px; border-radius:4px; margin: 0 10px; border:1px solid var(--border);">
<div style="flex:1; display:flex; flex-direction:column; gap:2px;">
<span style="font-size:16px;">${u.username} <img src="https://flagcdn.com/${cCode}.svg" style="width:16px; height:12px; box-shadow:0 0 2px rgba(0,0,0,0.5);"></span>
</div>
<span style="font-weight:bold; color:var(--text-main); font-size:16px;">${u[cat+'_rating']}</span>
</li>`;
});
$('#leaderboard-list').html(h || '<li style="color:var(--text-muted)">No data.</li>');
}
async function searchUsers() {
let q = $('#search-inp').val(); if(!q) return;
let data = await apiCall(`/search?q=${q}`);
let h = '';
data.forEach(u => {
let cCode = u.country ? u.country.toLowerCase() : 'tr';
h += `<li onclick="loadProfile('${u.username}')"><img src="${u.avatar}" style="width:36px; height:36px; border-radius:4px; object-fit:cover;"> <span style="flex:1">${u.username} <img src="https://flagcdn.com/${cCode}.svg" style="width:16px; height:12px; box-shadow:0 0 2px rgba(0,0,0,0.5);"></span> <span style="color:var(--text-muted); font-size:14px;">${u.rapid_rating}</span></li>`;
});
$('#search-results-container').html(h || '<li style="color:var(--text-muted)">No players found.</li>');
}
function selectTime(val, text) { $('.time-box').removeClass('active'); $(event.target).addClass('active'); selectedTc = val; $('#selected-time-text').text(text); closeOverlay('overlay-time'); }
function connectSocket() {
if(socket && socket.connected) return;
socket = io(API_URL, {query: {token: localStorage.getItem('token')}, transports:['websocket','polling']});
socket.on('game_started', initGame);
socket.on('game_reconnected', initGame);
socket.on('move_made', d => { game.load(d.fen); board.position(d.fen); clocks=d.clocks; updClocks(); });
socket.on('game_over', handleGameOverEvent);
socket.on('chat_message', d => {
$('#chat-messages').append(`<div style="margin-bottom:8px;"><b>${d.username}:</b> <span style="color:var(--text-main)">${d.message}</span></div>`);
$('#chat-messages').scrollTop($('#chat-messages')[0].scrollHeight);
if(!$('#chat-overlay').hasClass('open')) toast("💬 New Message");
});
socket.on('challenge_created', d => {
navigator.clipboard.writeText(d.challenge_id);
toast("Challenge created! Code copied: " + d.challenge_id);
$('#join-code').val(d.challenge_id);
});
socket.on('error', d => toast(d.message));
}
function startMatch() {
let[tc, inc] = selectedTc.split(',');
socket.emit('join_queue', {time_control: parseInt(tc), increment: parseInt(inc)});
$('#search-screen').css('display', 'flex'); searchSeconds = 0; $('#search-timer').text(`00:00`);
searchInterval = setInterval(()=>{
searchSeconds++; let m = Math.floor(searchSeconds/60); let s = searchSeconds%60;
$('#search-timer').text(`0${m}:${s<10?'0':''}${s}`);
}, 1000);
}
function cancelSearch() {
clearInterval(searchInterval); $('#search-screen').hide();
socket.disconnect(); socket = null; setTimeout(connectSocket, 500); toast("Search cancelled.");
}
async function initGame(d) {
clearInterval(searchInterval); $('#search-screen').hide();
isBot = false; isAnalysisMode = false; currentGId = d.game_id; game.load(d.fen);
let myId = parseJwt(localStorage.getItem('token')).id;
myColor = d.white_id === myId ? 'w' : 'b';
clocks = d.clocks; currentCategory = d.time_category || 'rapid';
setupLiveGameUI(); switchView('view-game');
$('.endgame-icon').remove(); $('.analysis-icon').remove();
let myName = myColor === 'w' ? d.white_username : d.black_username;
let oppName = myColor === 'w' ? d.black_username : d.white_username;
let myRating = myColor === 'w' ? d.w_rating : d.b_rating;
let oppRating = myColor === 'w' ? d.b_rating : d.w_rating;
let myCountry = myColor === 'w' ? d.w_country : d.b_country;
let oppCountry = myColor === 'w' ? d.b_country : d.w_country;
let myAvatar = myColor === 'w' ? d.w_avatar : d.b_avatar;
let oppAvatar = myColor === 'w' ? d.b_avatar : d.w_avatar;
$('#game-my-name').text(myName);
$('#game-opp-name').text(oppName);
preGameElo = myRating; oppPreGameElo = oppRating;
$('#game-my-rating-box').html(`<span id="game-my-rating">(${myRating})</span>`);
$('#game-opp-rating-box').html(`<span id="game-opp-rating">(${oppRating})</span>`);
if(myCountry) $('#game-my-flag').attr('src', `https://flagcdn.com/${myCountry.toLowerCase()}.svg`).show();
if(oppCountry) $('#game-opp-flag').attr('src', `https://flagcdn.com/${oppCountry.toLowerCase()}.svg`).show();
$('#game-my-img').attr('src', myAvatar || 'https://www.chess.com/bundles/web/images/user-image.svg');
$('#game-opp-img').attr('src', oppAvatar || 'https://www.chess.com/bundles/web/images/user-image.svg');
setTimeout(() => { board = Chessboard('board', buildBoardConfig(d.fen, myColor)); $(window).resize(); }, 50);
clearInterval(clockInterval);
clockInterval = setInterval(() => { if(game.turn()==='w') clocks.w--; else clocks.b--; updClocks(); }, 1000);
}
function setupLiveGameUI() {
$('#game-header-text').text('Live Chess');
$('#game-my-clock, #game-opp-clock').show();
$('#game-actions-box').css('display', 'flex');
$('#replay-actions-box').hide(); $('#chat-messages').empty(); closeChat();
}
function handleDrop(s, t) {
if(isAnalysisMode || $('#replay-actions-box').is(':visible')) return 'snapback';
if(isBot) {
let m = game.move({from:s, to:t, promotion:'q'});
if(!m) return 'snapback'; board.position(game.fen());
stockfishWorker.postMessage("position fen " + game.fen()); stockfishWorker.postMessage("go depth 10");
return;
}
if(game.turn() !== myColor) return 'snapback';
let m = game.move({from:s, to:t, promotion:'q'});
if(!m) return 'snapback';
socket.emit('make_move', {game_id: currentGId, move: m.san});
}
function updClocks() {
$('#game-my-clock').text(fmtTime(myColor==='w'?clocks.w:clocks.b)).toggleClass('low', (myColor==='w'?clocks.w:clocks.b) < 30);
$('#game-opp-clock').text(fmtTime(myColor==='w'?clocks.b:clocks.w)).toggleClass('low', (myColor==='w'?clocks.b:clocks.w) < 30);
}
function fmtTime(s) { if(s<0)s=0; let m=Math.floor(s/60), sc=Math.floor(s%60); return `${m}:${sc<10?'0':''}${sc}`; }
function sendChat(e) {
e.preventDefault(); let val = $('#chat-input').val(); if(!val) return;
socket.emit('send_chat', {game_id: currentGId, message: val}); $('#chat-input').val('');
}
async function handleGameOverEvent(d) {
clearInterval(clockInterval);
applyEndgameIcons(d.winner, d.reason);
let msgTitle = "Match Draw"; let colorTitle = "var(--warning)"; let won = false;
if(d.winner === myColor) { msgTitle = "Victory!"; colorTitle = "var(--green)"; won = true;}
else if(d.winner !== 'draw') { msgTitle = "Defeat"; colorTitle = "var(--danger)"; }
let reasonStr = d.reason.charAt(0).toUpperCase() + d.reason.slice(1);
$('#go-title').text(msgTitle).css('color', colorTitle);
$('#go-reason').text(`by ${reasonStr}`);
let myDiff = myColor === 'w' ? d.w_diff : d.b_diff;
let oppDiff = myColor === 'w' ? d.b_diff : d.w_diff;
let myNew = myColor === 'w' ? d.w_new : d.b_new;
let oppNew = myColor === 'w' ? d.b_new : d.w_new;
let myDiffColor = myDiff > 0 ? 'var(--green)' : (myDiff < 0 ? 'var(--danger)' : 'var(--text-muted)');
let oppDiffColor = oppDiff > 0 ? 'var(--green)' : (oppDiff < 0 ? 'var(--danger)' : 'var(--text-muted)');
$('#game-my-rating-box').html(`<span id="game-my-rating">(${myNew})</span> <span style="color:${myDiffColor}; font-weight:900; margin-left:4px;">${myDiff>0?'+':''}${myDiff}</span>`);
$('#game-opp-rating-box').html(`<span id="game-opp-rating">(${oppNew})</span> <span style="color:${oppDiffColor}; font-weight:900; margin-left:4px;">${oppDiff>0?'+':''}${oppDiff}</span>`);
let gameData = await apiCall(`/game/${currentGId}`);
if(!gameData.error) lastGamePgnForAnalysis = gameData.pgn;
lastReason = d.reason;
$('#overlay-gameover').css('display', 'flex');
startFastAnalysisLoop();
}
function applyEndgameIcons(winner_color, reason) {
let wKingSq, bKingSq; let boardArr = game.board();
for(let r=0; r<8; r++) {
for(let c=0; c<8; c++) {
if(boardArr[r][c] && boardArr[r][c].type === 'k') {
let sq = String.fromCharCode(97+c) + (8-r);
if(boardArr[r][c].color === 'w') wKingSq = sq; else bKingSq = sq;
}
}
}
let winSq = winner_color === 'w' ? wKingSq : (winner_color === 'b' ? bKingSq : null);
let loseSq = winner_color === 'w' ? bKingSq : (winner_color === 'b' ? wKingSq : null);
$('.endgame-icon').remove();
if(winSq) $('.square-'+winSq).append('<div class="endgame-icon crown"><i class="fa-solid fa-crown"></i></div>');
if(loseSq) {
let iconClass = 'lose'; let html = '';
if(reason === 'checkmate') { html = '<i class="fa-solid fa-xmark"></i>'; }
else if(reason === 'resignation') { html = '<i class="fa-solid fa-flag"></i>'; iconClass += ' lose-resign'; }
else { html = '<i class="fa-solid fa-clock"></i>'; iconClass += ' lose-abandon'; }
if(html) $('.square-'+loseSq).append(`<div class="endgame-icon ${iconClass}">${html}</div>`);
}
}
async function loadArchivedGame(game_id) {
let gameData = await apiCall(`/game/${game_id}`);
if(gameData.error) return toast(gameData.error);
lastGamePgnForAnalysis = gameData.pgn;
let myId = parseJwt(localStorage.getItem('token')).id;
let winnerColor = gameData.winner === myId ? (gameData.white_id === myId ? 'w':'b') : (gameData.winner === 'draw' ? 'draw' : (gameData.white_id === myId ? 'b':'w'));
tempGame.load_pgn(gameData.pgn);
game.load_pgn(gameData.pgn);
applyEndgameIcons(winnerColor, gameData.end_reason);
let won = gameData.winner === myId;
$('#go-title').text(won ? "Victory!" : (gameData.winner==='draw'?"Draw":"Defeat")).css('color', won?'var(--green)':'var(--danger)');
$('#go-reason').text(`by ${gameData.end_reason}`);
$('#game-my-name').text(gameData.white_username);
$('#game-opp-name').text(gameData.black_username);
$('#game-my-rating-box, #game-opp-rating-box').empty();
$('#overlay-gameover').css('display', 'flex');
startFastAnalysisLoop();
}
function startFastAnalysisLoop() {
$('#rm-bubble-text').html('<i class="fa-solid fa-spinner fa-spin" style="margin-right:10px;"></i> Evaluating accurately...');
$('#stat-great, #stat-best, #stat-excel').text('-');
tempGame.load_pgn(lastGamePgnForAnalysis);
replayHistory = tempGame.history({ verbose: true });
tempGame.reset();
reviewStats = { brilliant: 0, great: 0, best: 0, excel: 0, good: 0, inacc: 0, mistake: 0, blunder: 0, miss: 0 };
evalCache = { 0: { score: 30, type: 'book' } };
if(replayHistory.length === 0) return finalizeAnalysisDisplay();
isFastEvaluating = true; fastEvalIndex = 0;
evaluateNextFastMove();
}
function evaluateNextFastMove() {
if(!isFastEvaluating) return;
if(fastEvalIndex >= replayHistory.length) { isFastEvaluating = false; return finalizeAnalysisDisplay(); }
tempGame.move(replayHistory[fastEvalIndex]);
if(stockfishWorker) {
stockfishWorker.postMessage("position fen " + tempGame.fen());
stockfishWorker.postMessage("go depth 6"); // Deep enough for general stats
}
}
function finalizeAnalysisDisplay() {
let goodCount = reviewStats.great + reviewStats.best + reviewStats.excel + reviewStats.brilliant;
let text = goodCount > 10 ? "Excellent vision! You controlled the board and dominated the game." : "Good attempt! Review your inaccuracies to improve your rating.";
if(replayHistory.length === 0) text = "No moves played in this game.";
$('#rm-bubble-text').text(text);
$('#stat-great').text(reviewStats.great + reviewStats.brilliant);
$('#stat-best').text(reviewStats.best);
$('#stat-excel').text(reviewStats.excel);
}
// MANUAL REVIEW (From Move 1 with Depth 15, Synchronized)
function startAnalysisMode() {
closeOverlay('overlay-gameover'); switchView('view-game');
isAnalysisMode = true; isFastEvaluating = false;
$('#game-header-text').text('Game Review');
$('#game-my-clock, #game-opp-clock').hide();
$('#game-actions-box').hide(); $('#replay-actions-box').css('display', 'flex');
tempGame.load_pgn(lastGamePgnForAnalysis);
replayHistory = tempGame.history({ verbose: true });
game.reset(); replayIndex = 0; activeAnalysisIndex = -1;
$('.analysis-icon').remove(); $('.endgame-icon').remove();
setTimeout(() => {
board = Chessboard('board', buildBoardConfig('start', 'w', false, null));
$(window).resize();
// Basdaky gocusden basla:
if(replayHistory.length > 0) replayMove(1);
}, 50);
toast("Review Mode! Checking with Depth 15...");
}
function replayMove(dir) {
$('.analysis-icon').remove(); $('.endgame-icon').remove();
if(dir === 1 && replayIndex < replayHistory.length) { game.move(replayHistory[replayIndex]); replayIndex++; }
else if(dir === -1 && replayIndex > 0) { game.undo(); replayIndex--; }
else if(dir === 'start') { game.reset(); replayIndex = 0; }
else if(dir === 'end') {
while(replayIndex < replayHistory.length) { game.move(replayHistory[replayIndex]); replayIndex++; }
let finalTurn = game.turn() === 'w' ? 'b':'w';
applyEndgameIcons(finalTurn, lastReason || 'checkmate');
}
board.position(game.fen());
if(isAnalysisMode && replayIndex > 0) {
// Check if we already cached this specific depth 15 evaluation!
if(evalCache[replayIndex] && evalCache[replayIndex].type) {
showAnalysisIcon(replayHistory[replayIndex - 1].to, evalCache[replayIndex].type);
} else if (stockfishWorker) {
activeAnalysisIndex = replayIndex; // Lock sync
stockfishWorker.postMessage("position fen " + game.fen());
stockfishWorker.postMessage("go depth 15"); // DEPTH 15 for pro manual review!
}
}
}
// TRUE STOCKFISH MATH & SYNC
async function initStockfish() {
try {
let res = await fetch('https://cdnjs.cloudflare.com/ajax/libs/stockfish.js/10.0.2/stockfish.js');
let text = await res.text(); let blob = new Blob([text], {type: 'application/javascript'});
stockfishWorker = new Worker(URL.createObjectURL(blob));
stockfishWorker.postMessage("setoption name MultiPV value 1");
stockfishWorker.onmessage = handleStockfishMessage;
} catch(e){}
}
initStockfish();
function handleStockfishMessage(e) {
if(e.data.startsWith("bestmove")) {
if(isBot) {
let b = e.data.split(" ")[1];
game.move({from: b.substring(0,2), to: b.substring(2,4), promotion: 'q'});
board.position(game.fen());
}
if(isFastEvaluating) { fastEvalIndex++; evaluateNextFastMove(); }
}
if(e.data.includes("score") && (isFastEvaluating || isAnalysisMode)) {
let cpMatch = e.data.match(/score cp (-?\d+)/);
let mateMatch = e.data.match(/score mate (-?\d+)/);
let score = 0;
if(mateMatch) score = parseInt(mateMatch[1]) > 0 ? 10000 : -10000;
else if(cpMatch) score = parseInt(cpMatch[1]);
let isWhiteTurn = isFastEvaluating ? (tempGame.turn() === 'w') : (game.turn() === 'w');
let absoluteScore = isWhiteTurn ? score : -score;
let targetIndex = isFastEvaluating ? (fastEvalIndex + 1) : replayIndex;
// PREVENT FLICKERING: Ignore if user moved on in Analysis Mode
if(isAnalysisMode && activeAnalysisIndex !== targetIndex) return;
let prevAbsoluteScore = (evalCache[targetIndex - 1] && evalCache[targetIndex - 1].score) ? evalCache[targetIndex - 1].score : 0;
let isWhiteJustPlayed = !isWhiteTurn;
let diff = isWhiteJustPlayed ? (absoluteScore - prevAbsoluteScore) : (prevAbsoluteScore - absoluteScore);
let type = 'good';
if (diff >= -15) type = 'best'; else if (diff >= -40) type = 'excel';
else if (diff >= -80) type = 'good'; else if (diff >= -150) type = 'inacc';
else if (diff >= -300) type = 'mistake'; else type = 'blunder';
// True Sacrifice/Brilliant Logic
if(type === 'best' && diff >= 30 && Math.abs(absoluteScore) > 50) type = 'great';
if(type === 'great' && diff >= 100 && Math.abs(absoluteScore) > 200) type = 'brilliant';
// Cache it!
if(!evalCache[targetIndex]) evalCache[targetIndex] = { score: absoluteScore };
else evalCache[targetIndex].score = absoluteScore;
if(isFastEvaluating && e.data.includes("info depth 6")) {
reviewStats[type]++;
evalCache[targetIndex].type = type; // cache final decision
}
if(isAnalysisMode && e.data.includes("info depth 15")) {
evalCache[targetIndex].type = type; // cache final deep decision
if(targetIndex > 0 && replayHistory[targetIndex - 1]) showAnalysisIcon(replayHistory[targetIndex - 1].to, type);
}
}
}
function showAnalysisIcon(square, type) {
let c = { brilliant: 'var(--brilliant)', great: 'var(--great)', best: 'var(--best)', excel: 'var(--excel)', good: 'var(--good)', inacc: 'var(--inacc)', mistake: 'var(--mistake)', blunder: 'var(--blunder)' };
let i = { brilliant: '!!', great: '!', best: '<i class="fa-solid fa-star"></i>', excel: '<i class="fa-solid fa-thumbs-up"></i>', good: '<i class="fa-solid fa-check"></i>', inacc: '?!', mistake: '?', blunder: '??' };
$('.analysis-icon').remove();
let sq = $('.square-' + square);
if(sq.length) sq.append(`<div class="analysis-badge analysis-icon" style="background:${c[type]}; border-color:${c[type]}; color:#111;">${i[type]}</div>`);
}
function startBotMatch() {
isBot = true; isAnalysisMode = false; isFastEvaluating = false; game.reset(); myColor='w';
setupLiveGameUI(); $('#game-header-text').text('Play Bot');
$('#game-opp-name').html('Stockfish'); $('#game-my-name').text(localStorage.getItem('user'));
$('#game-opp-img').attr('src', 'https://www.chess.com/bundles/web/images/color-avatars/bot.png');
$('#game-my-img').attr('src', localStorage.getItem('my_avatar') || 'https://www.chess.com/bundles/web/images/user-image.svg');
$('#game-actions-box, #game-my-clock, #game-opp-clock').hide();
switchView('view-game');
setTimeout(() => { board = Chessboard('board', buildBoardConfig('start', 'w')); $(window).resize(); }, 50);
if(stockfishWorker) stockfishWorker.postMessage("uci");
}
function confirmLeaveGame() {
if(isBot || isAnalysisMode || $('#replay-actions-box').is(':visible')) switchView('view-home');
else if(confirm("Are you sure you want to leave? If the game is active, you might lose.")) { clearInterval(clockInterval); switchView('view-home'); }
}
const puz=[{f:'r1b1k2r/ppppnppp/2n2q2/2b5/3NP3/2P1B3/PP3PPP/RN1QKB1R w KQkq - 0 1',s:'d4b5'},{f:'6k1/1p3ppp/p7/8/8/2Q5/q4PPP/2R3K1 w - - 0 1',s:'c3c8'},{f:'r2qk2r/pb1nbppp/1pn1p3/2ppP3/3P4/2PB1NN1/PP3PPP/R1BQK2R w KQkq - 1 10',s:'d3h7'},{f:'4r1k1/1p3ppp/p7/8/8/2Q5/q4PPP/2R3K1 w - - 0 1',s:'c3c8'},{f:'r1bq1rk1/1pp2ppp/p1np1n2/2b1p3/2B1P3/2NP1N2/PPP2PPP/R1BQ1RK1 w - - 0 1',s:'c1g5'}];
function loadPuzzles(){
let h=''; puz.forEach((p,i)=>{
h+=`<div class="menu-item" onclick="playPuz(${i})">
<div class="menu-icon-box" style="background:rgba(245, 158, 11, 0.2); border-color: rgba(245, 158, 11, 0.4);"><i class="fa-solid fa-puzzle-piece" style="color:var(--warning); filter: drop-shadow(0 0 8px rgba(245, 158, 11, 0.4));"></i></div>
<div class="menu-text"><h3>Puzzle #${i+1}</h3><p>Find the correct move</p></div>
</div>`});
$('#puzzle-container').html(h);
}
function playPuz(i){
isBot=false; isAnalysisMode=false; isFastEvaluating=false; game.load(puz[i].f);
setupLiveGameUI(); $('#game-header-text').text('Puzzle');
$('#game-opp-name').text(`Puzzle #${i+1}`); $('#game-my-name').text("Solve it");
$('#game-actions-box, #game-my-clock, #game-opp-clock').hide();
switchView('view-game');
setTimeout(() => {
board = Chessboard('board',{
draggable: true, position: puz[i].f, pieceTheme: 'https://chessboardjs.com/img/chesspieces/wikipedia/{piece}.png',
onDrop: (s,t) => {
if(s+t === puz[i].s) { toast("Correct!"); setTimeout(()=>switchView('view-puzzles'),1000); }
else { toast("Wrong move!"); return 'snapback'; }
}
}); $(window).resize();
}, 50);
}
</script>
</body>
</html>