v0.6.2: hero page site foot, dismiss detail on empty click, drop patches version footer.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
voson
2026-07-30 07:45:03 +08:00
co-authored by Cursor
parent 1aa6710e39
commit aac4593cc5
7 changed files with 110 additions and 38 deletions
+1 -1
View File
@@ -121,7 +121,7 @@ climperor/
| `web/data/hero_abilities.json` | 英雄技能与机制汇总(由 `fetch_hero_abilities.py` 生成;含 ability `tags` |
| `web/data/item_counter_stats.json` | OpenDota 对阵装备观测证据缓存(对阵购买率/条件胜率减同装备全局基线;可再生成;不进 recommend / relations |
| `web/data/hero_item_fears.json` | 英雄怕的装备(规则推导;Web「怕」行) |
| `web/frontend/` | 上分帝 Web 前端静态资源(`index.html` / `config.js` / `app.js` / `style.css` / `router.js` / `mobile-gate.js` / `_redirects` / `robots.txt` / `functions/`);`config.js``SITE_VERSION``SITE_ORIGIN``ABILITY_VIDEO_BASE``STATIC_ASSET_BASE`版本页底部显示 `v{SITE_VERSION}`;技能演示按官网 16:9(有空间加宽至约 720px,`contain` 不裁左右);移动端由 `mobile-gate.js` 拦截(搜索/AI 爬虫 UA 跳过) |
| `web/frontend/` | 上分帝 Web 前端静态资源(`index.html` / `config.js` / `app.js` / `style.css` / `router.js` / `mobile-gate.js` / `_redirects` / `robots.txt` / `functions/`);`config.js``SITE_VERSION``SITE_ORIGIN``ABILITY_VIDEO_BASE``STATIC_ASSET_BASE`英雄页底部(无详情时)显示 `v{SITE_VERSION}` 与数据更新时间;技能演示按官网 16:9(有空间加宽至约 720px,`contain` 不裁左右);移动端由 `mobile-gate.js` 拦截(搜索/AI 爬虫 UA 跳过) |
| `web/frontend/router.js` | History 路径路由:`parseHash` / `serializeHash`(操作 pathname+search/ `installRouter` / `syncStateToUrl`;状态↔URL 双向同步(顶层 `/heroes\|rankings\|streamers\|matches\|trends\|mechanics\|items\|patches` / 英雄 + 子标签 `skills\|core\|fears\|trends\|matchups\|matches\|streamers\|patches` / Immortal `/rankings[/region]` / 明星比赛 `/matches[/account_id][?origin=pro\|china][&page=N]` / 主播 `/streamers` / 走势 `/trends[/bracket][?sort=pr]` / 机制 `/mechanics[/{effect}]`(默认 `basic_dispel`) / 物品 / 版本 / 标签筛选 / 搜索;旧 `stats` / `/rankings/meta` 与 hash `#/...` 兼容) |
| `web/assets/hero_portraits/` | 官网横版头像(上分帝 Web;默认 wide 面部构图,非匹配模板) |
| `web/assets/attr_icons/` | 官网主属性图标(力量/敏捷/智力/全才,上分帝 Web 用) |
+11
View File
@@ -4,6 +4,17 @@
## [Unreleased]
## [0.6.2] - 2026-07-30
### Added
- 英雄页底部居中显示站点版本号与数据更新时间(取各数据集 `fetched_at` 最新值;打开详情时隐藏)。
- 点击定位标签上方空白(英雄网格空隙或工具栏空白)可关闭详情面板。
### Changed
- 版本页底部不再显示站点版本号(改由英雄页底部展示)。
## [0.6.1] - 2026-07-30
### Added
+1 -1
View File
@@ -56,7 +56,7 @@ from shared.paths import (
from seo_prerender import DEFAULT_SITE_ORIGIN, write_seo_bundle
from serve_relations import WEB_DIR, build_payload
SITE_VERSION = "0.6.1"
SITE_VERSION = "0.6.2"
DEFAULT_OSS_BASE = "https://climperor.oss-cn-shanghai.aliyuncs.com"
+78 -17
View File
@@ -76,6 +76,52 @@ function siteVersionLabel() {
return v ? `v${v}` : "";
}
/** Latest fetched_at among volatile site datasets (for hero detail footer). */
function siteDataFetchedAt() {
if (!state.data) return null;
const candidates = [];
const push = (v) => {
const ms = parseTimeMs(v);
if (Number.isFinite(ms)) candidates.push(ms);
};
push(state.data.hero_stats?.fetched_at);
push(state.data.leaderboards?.fetched_at);
push(state.data.hero_matches?.meta?.fetched_at);
push(state.data.pro_matches?.meta?.fetched_at);
push(state.data.streamers?.fetched_at);
push(state.data.stratz_hero_meta?.fetched_at);
push(state.data.stratz_matchup_tops?.fetched_at);
push(state.data.hero_items?.meta?.fetched_at);
if (!candidates.length) return null;
return new Date(Math.max(...candidates)).toISOString();
}
function renderHeroesSiteFoot() {
const el = $("#heroes-site-foot");
if (!el) return;
// Hide under the detail panel when a hero is selected.
const show = state.page === "heroes" && !state.selectedKey;
el.classList.toggle("hidden", !show);
if (!show) {
el.textContent = "";
el.removeAttribute("title");
el.setAttribute("aria-hidden", "true");
return;
}
const parts = [];
const ver = siteVersionLabel();
if (ver) parts.push(ver);
const fetchedAt = siteDataFetchedAt();
const fetchedInfo = fetchedAt
? formatFriendlyTime(fetchedAt)
: { text: "", title: "" };
if (fetchedInfo.text) parts.push(`更新于 ${fetchedInfo.text}`);
el.textContent = parts.join(" · ");
if (fetchedInfo.title) el.title = fetchedInfo.title;
else el.removeAttribute("title");
el.setAttribute("aria-hidden", parts.length ? "false" : "true");
}
function siteOrigin() {
const configured = typeof SITE_ORIGIN === "string" ? SITE_ORIGIN.trim().replace(/\/+$/, "") : "";
if (configured) return configured;
@@ -208,14 +254,6 @@ function clearSeoPrerender() {
if (el) el.remove();
}
function renderPatchesSiteVersion() {
const el = $("#patches-site-version");
if (!el) return;
const label = siteVersionLabel();
el.textContent = label;
el.setAttribute("aria-hidden", label ? "false" : "true");
}
const state = {
data: null,
page: "heroes", // heroes | rankings | matches | streamers | trends | mechanics | items | patches
@@ -385,6 +423,15 @@ function onHeroClick(key) {
render();
}
function clearHeroDetail() {
if (!state.selectedKey) return false;
state.selectedKey = null;
state.inspect = null;
syncStateToUrl();
render();
return true;
}
function renderTagbar() {
const bar = $("#tagbar");
if (!bar) return;
@@ -3471,7 +3518,6 @@ function renderPatches() {
if (select) select.innerHTML = "";
if (verEl) verEl.textContent = "";
root.innerHTML = '<div class="patches-empty">暂无版本数据</div>';
renderPatchesSiteVersion();
return;
}
@@ -3506,7 +3552,6 @@ function renderPatches() {
const det = details[state.selectedPatch];
if (!det) {
root.innerHTML = '<div class="patches-empty">该版本详情暂未收录</div>';
renderPatchesSiteVersion();
return;
}
@@ -3539,7 +3584,6 @@ function renderPatches() {
.map((h) => renderHeroEntry(h, lookup))
.join("");
renderPatchSection(root, "英雄改动", heroesHtml);
renderPatchesSiteVersion();
}
function escapeHtml(s) {
@@ -5612,6 +5656,7 @@ function render() {
} else {
renderPatches();
}
renderHeroesSiteFoot();
updateDocumentMeta();
}
@@ -5779,6 +5824,26 @@ function bindPageChrome() {
});
}
/** Click empty area above role tags (board remainder / toolbar chrome) to dismiss detail. */
function bindHeroesDismiss() {
const view = $("#heroes-view");
if (view) {
view.addEventListener("click", (e) => {
if (state.page !== "heroes" || !state.selectedKey) return;
if (e.target.closest(".hero")) return;
clearHeroDetail();
});
}
const toolbar = $("#heroes-toolbar");
if (toolbar) {
toolbar.addEventListener("click", (e) => {
if (state.page !== "heroes" || !state.selectedKey) return;
if (e.target.closest("button, a, input, select, textarea, label")) return;
clearHeroDetail();
});
}
}
async function main() {
// mobile-gate.js sets this before paint; skip desktop data boot on phones/tablets.
if (typeof window !== "undefined" && window.__CLIMPEROR_MOBILE__) return;
@@ -5796,12 +5861,7 @@ async function main() {
renderBoard();
return;
}
if (state.selectedKey) {
state.selectedKey = null;
state.inspect = null;
syncStateToUrl();
render();
}
clearHeroDetail();
return;
}
if (state.itemQuery && document.activeElement === $("#q-item")) {
@@ -5839,6 +5899,7 @@ async function main() {
);
bindSearch();
bindPageChrome();
bindHeroesDismiss();
const brandLogo = document.querySelector(".brand-logo");
if (brandLogo) brandLogo.src = assetUrl("/ui-icon/dota2_logo_wordmark.png");
const favicon = document.querySelector('link[rel="icon"]');
+1 -1
View File
@@ -1,5 +1,5 @@
/* Local defaults; production export overwrites via export_relations_site.py. */
var SITE_VERSION = "0.6.1";
var SITE_VERSION = "0.6.2";
var SITE_ORIGIN = "";
var ABILITY_VIDEO_BASE = "";
var STATIC_ASSET_BASE = "";
+6 -6
View File
@@ -43,8 +43,8 @@
}
</script>
<link rel="icon" href="/ui-icon/dota2_logo.png" type="image/png" />
<link rel="stylesheet" href="/style.css?v=0.6.1" />
<script src="/mobile-gate.js?v=0.6.1"></script>
<link rel="stylesheet" href="/style.css?v=0.6.2" />
<script src="/mobile-gate.js?v=0.6.2"></script>
</head>
<body>
<h1 class="sr-only">DOTA2 上分帝</h1>
@@ -227,13 +227,13 @@
<select id="patch-select" aria-label="选择版本"></select>
</div>
<div class="patches-detail" id="patches-detail"></div>
<footer class="patches-site-version" id="patches-site-version" aria-hidden="true"></footer>
</main>
<section class="detail" id="detail" aria-live="polite"></section>
<footer class="heroes-site-foot" id="heroes-site-foot" aria-hidden="true"></footer>
<script src="/config.js?v=0.6.1"></script>
<script src="/router.js?v=0.6.1"></script>
<script src="/app.js?v=0.6.1"></script>
<script src="/config.js?v=0.6.2"></script>
<script src="/router.js?v=0.6.2"></script>
<script src="/app.js?v=0.6.2"></script>
</body>
</html>
+12 -12
View File
@@ -493,6 +493,18 @@ body {
overflow: hidden;
box-sizing: border-box;
}
.heroes-site-foot {
flex: 0 0 auto;
width: 100%;
padding: 6px 14px 10px;
text-align: center;
font-size: 11px;
font-weight: var(--fw-medium);
color: var(--muted);
letter-spacing: 0.08em;
opacity: 0.55;
user-select: none;
}
.detail:not(.empty) {
height: var(--detail-fixed-h);
min-height: var(--detail-fixed-h);
@@ -2808,18 +2820,6 @@ body:has(#items-view:not(.hidden)) {
font-size: var(--fs-body);
line-height: var(--lh-body);
}
.patches-site-version {
width: min(var(--content-read), 92%);
padding: 4px 0 28px;
text-align: center;
font-size: 11px;
font-weight: var(--fw-medium);
color: var(--muted);
letter-spacing: 0.08em;
opacity: 0.55;
user-select: none;
}
.rankings-board {
overflow-x: hidden;
overflow-y: auto;