v0.4.1: OSS skill videos, site version footer, and mail contact.

Host ability demos on Aliyun OSS instead of Pages bundles; add patches-page version label and top-right mailto link; improve spirit bear portrait export.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
voson
2026-07-27 20:26:30 +08:00
co-authored by Cursor
parent adb8f303c2
commit 124bdbb7c7
11 changed files with 289 additions and 22 deletions
+20
View File
@@ -0,0 +1,20 @@
/*
X-Content-Type-Options: nosniff
/index.html
Cache-Control: public, max-age=0, must-revalidate
/data.json
Cache-Control: public, max-age=0, must-revalidate
/app.js
Cache-Control: public, max-age=60, must-revalidate
/config.js
Cache-Control: public, max-age=0, must-revalidate
/router.js
Cache-Control: public, max-age=60, must-revalidate
/style.css
Cache-Control: public, max-age=300, must-revalidate
+34 -3
View File
@@ -1,4 +1,30 @@
/* global fetch, document */
/* global fetch, document, ABILITY_VIDEO_BASE, SITE_VERSION */
/** OSS / CDN base for ability demos; empty => same-origin `/ability-video/...`. */
function abilityVideoBase() {
const raw =
typeof ABILITY_VIDEO_BASE === "string" ? ABILITY_VIDEO_BASE.trim() : "";
return raw.replace(/\/+$/, "");
}
function abilityVideoUrl(heroKey, abilityKey) {
const rel = `/ability-video/${heroKey}/${abilityKey}.webm`;
const base = abilityVideoBase();
return base ? `${base}${rel}` : rel;
}
function siteVersionLabel() {
const v = typeof SITE_VERSION === "string" ? SITE_VERSION.trim() : "";
return v ? `v${v}` : "";
}
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,
@@ -724,15 +750,17 @@ function mountSkillInspect(root, skillEnt, heroKey) {
// Official-page layout: demo clip on the left, name/desc column on the right.
const body = document.createElement("div");
body.className = "skill-info-body";
// Official demo clip (assets/ability_videos); hidden when not cached locally.
// Official demo clip: local `/ability-video/...` (serve_relations) or OSS base
// from config.js (static export / Pages). Hidden when the file is missing.
if (heroKey && skillEnt.ability_key) {
const video = document.createElement("video");
video.className = "skill-info-video";
video.src = `/ability-video/${heroKey}/${skillEnt.ability_key}.webm`;
video.src = abilityVideoUrl(heroKey, skillEnt.ability_key);
video.muted = true;
video.loop = true;
video.autoplay = true;
video.playsInline = true;
video.crossOrigin = "anonymous";
video.addEventListener("error", () => video.remove());
body.appendChild(video);
}
@@ -1723,6 +1751,7 @@ function renderPatches() {
if (select) select.innerHTML = "";
if (verEl) verEl.textContent = "";
root.innerHTML = '<div class="patches-empty">暂无版本数据,请运行 python fetch_patches.py</div>';
renderPatchesSiteVersion();
return;
}
@@ -1753,6 +1782,7 @@ function renderPatches() {
const det = details[state.selectedPatch];
if (!det) {
root.innerHTML = '<div class="patches-empty">该版本详情暂未抓取,请重跑 python fetch_patches.py</div>';
renderPatchesSiteVersion();
return;
}
@@ -1785,6 +1815,7 @@ function renderPatches() {
.map((h) => renderHeroEntry(h, lookup))
.join("");
renderPatchSection(root, "英雄改动", heroesHtml);
renderPatchesSiteVersion();
}
function escapeHtml(s) {
+3
View File
@@ -0,0 +1,3 @@
/* Local defaults; production export overwrites via export_relations_site.py. */
var SITE_VERSION = "0.4.1";
var ABILITY_VIDEO_BASE = "";
+14 -2
View File
@@ -12,6 +12,16 @@
<img class="brand-logo" src="ui-icon/dota2_logo.png" alt="Dota 2" />
<span class="brand-title">上分帝</span>
</div>
<a
class="contact-mail"
href="mailto:c9mhs8vfmuyv@outlook.com"
aria-label="邮件联系"
title="c9mhs8vfmuyv@outlook.com"
>
<svg class="contact-mail-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" focusable="false">
<path fill="currentColor" d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4-8 5-8-5V6l8 5 8-5v2z"/>
</svg>
</a>
<nav class="main-tabs" aria-label="主分页">
<button type="button" class="main-tab active" data-page="heroes">英雄</button>
<button type="button" class="main-tab" data-page="items">物品</button>
@@ -55,11 +65,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>
<script src="/router.js"></script>
<script src="/app.js"></script>
<script src="/config.js?v=0.4.1"></script>
<script src="/router.js?v=0.4.1"></script>
<script src="/app.js?v=0.4.1"></script>
</body>
</html>
+39
View File
@@ -75,6 +75,34 @@ body {
@media (max-width: 620px) {
.brand { display: none; }
}
.contact-mail {
position: absolute;
right: 20px;
top: 12px;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 8px;
border: none;
background: transparent;
color: var(--muted);
text-decoration: none;
transition: color 0.15s, background 0.15s;
}
.contact-mail:hover {
color: var(--text);
background: rgba(255, 255, 255, 0.06);
}
.contact-mail:focus-visible {
outline: 2px solid var(--sel);
outline-offset: 2px;
}
.contact-mail-icon {
display: block;
}
.main-tabs {
display: flex;
justify-content: center;
@@ -1609,6 +1637,17 @@ body:has(#items-view:not(.hidden)) {
color: var(--muted);
font-size: 14px;
}
.patches-site-version {
width: min(820px, 92%);
padding: 4px 0 28px;
text-align: center;
font-size: 11px;
font-weight: 500;
color: var(--muted);
letter-spacing: 0.08em;
opacity: 0.55;
user-select: none;
}
.patch-section {
margin-top: 26px;
}