добавил на страницы загрузок эффект матрицы
This commit is contained in:
@@ -298,6 +298,70 @@
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* ===== MATRIX LOADER ===== */
|
||||
.matrix-loader {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9999;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.92);
|
||||
}
|
||||
|
||||
.matrix-loader.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.matrix-canvas {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.matrix-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
text-align: center;
|
||||
padding: 24px 28px;
|
||||
border-radius: 16px;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
border: 1px solid rgba(0, 255, 120, 0.25);
|
||||
}
|
||||
|
||||
.matrix-title {
|
||||
font-size: 26px;
|
||||
color: #9dffb2;
|
||||
}
|
||||
|
||||
.matrix-status {
|
||||
font-size: 14px;
|
||||
color: #d7ffe1;
|
||||
}
|
||||
.matrix-loader.is-error .matrix-content {
|
||||
border-color: rgba(255, 90, 90, 0.35);
|
||||
box-shadow: 0 0 30px rgba(255, 90, 90, 0.12);
|
||||
}
|
||||
|
||||
.matrix-loader.is-error .matrix-title {
|
||||
color: #ff9c9c;
|
||||
text-shadow: 0 0 10px rgba(255, 90, 90, 0.25);
|
||||
}
|
||||
|
||||
.matrix-loader.is-error .matrix-status {
|
||||
color: #ffd4d4;
|
||||
}
|
||||
|
||||
.matrix-loader.is-success .matrix-title {
|
||||
color: #9dffb2;
|
||||
}
|
||||
|
||||
.matrix-status {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -414,8 +478,94 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
<div id="matrixLoader" class="matrix-loader" aria-hidden="true">
|
||||
<canvas id="matrixCanvas" class="matrix-canvas"></canvas>
|
||||
<div class="matrix-content">
|
||||
<div class="matrix-title">Подготовка матча</div>
|
||||
<div class="matrix-status" id="matrixStatusText">Инициализация...</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
const loader = document.getElementById("matrixLoader");
|
||||
const titleEl = document.getElementById("matrixTitle");
|
||||
const statusText = document.getElementById("matrixStatusText");
|
||||
const canvas = document.getElementById("matrixCanvas");
|
||||
const ctx = canvas ? canvas.getContext("2d") : null;
|
||||
|
||||
let matrixAnimationId = null;
|
||||
let drops = [];
|
||||
let fontSize = 16;
|
||||
let columns = 0;
|
||||
let lastTime = 0;
|
||||
const fps = 18;
|
||||
const interval = 1000 / fps;
|
||||
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ@$%#*+=<>";
|
||||
|
||||
function resizeCanvas() {
|
||||
if (!canvas || !ctx) return;
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
columns = Math.floor(canvas.width / fontSize);
|
||||
drops = Array(columns).fill(1);
|
||||
}
|
||||
|
||||
function drawMatrix(time = 0) {
|
||||
if (!canvas || !ctx) return;
|
||||
|
||||
if (time - lastTime < interval) {
|
||||
matrixAnimationId = requestAnimationFrame(drawMatrix);
|
||||
return;
|
||||
}
|
||||
|
||||
lastTime = time;
|
||||
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 0.08)";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
ctx.fillStyle = "#00ff66";
|
||||
ctx.font = `${fontSize}px monospace`;
|
||||
|
||||
for (let i = 0; i < drops.length; i++) {
|
||||
const text = chars[Math.floor(Math.random() * chars.length)];
|
||||
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
|
||||
|
||||
if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
|
||||
drops[i] = 0;
|
||||
}
|
||||
|
||||
drops[i] += 0.65;
|
||||
}
|
||||
|
||||
matrixAnimationId = requestAnimationFrame(drawMatrix);
|
||||
}
|
||||
|
||||
function startLoader(title, message) {
|
||||
if (loader) {
|
||||
loader.classList.add("active");
|
||||
loader.classList.remove("is-error", "is-success");
|
||||
}
|
||||
if (titleEl) titleEl.textContent = title || "Подготовка матча";
|
||||
if (statusText) statusText.textContent = message || "Инициализация...";
|
||||
document.body.style.overflow = "hidden";
|
||||
|
||||
resizeCanvas();
|
||||
if (!matrixAnimationId) {
|
||||
drawMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
function setLoaderState(state, title, message) {
|
||||
if (loader) {
|
||||
loader.classList.remove("is-error", "is-success");
|
||||
if (state) loader.classList.add(state);
|
||||
}
|
||||
if (titleEl) titleEl.textContent = title || "";
|
||||
if (statusText) statusText.textContent = message || "";
|
||||
}
|
||||
|
||||
window.addEventListener("resize", resizeCanvas);
|
||||
|
||||
async function handleSelectSubmit(event) {
|
||||
const form = event.target;
|
||||
if (!form.classList.contains("js-select-form")) return;
|
||||
@@ -423,17 +573,16 @@
|
||||
event.preventDefault();
|
||||
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
const operatorInput = form.querySelector('input[name="operator_name"]');
|
||||
const operatorName = operatorInput ? operatorInput.value : "";
|
||||
|
||||
if (submitButton) {
|
||||
submitButton.disabled = true;
|
||||
submitButton.textContent = "Подготовка...";
|
||||
}
|
||||
|
||||
startLoader("Подготовка матча", "Создание игровой сессии...");
|
||||
|
||||
try {
|
||||
const url = new URL(form.action, window.location.origin);
|
||||
url.searchParams.set("operator_name", operatorName);
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
method: "GET",
|
||||
@@ -444,13 +593,21 @@
|
||||
credentials: "same-origin"
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
let data = {};
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch (_) {}
|
||||
|
||||
if (!response.ok || !data.success) {
|
||||
throw new Error(data.error || data.vmix_error || "Не удалось выбрать матч");
|
||||
}
|
||||
|
||||
if (data.download_url) {
|
||||
setLoaderState("",
|
||||
"Подготовка матча",
|
||||
"Формирование проекта vMix..."
|
||||
);
|
||||
|
||||
const downloadResponse = await fetch(data.download_url, {
|
||||
method: "GET",
|
||||
credentials: "same-origin"
|
||||
@@ -465,6 +622,11 @@
|
||||
throw new Error(errText);
|
||||
}
|
||||
|
||||
setLoaderState("",
|
||||
"Подготовка матча",
|
||||
"Скачивание проекта vMix..."
|
||||
);
|
||||
|
||||
const blob = await downloadResponse.blob();
|
||||
|
||||
let filename = "project.vmix";
|
||||
@@ -489,13 +651,42 @@
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(blobUrl);
|
||||
|
||||
setLoaderState(
|
||||
"is-success",
|
||||
"Загрузка завершена",
|
||||
"Проект vMix успешно подготовлен.\nПереход во вкладку «Игра»..."
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
window.URL.revokeObjectURL(blobUrl);
|
||||
window.location.href = data.session_url + "?tab=game";
|
||||
}, 1200);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = data.session_url;
|
||||
setLoaderState(
|
||||
"is-success",
|
||||
"Загрузка завершена",
|
||||
"Матч подготовлен.\nПереход во вкладку «Игра»..."
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = data.session_url + "?tab=game";
|
||||
}, 1200);
|
||||
|
||||
} catch (error) {
|
||||
alert(error.message || "Ошибка при подготовке матча");
|
||||
setLoaderState(
|
||||
"is-error",
|
||||
"Ошибка подготовки",
|
||||
(error.message || "Ошибка при подготовке матча") + "\nВозврат к списку матчей через 3 секунды..."
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = "/admin/matches";
|
||||
}, 3000);
|
||||
} finally {
|
||||
if (submitButton) {
|
||||
submitButton.disabled = false;
|
||||
submitButton.textContent = "Выбрать матч";
|
||||
@@ -505,6 +696,7 @@
|
||||
|
||||
document.addEventListener("submit", handleSelectSubmit);
|
||||
})();
|
||||
|
||||
(function () {
|
||||
const LOGIN_URL = "/login";
|
||||
const IDLE_LIMIT_MS = 2 * 60 * 60 * 1000;
|
||||
|
||||
Reference in New Issue
Block a user