1698 lines
49 KiB
PHP
1698 lines
49 KiB
PHP
<?php
|
||
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/session_bootstrap.php';
|
||
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/user_avatar.php';
|
||
|
||
// Sprawdzenie czy użytkownik jest zalogowany
|
||
if (!isset($_SESSION['logged_in']) || !$_SESSION['logged_in']) {
|
||
// Przekierowanie do strony logowania
|
||
header('Location: /login/index.php');
|
||
exit();
|
||
}
|
||
|
||
if (!og_session_has_valid_username()) {
|
||
header('Location: /account/profile/?error=' . urlencode('Aby grać w ping-ponga musisz najpierw ustawić poprawny username.') . '&username_required=1&focus=username');
|
||
exit();
|
||
}
|
||
|
||
// Sprawdzenie zawieszenia konta (tylko do blokady gry online, bot jest dostępny)
|
||
$ppSuspended = false;
|
||
$ppSuspReason = '';
|
||
$ppSuspUntil = '';
|
||
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/account_suspension.php';
|
||
try {
|
||
$pdo = og_session_get_pdo();
|
||
if ($pdo instanceof PDO) {
|
||
$suspension = og_is_current_user_suspended($pdo);
|
||
$ppSuspended = !empty($suspension['is_suspended']);
|
||
$ppSuspReason = (string)($suspension['reason'] ?? '');
|
||
$ppSuspUntil = (string)($suspension['suspended_until'] ?? '');
|
||
}
|
||
} catch (Throwable $e) {
|
||
// Fail open
|
||
}
|
||
|
||
// Admini też mogą grać w ping-ponga
|
||
$ppAvatarFile = null;
|
||
$ppAvatarUrl = null;
|
||
if (isset($pdo) && $pdo instanceof PDO) {
|
||
$ppAvatarFile = og_get_user_avatar_file($pdo, (int)($_SESSION['user_id'] ?? 0));
|
||
$ppAvatarUrl = og_avatar_file_to_url($ppAvatarFile);
|
||
}
|
||
?>
|
||
<!--
|
||
Author: Wspólnie
|
||
Author URL: https://togethere.cloud
|
||
-->
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Ping-pong | kontakt: wspolpraca@togethere.cloud</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta charset="utf-8">
|
||
<meta name="keywords" content="projekty przyszłości"/>
|
||
<link rel="stylesheet" href="/css/header.css" type="text/css" media="all"/>
|
||
<link rel="stylesheet" href="/css/footer.css" type="text/css" media="all"/>
|
||
<link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css" media="all">
|
||
<link href="/css/style.css" rel="stylesheet" type="text/css" media="all"/>
|
||
<link href="//fonts.googleapis.com/css?family=Lato:400,500,600,700,800,900" rel="stylesheet">
|
||
|
||
<!-- Game Scripts -->
|
||
<script src="/disciplines/ping-pong/js/anti-tamper.js"></script>
|
||
<script src="/disciplines/ping-pong/js/audio-manager.js"></script>
|
||
<script src="/disciplines/ping-pong/js/bot-ai.js"></script>
|
||
<script src="/disciplines/ping-pong/js/game.js"></script>
|
||
<script src="/disciplines/ping-pong/js/ui-manager.js"></script>
|
||
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
background:
|
||
radial-gradient(circle at top, rgba(0, 255, 247, 0.18), transparent 32%),
|
||
radial-gradient(circle at 80% 20%, rgba(255, 0, 110, 0.18), transparent 28%),
|
||
linear-gradient(180deg, #050816 0%, #0c1026 38%, #06070d 100%);
|
||
font-family: 'Lato', sans-serif;
|
||
overflow-x: hidden;
|
||
min-height: 100vh;
|
||
margin: 0;
|
||
padding: 0;
|
||
cursor: url('/disciplines/ping-pong/img/cursor-small.png') 10 10,
|
||
url('/disciplines/ping-pong/img/cursor.png') 16 16,
|
||
default;
|
||
}
|
||
|
||
.menu-container,
|
||
.menu-button,
|
||
.back-button {
|
||
cursor: url('/disciplines/ping-pong/img/cursor-small.png') 10 10,
|
||
url('/disciplines/ping-pong/img/cursor.png') 16 16,
|
||
default;
|
||
}
|
||
|
||
body.game-active,
|
||
body.game-active #gameCanvas {
|
||
cursor: url('/disciplines/ping-pong/img/cursor.png') 16 16, crosshair;
|
||
}
|
||
|
||
body.game-active {
|
||
overflow: hidden;
|
||
}
|
||
|
||
body.game-active .back-button {
|
||
cursor: url('/disciplines/ping-pong/img/cursor.png') 16 16, default;
|
||
}
|
||
|
||
body.game-active #gameContainer {
|
||
justify-content: center;
|
||
padding-top: 20px;
|
||
padding-bottom: 20px;
|
||
}
|
||
|
||
#gameContainer {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
min-height: 100vh;
|
||
padding: 32px clamp(18px, 3vw, 38px) 60px;
|
||
box-sizing: border-box;
|
||
gap: 18px;
|
||
position: relative;
|
||
}
|
||
|
||
h1 {
|
||
color: #00fff7;
|
||
text-shadow: 0 0 20px #00fff7, 0 0 40px #00fff7;
|
||
font-size: clamp(1.8rem, 4vw, 3.6rem);
|
||
margin: 0;
|
||
text-align: center;
|
||
animation: neonPulse 2s ease-in-out infinite;
|
||
letter-spacing: 0.28em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
@keyframes neonPulse {
|
||
0%, 100% { text-shadow: 0 0 20px #00fff7, 0 0 40px #00fff7; }
|
||
50% { text-shadow: 0 0 30px #00fff7, 0 0 60px #00fff7, 0 0 80px #00fff7; }
|
||
}
|
||
|
||
.menu-container {
|
||
width: min(1440px, 100%);
|
||
background: linear-gradient(180deg, rgba(8, 13, 31, 0.92) 0%, rgba(5, 7, 16, 0.92) 100%);
|
||
border: 1px solid rgba(0, 255, 247, 0.38);
|
||
border-radius: 32px;
|
||
padding: clamp(24px, 3vw, 44px);
|
||
box-shadow: 0 28px 90px rgba(0, 0, 0, 0.45),
|
||
0 0 0 1px rgba(0, 255, 247, 0.08) inset,
|
||
inset 0 0 120px rgba(0, 255, 247, 0.05);
|
||
animation: slideInGlow 0.6s ease-out;
|
||
overflow: visible;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.menu-shell {
|
||
display: grid;
|
||
gap: 28px;
|
||
}
|
||
|
||
.menu-hero {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 24px;
|
||
align-items: start;
|
||
}
|
||
|
||
.player-lobby-navbar {
|
||
width: min(1440px, 100%);
|
||
}
|
||
|
||
body.game-active .player-lobby-navbar {
|
||
display: none;
|
||
}
|
||
|
||
.hero-panel,
|
||
.modes-stage,
|
||
.difficulty-shell {
|
||
position: relative;
|
||
border-radius: 28px;
|
||
border: 1px solid rgba(108, 237, 255, 0.18);
|
||
background: linear-gradient(180deg, rgba(13, 18, 35, 0.92) 0%, rgba(6, 9, 19, 0.96) 100%);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.hero-panel {
|
||
padding: clamp(24px, 3vw, 36px);
|
||
min-height: 420px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 30px;
|
||
}
|
||
|
||
.hero-panel::before,
|
||
.modes-stage::before,
|
||
.difficulty-shell::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.04), transparent 40%, rgba(0, 255, 247, 0.05));
|
||
pointer-events: none;
|
||
}
|
||
|
||
|
||
|
||
.hero-copy {
|
||
position: relative;
|
||
z-index: 1;
|
||
width: min(100%, 1180px);
|
||
max-width: none;
|
||
margin-inline: auto;
|
||
display: grid;
|
||
gap: 18px;
|
||
}
|
||
|
||
.hero-kicker {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
justify-self: start;
|
||
width: fit-content;
|
||
max-width: max-content;
|
||
padding: 8px 14px;
|
||
border-radius: 999px;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
background: rgba(255, 255, 255, 0.05);
|
||
color: #b7e7ff;
|
||
font-size: 0.76rem;
|
||
letter-spacing: 0.16em;
|
||
text-transform: uppercase;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.hero-title {
|
||
font-size: clamp(2.7rem, 6vw, 5.1rem);
|
||
line-height: 0.92;
|
||
font-weight: 900;
|
||
color: #f7fbff;
|
||
width: 100%;
|
||
max-width: none;
|
||
letter-spacing: -0.03em;
|
||
text-wrap: balance;
|
||
}
|
||
|
||
.hero-title strong {
|
||
display: inline;
|
||
color: #ffffff;
|
||
text-shadow: 0 0 30px rgba(255, 255, 255, 0.08);
|
||
}
|
||
|
||
.hero-description {
|
||
margin-top: 0;
|
||
width: 100%;
|
||
max-width: none;
|
||
font-size: 1rem;
|
||
line-height: 1.8;
|
||
color: rgba(221, 232, 243, 0.8);
|
||
}
|
||
|
||
.hero-actions {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
flex-wrap: nowrap;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-top: 6px;
|
||
width: 100%;
|
||
}
|
||
|
||
#mainMenu .hero-actions .menu-button {
|
||
flex: 1 1 0;
|
||
width: 100%;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.hero-metrics {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 14px;
|
||
width: min(100%, 1180px);
|
||
margin-inline: auto;
|
||
}
|
||
|
||
.hero-metric {
|
||
padding: 16px 18px;
|
||
border-radius: 20px;
|
||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02));
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
}
|
||
|
||
.hero-metric.wide {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
.hero-metric-value {
|
||
font-size: 1.28rem;
|
||
font-weight: 800;
|
||
color: #ffffff;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.hero-metric-label {
|
||
font-size: 0.84rem;
|
||
line-height: 1.55;
|
||
color: rgba(221, 232, 243, 0.68);
|
||
}
|
||
|
||
/* Panel z regułami/snapshotem ustawień */
|
||
.rules-panel {
|
||
margin: 0;
|
||
padding: 20px 22px;
|
||
border-radius: 22px;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
background: linear-gradient(90deg, rgba(255, 255, 255, 0.05), rgba(10, 16, 34, 0.72));
|
||
color: #dffcff;
|
||
font-size: 0.98em;
|
||
line-height: 1.6;
|
||
}
|
||
.rules-grid { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 12px; }
|
||
.rule-chip {
|
||
background: rgba(4, 13, 31, 0.6);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 16px;
|
||
padding: 12px 14px;
|
||
text-align: center;
|
||
font-weight: 700;
|
||
color: #ffffff;
|
||
}
|
||
.rules-panel .caption { font-weight: 800; margin-bottom: 12px; color: #8fdcff; text-transform: uppercase; letter-spacing: 0.14em; font-size: 0.74rem; }
|
||
.rules-panel .special { margin-top: 10px; opacity: 0.9; }
|
||
|
||
.player-lobby-card {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 16px;
|
||
margin-bottom: 0;
|
||
padding: 20px;
|
||
border-radius: 26px;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
background: linear-gradient(180deg, rgba(18, 27, 50, 0.9) 0%, rgba(7, 11, 22, 0.9) 100%);
|
||
box-shadow: inset 0 0 40px rgba(255, 255, 255, 0.03);
|
||
}
|
||
|
||
.player-lobby-navbar .player-lobby-card {
|
||
padding: 18px 22px;
|
||
border-radius: 28px;
|
||
}
|
||
|
||
.player-lobby-navbar .player-lobby-grid {
|
||
grid-template-columns: repeat(8, minmax(0, 1fr));
|
||
}
|
||
|
||
.player-lobby-head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
}
|
||
|
||
.player-lobby-avatar {
|
||
width: 64px;
|
||
height: 64px;
|
||
border-radius: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
place-items: center;
|
||
font-size: 1.45em;
|
||
font-weight: 900;
|
||
color: #071018;
|
||
background: linear-gradient(135deg, #e9f8ff 0%, #7cdcff 100%);
|
||
box-shadow: 0 14px 30px rgba(124, 220, 255, 0.22);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.player-lobby-avatar img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
|
||
.player-lobby-kicker {
|
||
font-size: 0.72em;
|
||
letter-spacing: .18em;
|
||
text-transform: uppercase;
|
||
color: #8ceef9;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.player-lobby-name {
|
||
font-size: 1.8em;
|
||
font-weight: 900;
|
||
color: #ffffff;
|
||
line-height: 1.05;
|
||
overflow-wrap: anywhere;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.player-lobby-meta {
|
||
margin-top: 8px;
|
||
color: rgba(223, 252, 255, 0.8);
|
||
font-size: 0.94em;
|
||
line-height: 1.5;
|
||
overflow-wrap: anywhere;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.player-lobby-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.player-lobby-stat {
|
||
padding: 14px;
|
||
border-radius: 18px;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
background: rgba(255, 255, 255, 0.035);
|
||
}
|
||
|
||
.player-lobby-stat-label {
|
||
font-size: 0.7em;
|
||
letter-spacing: .12em;
|
||
text-transform: uppercase;
|
||
color: rgba(223, 252, 255, 0.62);
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.player-lobby-stat-value {
|
||
font-size: 1.15em;
|
||
font-weight: 800;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.modes-layout {
|
||
display: grid;
|
||
gap: 22px;
|
||
}
|
||
|
||
.mode-stack {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 18px;
|
||
}
|
||
|
||
.modes-stage,
|
||
.difficulty-shell {
|
||
padding: 24px;
|
||
}
|
||
|
||
.section-head {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: flex-start;
|
||
gap: 28px;
|
||
align-items: start;
|
||
margin-bottom: 22px;
|
||
}
|
||
|
||
.section-head > div:first-child {
|
||
display: grid;
|
||
gap: 20px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: clamp(1.7rem, 3vw, 2.6rem);
|
||
line-height: 1.02;
|
||
font-weight: 900;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.section-copy {
|
||
max-width: 44ch;
|
||
font-size: 0.96rem;
|
||
line-height: 1.7;
|
||
color: rgba(220, 232, 243, 0.7);
|
||
}
|
||
|
||
.mode-entry {
|
||
width: 100%;
|
||
padding: 0;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 24px;
|
||
background: linear-gradient(135deg, rgba(18, 27, 50, 0.96), rgba(10, 14, 28, 0.96));
|
||
color: #ffffff;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
transition: transform 0.28s ease, border-color 0.28s ease, box-shadow 0.28s ease;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.mode-entry:hover {
|
||
transform: translateY(-4px);
|
||
border-color: rgba(163, 227, 255, 0.36);
|
||
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.26);
|
||
}
|
||
|
||
.mode-entry-shell {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 22px;
|
||
padding: 24px;
|
||
min-height: 100%;
|
||
}
|
||
|
||
.mode-entry-copy-wrap {
|
||
display: grid;
|
||
gap: 16px;
|
||
}
|
||
|
||
.mode-entry-kicker {
|
||
font-size: 0.74rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.18em;
|
||
color: rgba(173, 215, 240, 0.62);
|
||
}
|
||
|
||
.mode-entry-title {
|
||
font-size: clamp(1.9rem, 2.5vw, 2.55rem);
|
||
line-height: 0.98;
|
||
font-weight: 900;
|
||
letter-spacing: -0.03em;
|
||
}
|
||
|
||
.mode-entry-copy {
|
||
font-size: 0.98rem;
|
||
line-height: 1.75;
|
||
color: rgba(224, 232, 241, 0.78);
|
||
max-width: 48ch;
|
||
}
|
||
|
||
.mode-entry-footer {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
|
||
.mode-entry-point {
|
||
padding: 8px 12px;
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
font-size: 0.85rem;
|
||
color: #f4f8fb;
|
||
}
|
||
|
||
.mode-entry-cta {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
width: 100%;
|
||
margin-top: 4px;
|
||
padding: 14px 16px;
|
||
border-radius: 16px;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
font-size: 0.86rem;
|
||
font-weight: 800;
|
||
letter-spacing: 0.16em;
|
||
text-transform: uppercase;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.mode-entry-cta::after {
|
||
content: '->';
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.mode-entry.online {
|
||
background: linear-gradient(135deg, rgba(18, 42, 68, 0.96), rgba(7, 12, 24, 0.96));
|
||
}
|
||
|
||
.mode-entry.bot {
|
||
background: linear-gradient(135deg, rgba(66, 18, 38, 0.96), rgba(19, 8, 19, 0.96));
|
||
}
|
||
|
||
.difficulty-shell {
|
||
display: grid;
|
||
grid-template-columns: minmax(260px, 0.78fr) minmax(0, 1.22fr);
|
||
gap: 24px;
|
||
}
|
||
|
||
.difficulty-copy {
|
||
display: grid;
|
||
align-content: start;
|
||
gap: 18px;
|
||
}
|
||
|
||
.difficulty-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.difficulty-card {
|
||
padding: 22px;
|
||
border-radius: 22px;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
|
||
color: #ffffff;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
transition: transform 0.28s ease, border-color 0.28s ease, box-shadow 0.28s ease;
|
||
}
|
||
|
||
.difficulty-card:hover {
|
||
transform: translateY(-4px);
|
||
border-color: rgba(163, 227, 255, 0.36);
|
||
box-shadow: 0 18px 38px rgba(0, 0, 0, 0.24);
|
||
}
|
||
|
||
.difficulty-badge {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 46px;
|
||
height: 46px;
|
||
border-radius: 14px;
|
||
margin-bottom: 18px;
|
||
font-size: 1rem;
|
||
font-weight: 900;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
}
|
||
|
||
.difficulty-title {
|
||
font-size: 1.3rem;
|
||
font-weight: 900;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.difficulty-desc {
|
||
font-size: 0.95rem;
|
||
line-height: 1.7;
|
||
color: rgba(220, 232, 243, 0.74);
|
||
}
|
||
|
||
.difficulty-card.easy .difficulty-badge {
|
||
background: rgba(103, 232, 180, 0.18);
|
||
color: #9effd1;
|
||
}
|
||
|
||
.difficulty-card.medium .difficulty-badge {
|
||
background: rgba(255, 214, 102, 0.18);
|
||
color: #ffe08c;
|
||
}
|
||
|
||
.difficulty-card.hard .difficulty-badge {
|
||
background: rgba(255, 130, 130, 0.18);
|
||
color: #ff9898;
|
||
}
|
||
|
||
.difficulty-card.extreme {
|
||
background: linear-gradient(180deg, rgba(56, 10, 35, 0.92), rgba(16, 7, 16, 0.98));
|
||
}
|
||
|
||
.difficulty-card.extreme .difficulty-badge {
|
||
background: rgba(210, 98, 255, 0.18);
|
||
color: #e7a4ff;
|
||
}
|
||
|
||
.menu-title {
|
||
font-size: clamp(1.8rem, 3vw, 2.8rem);
|
||
text-align: left;
|
||
margin-bottom: 0;
|
||
font-weight: 900;
|
||
color: #ffffff;
|
||
letter-spacing: -0.03em;
|
||
background: none;
|
||
-webkit-text-fill-color: initial;
|
||
filter: none;
|
||
animation: none;
|
||
}
|
||
|
||
.buttons-grid {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 14px;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.menu-button {
|
||
appearance: none;
|
||
background: linear-gradient(135deg, #dff8ff 0%, #8de7ff 100%);
|
||
color: #071018;
|
||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||
padding: 15px 22px;
|
||
font-size: 0.92rem;
|
||
font-weight: 800;
|
||
border-radius: 999px;
|
||
margin: 0;
|
||
transition: transform 0.28s ease, box-shadow 0.28s ease, border-color 0.28s ease, background 0.28s ease;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10px;
|
||
width: auto;
|
||
position: relative;
|
||
overflow: hidden;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.14em;
|
||
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.24);
|
||
}
|
||
|
||
.menu-button:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 16px 30px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.menu-button.secondary {
|
||
background: transparent;
|
||
color: #e6f3fb;
|
||
border-color: rgba(255, 255, 255, 0.12);
|
||
box-shadow: none;
|
||
}
|
||
|
||
.menu-button.secondary:hover {
|
||
border-color: rgba(163, 227, 255, 0.34);
|
||
background: rgba(255, 255, 255, 0.04);
|
||
}
|
||
|
||
.menu-button.bot {
|
||
background: linear-gradient(135deg, #ffd9e8 0%, #ff9bc3 100%);
|
||
color: #2a0916;
|
||
}
|
||
|
||
@keyframes slideInGlow {
|
||
0% {
|
||
opacity: 0;
|
||
transform: scale(0.95);
|
||
box-shadow: 0 0 0 rgba(0, 255, 247, 0);
|
||
}
|
||
100% {
|
||
opacity: 1;
|
||
transform: scale(1);
|
||
box-shadow: 0 0 50px rgba(0, 255, 247, 0.4);
|
||
}
|
||
}
|
||
|
||
.menu-caption {
|
||
font-size: 0.78rem;
|
||
letter-spacing: 0.18em;
|
||
text-transform: uppercase;
|
||
color: #8fdcff;
|
||
}
|
||
|
||
@keyframes titleShine {
|
||
0%, 100% { filter: drop-shadow(0 0 15px rgba(0, 255, 247, 0.8)); }
|
||
50% { filter: drop-shadow(0 0 25px rgba(0, 255, 247, 1)); }
|
||
}
|
||
|
||
.menu-button.extreme {
|
||
background: linear-gradient(135deg, #ffccd8 0%, #d58bff 100%);
|
||
color: #210617;
|
||
}
|
||
|
||
@keyframes extremePulse {
|
||
0%, 100% {
|
||
box-shadow: 0 4px 20px rgba(139, 0, 255, 0.6),
|
||
0 0 30px rgba(255, 0, 0, 0.3);
|
||
}
|
||
50% {
|
||
box-shadow: 0 8px 40px rgba(139, 0, 255, 1),
|
||
0 0 60px rgba(255, 0, 0, 0.8),
|
||
0 0 80px rgba(0, 0, 0, 0.5);
|
||
}
|
||
}
|
||
|
||
.menu-button.extreme:hover {
|
||
box-shadow: 0 8px 30px rgba(139, 0, 255, 1),
|
||
0 0 70px rgba(255, 0, 0, 0.9),
|
||
0 0 100px rgba(0, 0, 0, 0.7),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||
border-color: rgba(139, 0, 255, 1);
|
||
transform: translateY(-3px) scale(1.05);
|
||
}
|
||
|
||
.menu-button.extreme:active {
|
||
box-shadow: 0 4px 15px rgba(139, 0, 255, 0.7);
|
||
}
|
||
|
||
.menu-button:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
@media (max-width: 1180px) {
|
||
.menu-hero,
|
||
.difficulty-shell {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.mode-stack {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.hero-title {
|
||
max-width: none;
|
||
}
|
||
|
||
.player-lobby-navbar .player-lobby-grid {
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
@media (max-width: 920px) {
|
||
#gameContainer {
|
||
padding-top: 24px;
|
||
}
|
||
|
||
#mainMenu .hero-actions {
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
#mainMenu .hero-actions .menu-button {
|
||
flex: 1 1 calc(50% - 6px);
|
||
}
|
||
|
||
#mainMenu .hero-actions .menu-button.secondary {
|
||
flex-basis: 100%;
|
||
}
|
||
|
||
.player-lobby-grid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.hero-metrics {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
body {
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
#gameContainer {
|
||
padding-left: 14px;
|
||
padding-right: 14px;
|
||
}
|
||
|
||
#mainMenu .hero-actions .menu-button {
|
||
flex-basis: 100%;
|
||
}
|
||
|
||
.player-lobby-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.hero-metrics,
|
||
.rules-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.difficulty-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.player-lobby-card,
|
||
.menu-container,
|
||
.modes-stage,
|
||
.difficulty-shell {
|
||
padding: 18px;
|
||
}
|
||
|
||
.player-lobby-navbar .player-lobby-grid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.section-head {
|
||
flex-direction: column;
|
||
align-items: start;
|
||
}
|
||
|
||
.mode-entry-shell {
|
||
padding: 18px;
|
||
}
|
||
}
|
||
|
||
#gameCanvas {
|
||
border: 3px solid #00fff7;
|
||
border-radius: 10px;
|
||
box-shadow: 0 0 40px rgba(0, 255, 247, 0.5);
|
||
background: #0a0a0a;
|
||
display: none;
|
||
outline: none;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
#gameCanvas:focus {
|
||
box-shadow: 0 0 50px rgba(0, 255, 247, 0.8);
|
||
}
|
||
|
||
.bot-arena-decor {
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
overflow: hidden;
|
||
display: none;
|
||
}
|
||
|
||
body.game-active .bot-arena-decor {
|
||
display: block;
|
||
}
|
||
|
||
.bot-decor-item {
|
||
position: absolute;
|
||
display: block;
|
||
font-size: clamp(22px, 3.2vw, 38px);
|
||
opacity: .16;
|
||
filter: grayscale(1) brightness(.42) drop-shadow(0 10px 22px rgba(0,0,0,.38));
|
||
animation: botArenaFloat 16s ease-in-out infinite;
|
||
transform: translate3d(0,0,0);
|
||
user-select: none;
|
||
}
|
||
|
||
.bot-decor-item.item-1 { top: 10%; left: 4%; animation-duration: 18s; }
|
||
.bot-decor-item.item-2 { top: 20%; left: 15%; animation-duration: 14s; animation-delay: -6s; }
|
||
.bot-decor-item.item-3 { top: 72%; left: 10%; animation-duration: 17s; animation-delay: -9s; }
|
||
.bot-decor-item.item-4 { top: 8%; right: 8%; animation-duration: 19s; animation-delay: -4s; }
|
||
.bot-decor-item.item-5 { top: 58%; right: 4%; animation-duration: 15s; animation-delay: -7s; }
|
||
.bot-decor-item.item-6 { bottom: 12%; right: 16%; animation-duration: 20s; animation-delay: -11s; }
|
||
.bot-decor-item.item-7 { bottom: 18%; left: 22%; animation-duration: 13s; animation-delay: -5s; }
|
||
.bot-decor-item.item-8 { top: 46%; left: 50%; animation-duration: 21s; animation-delay: -10s; }
|
||
|
||
@keyframes botArenaFloat {
|
||
0%, 100% { transform: translate3d(0,0,0) rotate(0deg) scale(1); }
|
||
25% { transform: translate3d(12px,-18px,0) rotate(5deg) scale(1.08); }
|
||
50% { transform: translate3d(-10px,12px,0) rotate(-4deg) scale(.94); }
|
||
75% { transform: translate3d(18px,8px,0) rotate(3deg) scale(1.03); }
|
||
}
|
||
|
||
.score-container {
|
||
display: none;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 700px;
|
||
margin: 0;
|
||
gap: 0;
|
||
}
|
||
|
||
.score-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.score-label {
|
||
font-size: 0.85em;
|
||
font-weight: bold;
|
||
text-transform: uppercase;
|
||
letter-spacing: 2px;
|
||
}
|
||
|
||
.score-label.player {
|
||
color: #0080ff;
|
||
text-shadow: 0 0 10px #0080ff;
|
||
}
|
||
|
||
.score-label.bot {
|
||
color: #ff006e;
|
||
text-shadow: 0 0 10px #ff006e;
|
||
}
|
||
|
||
.score-label.timer {
|
||
color: #00fff7;
|
||
text-shadow: 0 0 10px #00fff7;
|
||
}
|
||
|
||
nav,
|
||
footer {
|
||
display: none !important;
|
||
}
|
||
|
||
.score {
|
||
font-size: 2em;
|
||
font-weight: bold;
|
||
text-shadow: 0 0 10px;
|
||
}
|
||
|
||
.score.player {
|
||
color: #0080ff;
|
||
text-shadow: 0 0 20px #0080ff;
|
||
}
|
||
|
||
.score.bot {
|
||
color: #ff006e;
|
||
text-shadow: 0 0 20px #ff006e;
|
||
}
|
||
.score.timer {
|
||
color: #00fff7;
|
||
text-shadow: 0 0 15px #00fff7;
|
||
font-size: 1.8em;
|
||
}
|
||
.back-button {
|
||
background: rgba(8, 14, 28, 0.88);
|
||
color: #eef7fd;
|
||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||
padding: 14px 24px;
|
||
font-size: 0.9em;
|
||
font-weight: 800;
|
||
border-radius: 999px;
|
||
margin-top: 25px;
|
||
box-shadow: 0 14px 32px rgba(0, 0, 0, 0.28);
|
||
transition: transform 0.28s ease, box-shadow 0.28s ease, border-color 0.28s ease, background 0.28s ease;
|
||
display: none;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.14em;
|
||
}
|
||
|
||
.back-button:hover {
|
||
background: rgba(18, 28, 54, 0.96);
|
||
box-shadow: 0 18px 38px rgba(0, 0, 0, 0.34);
|
||
border-color: rgba(143, 220, 255, 0.4);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.back-button:active {
|
||
transform: translateY(0);
|
||
box-shadow: 0 10px 18px rgba(0, 0, 0, 0.24);
|
||
}
|
||
|
||
.modal {
|
||
position: fixed;
|
||
z-index: 1000;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
animation: fadeIn 0.3s ease;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.modal[style*="display: block"] {
|
||
display: flex !important;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
|
||
.modal-content {
|
||
background: linear-gradient(180deg, rgba(13, 18, 35, 0.98) 0%, rgba(6, 9, 19, 0.98) 100%);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 28px;
|
||
padding: 40px;
|
||
width: 90%;
|
||
max-width: 500px;
|
||
text-align: center;
|
||
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.48);
|
||
animation: modalSlide 0.5s ease;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
@keyframes modalSlide {
|
||
from { transform: translateY(-100px); opacity: 0; }
|
||
to { transform: translateY(0); opacity: 1; }
|
||
}
|
||
|
||
.modal-title {
|
||
color: #f7fbff;
|
||
font-size: 2.5em;
|
||
margin-bottom: 20px;
|
||
text-shadow: none;
|
||
width: 100%;
|
||
}
|
||
|
||
.modal-text {
|
||
color: #ffffff;
|
||
font-size: 1.3em;
|
||
margin-bottom: 30px;
|
||
line-height: 1.5;
|
||
width: 100%;
|
||
word-wrap: break-word;
|
||
}
|
||
|
||
.win-modal .modal-title {
|
||
color: #00ff00;
|
||
text-shadow: 0 0 20px #00ff00;
|
||
animation: winPulse 1s ease-in-out infinite;
|
||
}
|
||
|
||
.lose-modal .modal-title {
|
||
color: #ff006e;
|
||
text-shadow: 0 0 20px #ff006e;
|
||
}
|
||
|
||
@keyframes winPulse {
|
||
0%, 100% { transform: scale(1); }
|
||
50% { transform: scale(1.1); }
|
||
}
|
||
|
||
/* Style dla przycisków w modalach */
|
||
.modal-content .menu-button {
|
||
width: min(100%, 320px);
|
||
padding: 16px 24px;
|
||
font-size: 0.95rem;
|
||
margin: 10px 0;
|
||
font-weight: 800;
|
||
letter-spacing: 0.14em;
|
||
}
|
||
|
||
.modal-content .menu-button:first-of-type {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.win-modal .menu-button:not(.bot) {
|
||
background: linear-gradient(135deg, #00ff88 0%, #00ccff 100%);
|
||
border-color: rgba(0, 255, 136, 0.5);
|
||
box-shadow: 0 6px 25px rgba(0, 255, 136, 0.5),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.win-modal .menu-button:not(.bot):hover {
|
||
box-shadow: 0 10px 35px rgba(0, 255, 136, 0.7),
|
||
0 0 60px rgba(0, 255, 136, 0.4),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||
border-color: rgba(0, 255, 136, 0.9);
|
||
}
|
||
|
||
.lose-modal .menu-button:not(.bot) {
|
||
background: linear-gradient(135deg, #ff006e 0%, #ff0099 100%);
|
||
border-color: rgba(255, 0, 110, 0.5);
|
||
box-shadow: 0 6px 25px rgba(255, 0, 110, 0.5),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.lose-modal .menu-button:not(.bot):hover {
|
||
box-shadow: 0 10px 35px rgba(255, 0, 110, 0.7),
|
||
0 0 60px rgba(255, 0, 110, 0.4),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||
border-color: rgba(255, 0, 110, 0.9);
|
||
}
|
||
|
||
.modal-content .menu-button.bot {
|
||
background: linear-gradient(135deg, #6b46c1 0%, #8b5cf6 100%);
|
||
border-color: rgba(139, 92, 246, 0.5);
|
||
box-shadow: 0 6px 25px rgba(139, 92, 246, 0.5),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.modal-content .menu-button.bot:hover {
|
||
box-shadow: 0 10px 35px rgba(139, 92, 246, 0.7),
|
||
0 0 60px rgba(139, 92, 246, 0.4),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||
border-color: rgba(139, 92, 246, 0.9);
|
||
}
|
||
|
||
.game-controls-hint {
|
||
color: #00fff7;
|
||
font-size: 0.9em;
|
||
margin-top: 10px;
|
||
text-align: center;
|
||
display: none;
|
||
opacity: 0.7;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- Tutaj PHP sprawdza sesje czy zalogowany i wczytuje odpowiednią nawigację -->
|
||
<?php
|
||
if (!empty($_SESSION['logged_in'])) {
|
||
if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin') {
|
||
include __DIR__ . '/../../global/navLoginedAdmin.php';
|
||
} else {
|
||
include __DIR__ . '/../../global/navLogined.php';
|
||
}
|
||
} else {
|
||
include __DIR__ . '/../../global/navNoLogined.php';
|
||
}
|
||
?>
|
||
|
||
<div id="gameContainer">
|
||
<div class="bot-arena-decor" aria-hidden="true">
|
||
<span class="bot-decor-item item-1">🌑</span>
|
||
<span class="bot-decor-item item-2">🕶️</span>
|
||
<span class="bot-decor-item item-3">🖤</span>
|
||
<span class="bot-decor-item item-4">🎮</span>
|
||
<span class="bot-decor-item item-5">♠️</span>
|
||
<span class="bot-decor-item item-6">🌘</span>
|
||
<span class="bot-decor-item item-7">🎱</span>
|
||
<span class="bot-decor-item item-8">🕹️</span>
|
||
</div>
|
||
|
||
<div class="player-lobby-navbar">
|
||
<div class="player-lobby-card" id="playerLobbyCard">
|
||
<div class="player-lobby-head">
|
||
<div class="player-lobby-avatar" id="playerLobbyAvatar"><?php echo strtoupper(substr((string) ($_SESSION['username'] ?? 'G'), 0, 1)); ?></div>
|
||
<div>
|
||
<div class="player-lobby-kicker">Profil aktywnego gracza</div>
|
||
<div class="player-lobby-name" id="playerLobbyName"><?php echo htmlspecialchars((string) ($_SESSION['username'] ?? 'Gracz'), ENT_QUOTES, 'UTF-8'); ?></div>
|
||
<div class="player-lobby-meta" id="playerLobbyMeta">Ładowanie danych konta przed wyborem trybu…</div>
|
||
</div>
|
||
</div>
|
||
<div class="player-lobby-grid" id="playerLobbyGrid"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Menu główne -->
|
||
<div id="mainMenu" class="menu-container">
|
||
<div class="menu-shell">
|
||
<div class="menu-hero">
|
||
<section class="hero-panel">
|
||
<div class="hero-copy">
|
||
<div class="hero-kicker">Ping-Pong</div>
|
||
<div class="hero-title">Stół gotowy. <strong>Wybierz tempo.</strong></div>
|
||
<div class="hero-description">Wejdź od razu do gry. Jeśli chcesz rywalizacji, wskakujesz do online. Jeśli chcesz złapać rytm przed meczem, odpalasz trening z botem.</div>
|
||
<div class="hero-actions">
|
||
<button class="menu-button" onclick="showOnlineMessage()">Graj online</button>
|
||
<button class="menu-button bot" onclick="showDifficultyMenu()">Trening z botem</button>
|
||
<button class="menu-button secondary" onclick="window.location.href='/disciplines/'">Wróć do dyscyplin</button>
|
||
</div>
|
||
</div>
|
||
<div class="hero-metrics">
|
||
<div class="hero-metric">
|
||
<div class="hero-metric-value">Online 1v1</div>
|
||
<div class="hero-metric-label">Wchodzisz do kolejki i grasz przeciwko drugiej osobie.</div>
|
||
</div>
|
||
<div class="hero-metric">
|
||
<div class="hero-metric-value">4 poziomy</div>
|
||
<div class="hero-metric-label">Od lekkiej rozgrzewki po bot, który nie oddaje piłki za darmo.</div>
|
||
</div>
|
||
<div class="hero-metric wide">
|
||
<div class="hero-metric-value">Jasne zasady</div>
|
||
<div class="hero-metric-label">Najważniejsze reguły masz obok trybów, więc nie szukasz ich po całej stronie.</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
<div class="rules-panel" id="rulesPanel" style="display:none">
|
||
<div class="caption">Reguły meczu</div>
|
||
<div class="rules-grid">
|
||
<div class="rule-chip" id="chipPoints">Set do 11</div>
|
||
<div class="rule-chip" id="chipSets">Mecz do 3 setów</div>
|
||
<div class="rule-chip" id="chipServe">Serwis co 2</div>
|
||
</div>
|
||
<div class="special" id="specialRulesText"></div>
|
||
</div>
|
||
<div class="modes-layout">
|
||
<section class="modes-stage">
|
||
<div class="section-head">
|
||
<div>
|
||
<div class="menu-caption">Tryby gry</div>
|
||
<div class="section-title">Wybierz wejście w mecz</div>
|
||
</div>
|
||
<div class="section-copy">Masz dwa kierunki. Jeden prowadzi prosto do rywalizacji z graczem, drugi daje szybki trening bez czekania i bez zbędnych ekranów.</div>
|
||
</div>
|
||
<div class="mode-stack">
|
||
<button class="mode-entry online" onclick="showOnlineMessage()">
|
||
<span class="mode-entry-shell">
|
||
<span class="mode-entry-copy-wrap">
|
||
<span class="mode-entry-kicker">Tryb rankingowy</span>
|
||
<span class="mode-entry-title">Graj online</span>
|
||
<span class="mode-entry-copy">Wchodzisz do kolejki 1v1 i grasz o wynik przeciwko drugiej osobie</span>
|
||
<span class="mode-entry-footer">
|
||
<span class="mode-entry-point">kolejka 1v1</span>
|
||
<span class="mode-entry-point">set do 11</span>
|
||
<span class="mode-entry-point">mecz do 3 setów</span>
|
||
</span>
|
||
<span class="mode-entry-cta">Wejdź do lobby</span>
|
||
</span>
|
||
</span>
|
||
</button>
|
||
<button class="mode-entry bot" onclick="showDifficultyMenu()">
|
||
<span class="mode-entry-shell">
|
||
<span class="mode-entry-copy-wrap">
|
||
<span class="mode-entry-kicker">Tryb treningowy</span>
|
||
<span class="mode-entry-title">Graj z botem</span>
|
||
<span class="mode-entry-copy">Dobry, jeśli chcesz wyczuć odbicie, złapać rytm albo po prostu pograć bez czekania na przeciwnika i bez ciśnienia wyniku.</span>
|
||
<span class="mode-entry-footer">
|
||
<span class="mode-entry-point">4 poziomy trudności</span>
|
||
<span class="mode-entry-point">natychmiastowy start</span>
|
||
<span class="mode-entry-point">trening tempa</span>
|
||
</span>
|
||
<span class="mode-entry-cta">Wybierz poziom</span>
|
||
</span>
|
||
</span>
|
||
</button>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Menu wyboru poziomu trudności -->
|
||
<div id="difficultyMenu" class="menu-container" style="display: none;">
|
||
<div class="difficulty-shell">
|
||
<div class="difficulty-copy">
|
||
<div class="menu-caption">Solo training</div>
|
||
<div class="menu-title">Wybierz poziom trudności</div>
|
||
<div class="section-copy">Każdy poziom zmienia tempo reakcji bota i margines błędu. Zacznij nisko, jeśli chcesz wejść płynnie, albo odpal mocniejszy poziom, jeśli chcesz od razu grać agresywniej.</div>
|
||
<div class="hero-actions">
|
||
<button class="menu-button secondary" onclick="backToMainMenu()">Powrót do menu</button>
|
||
</div>
|
||
</div>
|
||
<div class="difficulty-grid">
|
||
<button class="difficulty-card easy" onclick="startGame('easy')">
|
||
<span class="difficulty-badge">01</span>
|
||
<span class="difficulty-title">Łatwy</span>
|
||
<span class="difficulty-desc">Spokojne tempo i więcej przestrzeni na ustawienie ręki. Dobry start po przerwie.</span>
|
||
</button>
|
||
<button class="difficulty-card medium" onclick="startGame('medium')">
|
||
<span class="difficulty-badge">02</span>
|
||
<span class="difficulty-title">Średni</span>
|
||
<span class="difficulty-desc">Najbardziej uniwersalny poziom. Uczy rytmu wymian i nie wybacza całkiem darmowych błędów.</span>
|
||
</button>
|
||
<button class="difficulty-card hard" onclick="startGame('hard')">
|
||
<span class="difficulty-badge">03</span>
|
||
<span class="difficulty-title">Trudny</span>
|
||
<span class="difficulty-desc">Wyższe tempo, mniej czasu na decyzję i większa kara za spóźniony ruch.</span>
|
||
</button>
|
||
<button class="difficulty-card extreme" onclick="startGame('extreme')">
|
||
<span class="difficulty-badge">04</span>
|
||
<span class="difficulty-title">Extreme</span>
|
||
<span class="difficulty-desc">Poziom na agresywną serię. Dla gracza, który chce natychmiast sprawdzić refleks i koncentrację.</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Wynik -->
|
||
<div class="score-container" id="scoreContainer">
|
||
<div class="score-wrapper">
|
||
<div class="score-label player" id="playerScoreLabel">👤 GRACZ • SETY 0</div>
|
||
<div class="score player" id="playerScore">0</div>
|
||
</div>
|
||
<div class="score-wrapper timer-wrapper">
|
||
<div class="score-label timer">⏱️ CZAS</div>
|
||
<div class="score timer" id="gameTimer">0:00</div>
|
||
</div>
|
||
<div class="score-wrapper">
|
||
<div class="score-label bot" id="botScoreLabel">🤖 BOT • SETY 0</div>
|
||
<div class="score bot" id="botScore">0</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Canvas gry -->
|
||
<canvas id="gameCanvas" width="700" height="450" tabindex="0"></canvas>
|
||
<div class="game-controls-hint" id="controlsHint">
|
||
⌨️ Sterowanie: ⬆️⬇️ Strzałki lub W/S
|
||
</div>
|
||
<button class="back-button" id="gameBackButton" onclick="endGame()">← Zakończ Grę</button>
|
||
</div>
|
||
|
||
<!-- Modal "W przygotowaniu" -->
|
||
<div id="comingSoonModal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-title">W Przygotowaniu</div>
|
||
<div class="modal-text">Ta funkcja jest obecnie w fazie rozwoju. Wkrótce będzie dostępna!</div>
|
||
<button class="menu-button" onclick="closeModal('comingSoonModal')">OK</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal wygranej -->
|
||
<div id="winModal" class="modal win-modal">
|
||
<div class="modal-content">
|
||
<div class="modal-title">🎉 WYGRAŁEŚ! 🎉</div>
|
||
<div class="modal-text">Gratulacje! Pokonałeś przeciwnika!</div>
|
||
<div class="modal-text" style="font-size: 32px; margin: 15px 0; color: #00fff7;">Czas: <span id="winTime">0:00</span></div>
|
||
<button class="menu-button" onclick="resetGame()">Zagraj Ponownie</button>
|
||
<button class="menu-button bot" onclick="endGame()">Menu Główne</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal przegranej -->
|
||
<div id="loseModal" class="modal lose-modal">
|
||
<div class="modal-content">
|
||
<div class="modal-title">💔 PRZEGRAŁEŚ 💔</div>
|
||
<div class="modal-text">Nie poddawaj się! Spróbuj jeszcze raz!</div>
|
||
<div class="modal-text" style="font-size: 32px; margin: 15px 0; color: #ff006e;">Czas: <span id="loseTime">0:00</span></div>
|
||
<button class="menu-button" onclick="resetGame()">Zagraj Ponownie</button>
|
||
<button class="menu-button bot" onclick="endGame()">Menu Główne</button>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Inicjalizacja gry po załadowaniu strony
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Utwórz instancję gry (globalną)
|
||
window.game = new PingPongGame('gameCanvas');
|
||
|
||
// Utwórz menedżer UI
|
||
window.uiManager = new UIManager(window.game);
|
||
|
||
// Bot: używamy stałych zasad/stylu (snapshoty tylko dla online)
|
||
renderBotDefaultsPanel();
|
||
loadPingPongLobbyProfile();
|
||
|
||
console.log('Ping-Pong Game initialized!');
|
||
});
|
||
|
||
async function loadPingPongLobbyProfile() {
|
||
const avatar = document.getElementById('playerLobbyAvatar');
|
||
const name = document.getElementById('playerLobbyName');
|
||
const meta = document.getElementById('playerLobbyMeta');
|
||
const grid = document.getElementById('playerLobbyGrid');
|
||
if (!grid) return;
|
||
|
||
const fallback = {
|
||
userId: <?php echo (int) ($_SESSION['user_id'] ?? 0); ?>,
|
||
username: <?php echo json_encode((string) ($_SESSION['username'] ?? 'Gracz'), JSON_UNESCAPED_UNICODE); ?>,
|
||
avatarUrl: <?php echo json_encode($ppAvatarUrl, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>,
|
||
role: <?php echo json_encode((string) ($_SESSION['role'] ?? 'user'), JSON_UNESCAPED_UNICODE); ?>,
|
||
balance: 0,
|
||
matchesPlayed: 0,
|
||
matchesWon: 0,
|
||
matchesLost: 0,
|
||
winRate: 0,
|
||
tournamentsPlayed: 0,
|
||
leaguesParticipated: 0,
|
||
totalTransactions: 0,
|
||
memberSince: '',
|
||
pingPongStats: { matchesPlayed: 0, matchesWon: 0, matchesLost: 0, matchesDraw: 0, winRate: 0, totalIncome: 0 },
|
||
};
|
||
|
||
const render = function(summary) {
|
||
const data = summary || fallback;
|
||
const username = data.username || fallback.username;
|
||
if (avatar) {
|
||
const initial = (username || 'G').charAt(0).toUpperCase();
|
||
avatar.innerHTML = '';
|
||
if (data.avatarUrl) {
|
||
const img = document.createElement('img');
|
||
img.src = String(data.avatarUrl);
|
||
img.alt = 'Avatar gracza';
|
||
img.loading = 'lazy';
|
||
img.addEventListener('error', function() {
|
||
avatar.textContent = initial;
|
||
}, { once: true });
|
||
avatar.appendChild(img);
|
||
} else {
|
||
avatar.textContent = initial;
|
||
}
|
||
}
|
||
if (name) name.textContent = username;
|
||
if (meta) {
|
||
const memberSince = data.memberSince ? new Date(data.memberSince).toLocaleDateString('pl-PL') : 'konto aktywne';
|
||
const displayRole = (data.role || fallback.role) === 'user' ? 'Player' : (data.role || fallback.role);
|
||
meta.textContent = `ID #${data.userId || fallback.userId} • ${displayRole} • ${memberSince}`;
|
||
}
|
||
|
||
const cards = [
|
||
['Saldo', `${Number(data.balance || 0).toFixed(2)} Playons`],
|
||
['Mecze PP', String((data.pingPongStats || {}).matchesPlayed || 0)],
|
||
['Win rate PP', `${Number((data.pingPongStats || {}).winRate || 0).toFixed(1)}%`],
|
||
['Turnieje', String(data.tournamentsPlayed || 0)],
|
||
['Ligi', String(data.leaguesParticipated || 0)],
|
||
['Transakcje', String(data.totalTransactions || 0)],
|
||
['Wygrane PP', String((data.pingPongStats || {}).matchesWon || 0)],
|
||
['Porażki PP', String((data.pingPongStats || {}).matchesLost || 0)],
|
||
];
|
||
|
||
grid.innerHTML = cards.map(function(card) {
|
||
return `<div class="player-lobby-stat"><div class="player-lobby-stat-label">${card[0]}</div><div class="player-lobby-stat-value">${card[1]}</div></div>`;
|
||
}).join('');
|
||
};
|
||
|
||
try {
|
||
const response = await fetch('/api/matches/ping-pong/1v1/player-summary.php', { credentials: 'include' });
|
||
const json = await response.json().catch(() => null);
|
||
if (!response.ok || !json || !json.success || !json.data) {
|
||
throw new Error('player_summary_error');
|
||
}
|
||
render(json.data);
|
||
} catch (error) {
|
||
render(fallback);
|
||
}
|
||
}
|
||
|
||
async function loadDisciplineSettingsSnapshot() {
|
||
const rulesPanel = document.getElementById('rulesPanel');
|
||
if (!rulesPanel) return;
|
||
|
||
const defaults = {
|
||
pointsToWin: 11,
|
||
setsToWin: 3,
|
||
serveRotation: 2,
|
||
specialRules: 'Deuce od 10–10, gramy do 2 przewagi.'
|
||
};
|
||
|
||
let rules = { ...defaults };
|
||
try {
|
||
const res = await fetch('/administration/disciplines/ping-pong/settings/?snapshot=true', { credentials: 'same-origin' });
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
const snap = data.snapshot || data.data || null;
|
||
if (snap && snap.rules) {
|
||
rules = {
|
||
pointsToWin: parseInt(snap.rules.pointsToWin) || defaults.pointsToWin,
|
||
setsToWin: parseInt(snap.rules.setsToWin) || defaults.setsToWin,
|
||
serveRotation: parseInt(snap.rules.serveRotation) || defaults.serveRotation,
|
||
specialRules: snap.rules.specialRules || ''
|
||
};
|
||
}
|
||
}
|
||
} catch (e) {
|
||
// Fallback na defaults gdy endpoint jest dostępny tylko dla admina
|
||
}
|
||
|
||
// Render chipów
|
||
document.getElementById('chipPoints').textContent = `Set do ${rules.pointsToWin}`;
|
||
document.getElementById('chipSets').textContent = `Mecz do ${rules.setsToWin} setów`;
|
||
document.getElementById('chipServe').textContent = `Serwis co ${rules.serveRotation}`;
|
||
const special = document.getElementById('specialRulesText');
|
||
special.textContent = rules.specialRules ? `Specjalne: ${rules.specialRules}` : '';
|
||
rulesPanel.style.display = 'block';
|
||
}
|
||
|
||
// Panel dla trybu z botem: stałe zasady/styl
|
||
function renderBotDefaultsPanel() {
|
||
const rulesPanel = document.getElementById('rulesPanel');
|
||
if (!rulesPanel) return;
|
||
const defaults = { pointsToWin: 11, setsToWin: 3, serveRotation: 2 };
|
||
const chipPoints = document.getElementById('chipPoints');
|
||
const chipSets = document.getElementById('chipSets');
|
||
const chipServe = document.getElementById('chipServe');
|
||
const special = document.getElementById('specialRulesText');
|
||
if (chipPoints) chipPoints.textContent = `Set do ${defaults.pointsToWin}`;
|
||
if (chipSets) chipSets.textContent = `Mecz do ${defaults.setsToWin} setów`;
|
||
if (chipServe) chipServe.textContent = `Serwis co ${defaults.serveRotation}`;
|
||
if (special) special.textContent = 'Tryb Bot: stałe zasady i styl. Snapshoty działają tylko w trybie online.';
|
||
rulesPanel.style.display = 'block';
|
||
}
|
||
</script>
|
||
|
||
<!-- Suspension modal + JS override for "Graj online" when account is suspended -->
|
||
<?php if ($ppSuspended): ?>
|
||
<style>
|
||
.susp-modal-overlay {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.72);
|
||
z-index: 99999;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.susp-modal-overlay.active {
|
||
display: flex;
|
||
}
|
||
.susp-modal {
|
||
background: #12131a;
|
||
border: 1px solid rgba(211,47,47,0.55);
|
||
border-radius: 18px;
|
||
padding: 40px 36px 32px;
|
||
max-width: 460px;
|
||
width: 92%;
|
||
box-shadow: 0 0 60px rgba(211,47,47,0.25), 0 24px 48px rgba(0,0,0,0.6);
|
||
text-align: center;
|
||
position: relative;
|
||
animation: suspModalIn 0.22s cubic-bezier(.34,1.4,.64,1) both;
|
||
}
|
||
@keyframes suspModalIn {
|
||
from { transform: scale(0.88); opacity: 0; }
|
||
to { transform: scale(1); opacity: 1; }
|
||
}
|
||
.susp-modal-icon {
|
||
font-size: 52px;
|
||
line-height: 1;
|
||
margin-bottom: 16px;
|
||
filter: drop-shadow(0 0 14px rgba(211,47,47,0.6));
|
||
}
|
||
.susp-modal h2 {
|
||
color: #ef5350;
|
||
font-size: 1.35em;
|
||
font-weight: 800;
|
||
margin-bottom: 12px;
|
||
letter-spacing: 0.01em;
|
||
}
|
||
.susp-modal-body {
|
||
color: #cfd8dc;
|
||
font-size: 0.95em;
|
||
line-height: 1.65;
|
||
margin-bottom: 28px;
|
||
}
|
||
.susp-modal-body strong {
|
||
color: #fff;
|
||
}
|
||
.susp-modal-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
.susp-modal-btn {
|
||
padding: 11px 26px;
|
||
border-radius: 9px;
|
||
font-size: 0.9em;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
border: none;
|
||
transition: all 0.2s;
|
||
text-decoration: none;
|
||
display: inline-block;
|
||
}
|
||
.susp-modal-btn.primary {
|
||
background: linear-gradient(135deg,#c62828,#e53935);
|
||
color: #fff;
|
||
}
|
||
.susp-modal-btn.primary:hover { background: linear-gradient(135deg,#b71c1c,#d32f2f); }
|
||
.susp-modal-btn.secondary {
|
||
background: rgba(255,255,255,0.07);
|
||
color: #cfd8dc;
|
||
border: 1px solid rgba(255,255,255,0.15);
|
||
}
|
||
.susp-modal-btn.secondary:hover { background: rgba(255,255,255,0.13); }
|
||
</style>
|
||
|
||
<div class="susp-modal-overlay" id="suspModalOverlay" role="dialog" aria-modal="true" aria-labelledby="suspModalTitle">
|
||
<div class="susp-modal">
|
||
<div class="susp-modal-icon">⛔</div>
|
||
<h2 id="suspModalTitle">Konto zawieszone</h2>
|
||
<div class="susp-modal-body" id="suspModalBody"></div>
|
||
<div class="susp-modal-actions">
|
||
<a href="/bok/" class="susp-modal-btn primary">Skontaktuj się z BOK</a>
|
||
<button class="susp-modal-btn secondary" onclick="document.getElementById('suspModalOverlay').classList.remove('active')">Zamknij</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function() {
|
||
var reason = <?php echo json_encode($ppSuspReason); ?>;
|
||
var until = <?php echo json_encode($ppSuspUntil); ?>;
|
||
|
||
function buildSuspBody() {
|
||
var html = 'Gra online jest dostępna tylko dla aktywnych kont.';
|
||
if (reason) { html += '<br><br><strong>Powód zawieszenia:</strong> ' + reason.replace(/</g,'<'); }
|
||
if (until) { html += '<br><strong>Zawieszone do:</strong> ' + until.replace(/</g,'<'); }
|
||
else { html += '<br><strong>Czas trwania:</strong> bezterminowe.'; }
|
||
return html;
|
||
}
|
||
|
||
function openSuspModal() {
|
||
var overlay = document.getElementById('suspModalOverlay');
|
||
var body = document.getElementById('suspModalBody');
|
||
if (!overlay) return;
|
||
body.innerHTML = buildSuspBody();
|
||
overlay.classList.add('active');
|
||
}
|
||
|
||
// Close on overlay click (outside modal box)
|
||
document.getElementById('suspModalOverlay').addEventListener('click', function(e) {
|
||
if (e.target === this) this.classList.remove('active');
|
||
});
|
||
|
||
// Override the global showOnlineMessage defined in ui-manager.js
|
||
window.showOnlineMessage = function() { openSuspModal(); };
|
||
|
||
// Auto-open modal if redirected from 1v1 page
|
||
if (window.location.search.indexOf('blocked=suspended') !== -1) {
|
||
document.addEventListener('DOMContentLoaded', function() { openSuspModal(); });
|
||
// Already loaded? Open immediately
|
||
if (document.readyState !== 'loading') openSuspModal();
|
||
}
|
||
|
||
// Keyboard close
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') {
|
||
var overlay = document.getElementById('suspModalOverlay');
|
||
if (overlay) overlay.classList.remove('active');
|
||
}
|
||
});
|
||
})();
|
||
</script>
|
||
<?php else: ?>
|
||
<script>
|
||
window.IS_SUSPENDED = false;
|
||
</script>
|
||
<?php endif; ?>
|
||
|
||
<?php
|
||
if (!empty($_SESSION['logged_in'])) {
|
||
include __DIR__ . '/../../global/footerLogined.php';
|
||
} else {
|
||
include __DIR__ . '/../../global/footerNoLogined.php';
|
||
}
|
||
?>
|
||
</body>
|
||
</html>
|