- public/js/ui/stats.js: one record per finished match in localStorage (sp.pref.stats, envelope {v, records}, at most
1000, quota shrink of the oldest), content-id dedupe of the lobby's result replay, a MIGRATIONS chain and
normalizeRecord (defaults for missing fields, unknown fields kept, a newer envelope refused), merge-by-id export /
import, aggregateStats.
- public/js/screens/stats.js: the viewer (总览 / 策略 / 称号 / 战斗累计 / 最近对局, a row re-views that match's
settlement), 导出 / 导入 / 清空.
- Applied from the PR's own diff after reading it in full (no network, no new dependency, server untouched); only the
UI strings of the page are added to the four packs (en from the PR; ja / ko / zh-TW translated for this commit).
- The follow-up commits rework the page (the counting rule for matches left early, storage bounds, the layout).
- Tests: test/ui/stats.test.js 20 / 20; i18n extract 0, check --all --strict clean.
108 lines
6.2 KiB
HTML
108 lines
6.2 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<!-- A landscape game: no page zoom (iOS additionally needs the gesture handling of js/ui/device.js), full-bleed behind
|
||
notches (viewport-fit=cover; css/devices.css keeps the HUD inside the safe area). -->
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
|
||
<meta name="theme-color" content="#0c0f0e" />
|
||
<meta name="color-scheme" content="dark" />
|
||
<meta name="format-detection" content="telephone=no" />
|
||
<!-- "Add to Home Screen" (iOS / Android) opens full screen without browser bars -->
|
||
<meta name="mobile-web-app-capable" content="yes" />
|
||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||
<meta name="apple-mobile-web-app-title" content="卫戍协议" />
|
||
<meta name="description" content="卫戍协议:盟约 · 网页联机复刻(非官方同人作品)" />
|
||
<title>卫戍协议:盟约 · STRONGHOLD PROTOCOL</title>
|
||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect width='24' height='24' fill='%230c0f0e'/%3E%3Cpath fill='%234ed8af' d='M5 3h3v2h2V3h4v2h2V3h3v5l-2 2v7l2 2v2H5v-2l2-2v-7L5 8z'/%3E%3C/svg%3E" />
|
||
|
||
<!-- Web fonts: never render-blocking (LAN/offline play must still start); system fallbacks in theme.css. -->
|
||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||
<link rel="stylesheet" media="print" onload="this.media='all'"
|
||
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&family=Oxanium:wght@400;500;600;700&family=Rajdhani:wght@500;600;700&display=swap" />
|
||
<!-- Self-hosted Bender / Novecento (provided by the assets pipeline). -->
|
||
<link rel="stylesheet" href="/fonts/fonts.css" />
|
||
|
||
<link rel="stylesheet" href="/css/theme.css" />
|
||
<link rel="stylesheet" href="/css/components.css" />
|
||
<link rel="stylesheet" href="/css/screens/title.css" />
|
||
<link rel="stylesheet" href="/css/screens/lobby.css" />
|
||
<link rel="stylesheet" href="/css/screens/room.css" />
|
||
<link rel="stylesheet" href="/css/screens/game.css" />
|
||
<link rel="stylesheet" href="/css/screens/game-shop.css" />
|
||
<link rel="stylesheet" href="/css/screens/game-panels.css" />
|
||
<link rel="stylesheet" href="/css/screens/briefing.css" />
|
||
<link rel="stylesheet" href="/css/screens/draft.css" />
|
||
<link rel="stylesheet" href="/css/screens/result.css" />
|
||
<link rel="stylesheet" href="/css/screens/guide.css" />
|
||
<link rel="stylesheet" href="/css/screens/loadout.css" />
|
||
<link rel="stylesheet" href="/css/screens/stats.css" />
|
||
<link rel="stylesheet" href="/css/emotes.css" />
|
||
<link rel="stylesheet" href="/css/devices.css" />
|
||
|
||
<script type="importmap">
|
||
{ "imports": { "preact": "/vendor/preact.module.js", "preact/hooks": "/vendor/hooks.module.js", "htm": "/vendor/htm.module.js" } }
|
||
</script>
|
||
<link rel="modulepreload" href="/vendor/preact.module.js" />
|
||
<link rel="modulepreload" href="/vendor/hooks.module.js" />
|
||
<link rel="modulepreload" href="/vendor/htm.module.js" />
|
||
<link rel="modulepreload" href="/js/main.js" />
|
||
<!-- Battlefield renderer libs (classic scripts, injected by js/render/app.js when a match view mounts): warm the cache
|
||
at low priority so entering the match doesn't wait on ~800 KB of script. -->
|
||
<link rel="prefetch" href="/vendor/pixi.min.js" as="script" />
|
||
<link rel="prefetch" href="/vendor/pixi-spine.js" as="script" />
|
||
</head>
|
||
<body>
|
||
<div id="app"></div>
|
||
|
||
<div id="boot" class="boot" aria-hidden="true">
|
||
<div class="boot__inner">
|
||
<div class="boot__hex"></div>
|
||
<div class="boot__text">STRONGHOLD PROTOCOL</div>
|
||
<div id="boot-err" class="boot__err"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="rotate-hint" role="alert">
|
||
<div class="rotate-hint__phone"></div>
|
||
<div class="rotate-hint__title">请将设备横屏</div>
|
||
<div>卫戍协议:盟约 需要横屏显示以获得完整体验</div>
|
||
<div class="rotate-hint__micro">ROTATE DEVICE</div>
|
||
</div>
|
||
|
||
<noscript><p style="color:#c3cbc7;text-align:center;padding:40px">需要启用 JavaScript 才能运行本游戏。</p></noscript>
|
||
<script>
|
||
// Boot diagnostics (plain ES5: runs everywhere). A browser too old for the app says so instead of spinning forever:
|
||
// no ES modules (nomodule below), a module graph that cannot load (e.g. import maps: Safari / iOS < 16.4), or no
|
||
// boot after 9 s.
|
||
window.__spBootFail = function (why) {
|
||
var e = document.getElementById('boot-err');
|
||
if (!e || e.getAttribute('data-final')) return;
|
||
var im = false;
|
||
try { im = !!(window.HTMLScriptElement && HTMLScriptElement.supports && HTMLScriptElement.supports('importmap')); } catch (x) { im = false; }
|
||
e.textContent = why === 'old' || !im
|
||
? '当前浏览器版本过旧,无法运行本游戏。请升级到 iOS / iPadOS 16.4+(Safari 16.4+)、Chrome / Edge 90+ 或 Firefox 108+ 后刷新。'
|
||
: '游戏脚本加载失败,请检查网络连接后刷新页面。';
|
||
e.setAttribute('data-final', '1');
|
||
};
|
||
// a module graph that fails to link (bare specifier without import-map support) reports here, not on the <script>
|
||
window.addEventListener('error', function (ev) {
|
||
var b = document.getElementById('boot');
|
||
if (!b || b.classList.contains('is-done')) return;
|
||
if (/specifier|resolve|import|module/i.test(String(ev && ev.message))) window.__spBootFail('load');
|
||
});
|
||
</script>
|
||
<script type="module" src="/js/main.js" onerror="window.__spBootFail && window.__spBootFail('load')"></script>
|
||
<script nomodule>window.__spBootFail && window.__spBootFail('old');</script>
|
||
<script>
|
||
// If modules never boot (very old browser / blocked script), explain instead of spinning forever.
|
||
setTimeout(function () {
|
||
var b = document.getElementById('boot'), e = document.getElementById('boot-err');
|
||
if (b && !b.classList.contains('is-done') && e && !e.textContent) e.textContent = '加载时间较长…如长时间无响应,请使用最新版 Chrome / Edge / Safari / Firefox 并刷新页面。';
|
||
}, 9000);
|
||
</script>
|
||
</body>
|
||
</html>
|