togethere.cloud/public_html/disciplines/ping-pong/index.php

1698 lines
49 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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 1010, 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,'&lt;'); }
if (until) { html += '<br><strong>Zawieszone do:</strong> ' + until.replace(/</g,'&lt;'); }
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>