e837e191c2
- Replace Inter font with Cinzel (headings) + Jura (body) - Deepen dark background palette with atmospheric gradient orbs - Add subtle noise texture overlay for depth - Refine glass cards with animated gradient border glow on hover - Sharpen accent colors: violet #8b5cf6 + cyan #22d3ee - Improve button tactile feedback and shadow system - Add k8s manifests for minikube local deployment Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
300 lines
11 KiB
Plaintext
300 lines
11 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<meta name="description" content="GM-Relay — панель управления для Мастеров Игры. Управляйте сессиями настольных ролевых игр через Telegram." />
|
|
<meta name="theme-color" content="#0a0e1a" />
|
|
<base href="/" />
|
|
<ResourcePreloader />
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<link href="https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@400;700&family=Cinzel:wght@400;600;700&family=Jura:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
<link rel="stylesheet" href="@Assets["app.css"]" />
|
|
<link rel="stylesheet" href="@Assets["GmRelay.Web.styles.css"]" />
|
|
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
|
<ImportMap />
|
|
<link rel="icon" type="image/png" href="favicon.png" />
|
|
<HeadOutlet @rendermode="InteractiveServer" />
|
|
</head>
|
|
|
|
<body>
|
|
<Routes @rendermode="InteractiveServer" />
|
|
<ReconnectModal />
|
|
<script src="@Assets["_framework/blazor.web.js"]"></script>
|
|
<script>
|
|
window.waitForTelegramMiniApp = async function (timeoutMs) {
|
|
var deadline = Date.now() + (timeoutMs || 3000);
|
|
|
|
while (Date.now() <= deadline) {
|
|
if (window.Telegram && window.Telegram.WebApp) {
|
|
return window.Telegram.WebApp;
|
|
}
|
|
|
|
await new Promise(function (resolve) {
|
|
setTimeout(resolve, 100);
|
|
});
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
window.waitForTelegramMiniAppInitData = async function (timeoutMs) {
|
|
var deadline = Date.now() + (timeoutMs || 3000);
|
|
|
|
while (Date.now() <= deadline) {
|
|
if (window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.initData) {
|
|
return window.Telegram.WebApp;
|
|
}
|
|
|
|
await new Promise(function (resolve) {
|
|
setTimeout(resolve, 100);
|
|
});
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
window.syncTelegramMiniAppViewport = function (webApp) {
|
|
if (!webApp) {
|
|
return;
|
|
}
|
|
|
|
var root = document.documentElement;
|
|
var safeArea = webApp.safeAreaInset || {};
|
|
var contentSafeArea = webApp.contentSafeAreaInset || {};
|
|
var setPx = function (name, value) {
|
|
root.style.setProperty(name, Math.max(0, Number(value) || 0) + 'px');
|
|
};
|
|
|
|
setPx('--gm-tg-safe-top', safeArea.top);
|
|
setPx('--gm-tg-safe-right', safeArea.right);
|
|
setPx('--gm-tg-safe-bottom', safeArea.bottom);
|
|
setPx('--gm-tg-safe-left', safeArea.left);
|
|
setPx('--gm-tg-content-safe-top', contentSafeArea.top);
|
|
setPx('--gm-tg-content-safe-right', contentSafeArea.right);
|
|
setPx('--gm-tg-content-safe-bottom', contentSafeArea.bottom);
|
|
setPx('--gm-tg-content-safe-left', contentSafeArea.left);
|
|
|
|
if (webApp.viewportHeight) {
|
|
root.style.setProperty('--gm-tg-viewport-height', webApp.viewportHeight + 'px');
|
|
}
|
|
};
|
|
|
|
window.prepareTelegramMiniApp = function (webApp) {
|
|
if (!webApp) {
|
|
return;
|
|
}
|
|
|
|
document.body.classList.add('telegram-mini-app');
|
|
window.syncTelegramMiniAppViewport(webApp);
|
|
|
|
try {
|
|
webApp.ready();
|
|
} catch (error) {
|
|
}
|
|
|
|
try {
|
|
webApp.expand();
|
|
} catch (error) {
|
|
}
|
|
|
|
if (webApp.onEvent && !window.gmRelayTelegramMiniAppViewportEventsRegistered) {
|
|
window.gmRelayTelegramMiniAppViewportEventsRegistered = true;
|
|
webApp.onEvent('safeAreaChanged', function () {
|
|
window.syncTelegramMiniAppViewport(webApp);
|
|
});
|
|
webApp.onEvent('contentSafeAreaChanged', function () {
|
|
window.syncTelegramMiniAppViewport(webApp);
|
|
});
|
|
webApp.onEvent('viewportChanged', function () {
|
|
window.syncTelegramMiniAppViewport(webApp);
|
|
});
|
|
}
|
|
};
|
|
|
|
(async function () {
|
|
var webApp = await window.waitForTelegramMiniApp(1000);
|
|
window.prepareTelegramMiniApp(webApp);
|
|
})();
|
|
|
|
window.loadTelegramWidget = function (botUsername, authUrl) {
|
|
var container = document.getElementById('telegram-login-container');
|
|
if (!container) return;
|
|
container.innerHTML = '';
|
|
window.gmRelayTelegramLoginAuthUrl = authUrl || '/auth/telegram-login';
|
|
var script = document.createElement('script');
|
|
script.async = true;
|
|
script.src = 'https://telegram.org/js/telegram-widget.js?22';
|
|
script.setAttribute('data-telegram-login', botUsername);
|
|
script.setAttribute('data-size', 'large');
|
|
script.setAttribute('data-onauth', 'window.handleTelegramLogin(user)');
|
|
script.setAttribute('data-request-access', 'write');
|
|
container.appendChild(script);
|
|
};
|
|
|
|
window.handleTelegramLogin = async function (user) {
|
|
if (!user) {
|
|
window.location.href = '/login?error=auth_failed';
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var response = await fetch(window.gmRelayTelegramLoginAuthUrl || '/auth/telegram-login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'same-origin',
|
|
cache: 'no-store',
|
|
body: JSON.stringify(user)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
window.location.href = '/login?error=auth_failed';
|
|
return;
|
|
}
|
|
|
|
var payload = await response.json();
|
|
window.location.href = payload.redirectUrl || '/';
|
|
} catch (error) {
|
|
window.location.href = '/login?error=auth_failed';
|
|
}
|
|
};
|
|
|
|
window.watchTelegramMiniAppLogin = function (statusUrl, redirectUrl, reloadOnReturn) {
|
|
window.gmRelayMiniAppLoginReloadOnReturn =
|
|
window.gmRelayMiniAppLoginReloadOnReturn || reloadOnReturn === true;
|
|
|
|
if (window.gmRelayMiniAppLoginWatcher) {
|
|
return;
|
|
}
|
|
|
|
window.gmRelayMiniAppLoginLeftPage = false;
|
|
|
|
var stopWatching = function () {
|
|
if (window.gmRelayMiniAppLoginWatcher) {
|
|
window.clearInterval(window.gmRelayMiniAppLoginWatcher);
|
|
window.gmRelayMiniAppLoginWatcher = null;
|
|
}
|
|
};
|
|
|
|
var reloadAfterExternalLogin = function () {
|
|
if (!window.gmRelayMiniAppLoginReloadOnReturn || !window.gmRelayMiniAppLoginLeftPage) {
|
|
return;
|
|
}
|
|
|
|
window.gmRelayMiniAppLoginLeftPage = false;
|
|
|
|
try {
|
|
var refreshKey = 'gmrelay-miniapp-login-refresh:' + window.location.pathname;
|
|
if (window.sessionStorage && window.sessionStorage.getItem(refreshKey) === '1') {
|
|
return;
|
|
}
|
|
|
|
if (window.sessionStorage) {
|
|
window.sessionStorage.setItem(refreshKey, '1');
|
|
}
|
|
} catch (error) {
|
|
}
|
|
|
|
window.location.reload();
|
|
};
|
|
|
|
var allowNextExternalLoginReload = function () {
|
|
window.gmRelayMiniAppLoginLeftPage = true;
|
|
|
|
try {
|
|
var refreshKey = 'gmrelay-miniapp-login-refresh:' + window.location.pathname;
|
|
if (window.sessionStorage) {
|
|
window.sessionStorage.removeItem(refreshKey);
|
|
}
|
|
} catch (error) {
|
|
}
|
|
};
|
|
|
|
var checkLogin = async function (reloadWhenUnauthenticated) {
|
|
try {
|
|
var response = await fetch(statusUrl, {
|
|
credentials: 'same-origin',
|
|
cache: 'no-store'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
return;
|
|
}
|
|
|
|
var payload = await response.json();
|
|
if (payload.authenticated) {
|
|
stopWatching();
|
|
window.location.href = redirectUrl || '/';
|
|
return;
|
|
}
|
|
|
|
if (reloadWhenUnauthenticated) {
|
|
reloadAfterExternalLogin();
|
|
}
|
|
} catch (error) {
|
|
return;
|
|
}
|
|
};
|
|
|
|
window.gmRelayMiniAppLoginWatcher = window.setInterval(checkLogin, 1000);
|
|
window.addEventListener('blur', function () {
|
|
allowNextExternalLoginReload();
|
|
});
|
|
window.addEventListener('focus', function () {
|
|
void checkLogin(true);
|
|
});
|
|
document.addEventListener('visibilitychange', function () {
|
|
if (document.hidden) {
|
|
allowNextExternalLoginReload();
|
|
return;
|
|
}
|
|
|
|
void checkLogin(true);
|
|
});
|
|
|
|
void checkLogin(false);
|
|
};
|
|
|
|
window.authenticateTelegramMiniApp = async function (authUrl, redirectUrl) {
|
|
var webApp = await window.waitForTelegramMiniApp(3000);
|
|
if (!webApp) {
|
|
return { authenticated: false, reason: 'telegram-webapp-missing' };
|
|
}
|
|
|
|
window.prepareTelegramMiniApp(webApp);
|
|
|
|
if (!webApp.initData) {
|
|
return { authenticated: false, reason: 'telegram-init-data-empty' };
|
|
}
|
|
|
|
try {
|
|
var response = await fetch(authUrl, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'same-origin',
|
|
cache: 'no-store',
|
|
body: JSON.stringify({ initData: webApp.initData })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
return {
|
|
authenticated: false,
|
|
reason: 'telegram-auth-failed',
|
|
status: response.status
|
|
};
|
|
}
|
|
|
|
var payload = await response.json();
|
|
window.location.href = payload.redirectUrl || redirectUrl || '/';
|
|
return { authenticated: true, redirectUrl: payload.redirectUrl || redirectUrl || '/' };
|
|
} catch (error) {
|
|
return { authenticated: false, reason: 'telegram-auth-failed' };
|
|
}
|
|
};
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|