@
feat(quantik-game): фаза 1 — оболочка игры + физ-уровень + прогресс (MVP) Страница /quantik монтирует уровень-спеку в SimEngine (игровой режим: HUD из Ф0 + слайдеры закона + play/reset), на победу шлёт результат и показывает экран успеха (звёзды/время/попытки, inline SVG). Уровень phys-artillery-1 как данные (levels.js): гравитация + запуск тела из угла/скорости, портал, бонус-звезда. Бэкенд: миграция 076 game_progress (UNIQUE user+level), /api/game/progress (GET свой / POST upsert best time/stars, attempts++, auth-only, валидация входа), клиент LS.gameProgress*, пункт сайдбара. game.test.js 13/13; npm test 251 pass/8 baseline; lint:routes 0. Уровень проверен на реальном интеграторе (311 выигрышных комбо, 31 на 3★). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Квантик — Законы Мира</title>
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@400;700;800&family=Manrope:wght@400;500;600;700&display=swap" rel="stylesheet"/>
|
||||
<link rel="stylesheet" href="/css/ls.css"/>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css" />
|
||||
<script src="https://unpkg.com/lucide@0.469.0/dist/umd/lucide.min.js"></script>
|
||||
<style>
|
||||
/* ── Раскладка игровой страницы ── */
|
||||
.qg-wrap { display: flex; flex-direction: column; height: 100vh; min-height: 0; }
|
||||
.qg-top {
|
||||
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
|
||||
padding: 12px 20px; border-bottom: 1px solid var(--border); background: var(--surface);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.qg-title { font-family: 'Unbounded', sans-serif; font-weight: 800; font-size: 1.05rem; color: var(--text); white-space: nowrap; }
|
||||
.qg-sub { font-size: .8rem; color: var(--text-3); flex: 1; min-width: 0; }
|
||||
.qg-pill { font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; padding: 3px 10px; border-radius: 99px; background: rgba(34,211,238,0.14); color: #0e7c8a; }
|
||||
|
||||
/* сцена: на всю площадь, тёмный фон (full-bleed root движка растягивается inset:0) */
|
||||
.qg-stage { flex: 1; min-height: 0; position: relative; background: #0D0D1A; overflow: hidden; }
|
||||
.qg-stage .sim-spec-root { position: absolute; inset: 0; }
|
||||
|
||||
.qg-fallback { padding: 40px; color: #cbd5e1; font-family: 'Manrope', sans-serif; max-width: 520px; }
|
||||
|
||||
/* ── Экран успеха (оверлей) ── */
|
||||
.qg-overlay {
|
||||
position: absolute; inset: 0; z-index: 20;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: rgba(7, 7, 18, 0.72); backdrop-filter: blur(4px);
|
||||
}
|
||||
.qg-card {
|
||||
background: var(--surface); border: 1px solid var(--border); border-radius: 18px;
|
||||
padding: 28px 30px; width: min(420px, 90vw); text-align: center;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.45);
|
||||
animation: qg-pop .22s ease;
|
||||
}
|
||||
@keyframes qg-pop { from { transform: scale(.92); opacity: 0; } to { transform: scale(1); opacity: 1; } }
|
||||
.qg-card-title { font-family: 'Unbounded', sans-serif; font-weight: 800; font-size: 1.3rem; color: var(--text); margin-bottom: 14px; }
|
||||
.qg-stars { display: flex; justify-content: center; gap: 6px; margin-bottom: 18px; }
|
||||
.qg-star { display: inline-flex; }
|
||||
.qg-star-svg { filter: drop-shadow(0 2px 6px rgba(251,191,36,0.4)); }
|
||||
.qg-stats { display: flex; justify-content: center; gap: 22px; margin-bottom: 22px; }
|
||||
.qg-stat { display: flex; flex-direction: column; gap: 3px; }
|
||||
.qg-stat-lbl { font-size: .7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--text-3); }
|
||||
.qg-stat-val { font-family: 'Unbounded', sans-serif; font-weight: 700; font-size: 1.05rem; color: var(--text); font-variant-numeric: tabular-nums; }
|
||||
.qg-actions { display: flex; justify-content: center; gap: 10px; }
|
||||
.qg-btn { min-width: 110px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<aside class="sidebar" id="app-sidebar"></aside>
|
||||
<main class="sb-content">
|
||||
<div class="qg-wrap">
|
||||
<div class="qg-top">
|
||||
<span class="qg-title" id="qg-title">Квантик — Законы Мира</span>
|
||||
<span class="qg-sub" id="qg-sub"></span>
|
||||
<span class="qg-pill">Физика</span>
|
||||
</div>
|
||||
<div class="qg-stage" id="qg-stage"></div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js"></script>
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script src="/js/notifications.js"></script>
|
||||
<script src="/js/mobile.js"></script>
|
||||
<!-- движок спек-симуляций (тот же путь, что lab.html / sim-builder.html) -->
|
||||
<script src="/js/labs/_sim_expr.js"></script>
|
||||
<script src="/js/labs/_sim_engine.js"></script>
|
||||
<!-- KaTeX для подписей сцены -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
|
||||
<!-- уровни (данные) + логика игры -->
|
||||
<script src="/js/game/levels.js"></script>
|
||||
<script src="/js/game/quantik-game.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
// Доступ: любой авторизованный пользователь (играют и ученики).
|
||||
if (!LS.initPage()) { return; } // initPage сам редиректит на /login, если не авторизован
|
||||
|
||||
var stage = document.getElementById('qg-stage');
|
||||
|
||||
if (!window.SimEngine || !window.SimExpr || !window.QuantikLevels || !window.QuantikGame) {
|
||||
stage.innerHTML = '<div class="qg-fallback">Движок игры не загрузился. Обновите страницу.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Уровень: ?level=<id> или первый из реестра (MVP — один уровень).
|
||||
var params = new URLSearchParams(location.search);
|
||||
var wantId = params.get('level');
|
||||
var level = wantId ? window.QuantikLevels.get(wantId) : null;
|
||||
if (!level) level = window.QuantikLevels.list()[0] || null;
|
||||
|
||||
if (!level) {
|
||||
stage.innerHTML = '<div class="qg-fallback">Уровень не найден.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('qg-title').textContent = level.title || 'Квантик';
|
||||
document.getElementById('qg-sub').textContent = level.hint || '';
|
||||
|
||||
var inst = window.QuantikGame.start({ host: stage, level: level });
|
||||
if (!inst) {
|
||||
stage.innerHTML = '<div class="qg-fallback">Не удалось запустить уровень.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
window.__quantik = inst; // для отладки
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user