я косяк, поправил веб. Капитан, работайте!

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
влад
2026-07-16 11:06:47 +03:00
co-authored by Cursor
parent 148d645760
commit 2c294424ef
85 changed files with 17410 additions and 110 deletions
+219
View File
@@ -0,0 +1,219 @@
<!DOCTYPE html>
<html lang="ru" translate="no" class="notranslate" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="google" content="notranslate">
<meta name="googlebot" content="notranslate">
<meta http-equiv="Content-Language" content="ru">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
<link rel="icon" href="/static/favicon-32.png" type="image/png" sizes="32x32">
<link rel="apple-touch-icon" href="/static/apple-touch-icon.png">
<title>WESP — загрузка</title>
<link rel="stylesheet" href="/static/css/wesp-logo-loader.css">
<link rel="stylesheet" href="/static/css/wesp-setup.css">
<link rel="stylesheet" href="/static/css/wesp-startup.css">
<link rel="modulepreload" href="/static/js/wesp-logo-loader.js?v=startup11s">
</head>
<body class="wesp-startup-screen">
<div class="wesp-startup__stage">
<div class="wesp-setup__intro" id="startupIntro" aria-hidden="false"></div>
<p class="wesp-startup__quip" id="startupQuip" aria-live="polite"></p>
</div>
<noscript>
<p class="wesp-startup__quip wesp-startup__quip--error">
Ошибка: нужен JavaScript для экрана загрузки.
</p>
</noscript>
<script type="module">
import { mountWespLogoLoader } from "/static/js/wesp-logo-loader.js?v=startup11s";
const quipEl = document.getElementById("startupQuip");
const introEl = document.getElementById("startupIntro");
const params = new URLSearchParams(window.location.search);
let defaultReturn = "/login";
const rawNext = params.get("next");
let returnTo = defaultReturn;
if (rawNext && String(rawNext).trim().startsWith("/")) {
returnTo = String(rawNext).trim();
} else {
const stored = sessionStorage.getItem("wespStartupReturn");
if (stored && String(stored).trim().startsWith("/") && stored !== "/") {
returnTo = String(stored).trim();
}
}
sessionStorage.setItem("wespStartupReturn", returnTo);
const theme = (params.get("theme") || "dark").trim().toLowerCase();
if (theme === "light" || theme === "dark" || theme === "organic") {
document.documentElement.setAttribute("data-theme", theme);
}
const QUIPS = [
"Сортируем гранулы по размеру мысли…",
"Калибруем весы — не дышите на тару",
"Синхронизируем рецепты с ближайшей галактикой",
"Ищем соседей по сети — здравствуйте, VLAN",
"Считаем компоненты рациона (почти честно)",
"Проверяем: server и client — не один Pi",
"Чайник почти вскипел, осталось чуть-чуть",
"Подтягиваем склад: где мука, там и отчёты",
"Собираем пиксели логотипа в боевой порядок",
"Готовим весы к первому «ну почти ноль»",
"Дозатор №3 просит ещё пять грамм терпения",
"Зоотехник ещё не проснулся — мы уже работаем",
"Перемалываем корм в биты и обратно",
"Сверяем тару с совестью",
"Логотип синеет — это не баг, это бренд",
"Кормление через пять… четыре… ну почти",
"mDNS: «есть кто живой?» — ждём ответа",
"Весы молчат — значит, всё стабильно",
"Рецепт «на глаз» конвертируем в граммы",
"Проверяем, что ноль — действительно ноль",
"Комптон заряжает пиксели…",
];
const READY_QUIP = "Готово — открываем двери";
const QUIP_ROTATE_MS = 1500;
const STARTUP_MIN_MS = 10000;
/** Скорость волн/shimmer — как раньше (не растягивать на 10 с). */
const STARTUP_LOGO_ANIM_MS = 6000;
const READY_HOLD_MS = 700;
function shuffleQuips(list) {
const deck = list.slice();
for (let i = deck.length - 1; i > 0; i -= 1) {
const j = Math.floor(Math.random() * (i + 1));
[deck[i], deck[j]] = [deck[j], deck[i]];
}
return deck;
}
let quipDeck = shuffleQuips(QUIPS);
let deckPos = 0;
let quipFadeSeq = 0;
let quipRotationEnabled = true;
let pollTimer = null;
let quipTimer = null;
let serverReady = false;
let finishStarted = false;
const minLeaveAt = performance.now() + STARTUP_MIN_MS;
function pickQuip() {
if (deckPos >= quipDeck.length) {
quipDeck = shuffleQuips(QUIPS);
deckPos = 0;
}
return quipDeck[deckPos++];
}
function setQuip(text, isError = false) {
if (!quipEl) return;
const next = String(text || "").trim();
if (!next) return;
const seq = ++quipFadeSeq;
quipEl.classList.add("wesp-startup__quip--fade");
quipEl.classList.toggle("wesp-startup__quip--error", Boolean(isError));
window.setTimeout(() => {
if (seq !== quipFadeSeq) return;
quipEl.textContent = next;
quipEl.classList.remove("wesp-startup__quip--fade");
}, 200);
}
function stopQuipRotation() {
quipRotationEnabled = false;
if (quipTimer) {
clearInterval(quipTimer);
quipTimer = null;
}
}
function rotateQuip() {
if (!quipRotationEnabled || document.hidden || finishStarted) return;
setQuip(pickQuip());
}
function tryFinish() {
if (finishStarted) return;
if (!serverReady) return;
if (performance.now() < minLeaveAt) return;
finishStarted = true;
stopQuipRotation();
if (pollTimer) {
clearInterval(pollTimer);
pollTimer = null;
}
setQuip(READY_QUIP);
const target = returnTo.startsWith("/") ? returnTo : defaultReturn;
window.setTimeout(() => window.location.replace(target), READY_HOLD_MS);
}
async function pollReady() {
if (finishStarted) return;
try {
const r = await fetch("/api/startup/status", { cache: "no-store" });
const d = await r.json().catch(() => ({}));
if (d.default_next && String(d.default_next).trim().startsWith("/")) {
defaultReturn = String(d.default_next).trim();
if (!rawNext) {
returnTo = defaultReturn;
sessionStorage.setItem("wespStartupReturn", returnTo);
}
}
if (d.ready) {
serverReady = true;
tryFinish();
return;
}
if (d.phase === "error" || d.error) {
finishStarted = true;
stopQuipRotation();
if (pollTimer) clearInterval(pollTimer);
setQuip(d.error || d.message || "Ошибка инициализации", true);
return;
}
} catch {
if (quipRotationEnabled && !quipEl.textContent) {
setQuip("Ожидание сервера…");
}
}
}
try {
await mountWespLogoLoader(introEl, {
loop: false,
durationMs: STARTUP_LOGO_ANIM_MS,
embedded: true,
transparentBackground: true,
knockoutBackground: true,
theme: document.documentElement.getAttribute("data-theme") || theme,
hideChrome: false,
});
} catch (err) {
console.error("startup logo loader failed", err);
finishStarted = true;
stopQuipRotation();
if (pollTimer) clearInterval(pollTimer);
setQuip(
"Не удалось загрузить анимацию. Проверьте /static/js/wesp-logo-loader.js и логи сервера.",
true,
);
throw err;
}
if (quipEl) quipEl.classList.remove("wesp-startup__quip--error");
setQuip(pickQuip());
quipTimer = window.setInterval(rotateQuip, QUIP_ROTATE_MS);
pollReady();
pollTimer = window.setInterval(pollReady, 800);
window.setTimeout(tryFinish, STARTUP_MIN_MS);
</script>
</body>
</html>