This commit is contained in:
zhx 2026-06-26 18:26:12 +08:00
parent 1efd1b779c
commit 2d1bd5cb52
3 changed files with 121 additions and 120 deletions

View File

@ -222,6 +222,13 @@
border: 1px solid var(--achievement-card-border);
background: var(--achievement-card-purple);
}
.badge-card:focus,
.badge-card:focus-visible,
.badge-card:active {
outline: none;
box-shadow: none;
transform: none;
}
.badge-card::before {
content: '';
position: absolute;
@ -300,20 +307,21 @@
.footer-text {
margin-top: 24px;
}
.loading {
.achievement-loading {
position: absolute;
left: 0;
top: 320px;
z-index: 7;
display: none;
width: 375px;
color: rgba(255, 255, 255, 0.72);
font-size: 12px;
line-height: 16px;
text-align: center;
}
.loading.active {
display: block;
.achievement-loading .hy-loading__mascot {
width: 76px;
height: 76px;
}
.achievement-loading .hy-loading__spinner {
border-color: rgba(255, 255, 255, 0.22);
border-top-color: #ffffff;
}
.toast {
position: fixed;
@ -504,21 +512,14 @@
></button>
</nav>
<section class="profile" aria-label="User">
<img
id="profileAvatar"
class="profile-avatar"
src="assets/figma/profile-avatar.png"
alt=""
/>
<img id="profileAvatar" class="profile-avatar" alt="" />
<div
id="profileFrame"
class="profile-frame"
aria-hidden="true"
>
<img src="assets/figma/header-frame.png" alt="" />
</div>
></div>
</section>
<div id="userName" class="user-name">Name</div>
<div id="userName" class="user-name"></div>
<div id="earnedText" class="earned">
Earn <strong id="earnedCount">0</strong> achievements
</div>
@ -561,10 +562,17 @@
</section>
<div
id="loading"
class="loading"
data-i18n="achievement.loading"
class="hy-loading achievement-loading"
aria-hidden="true"
>
Loading...
<div class="hy-loading__mascot" aria-hidden="true"></div>
<div class="hy-loading__spinner" aria-hidden="true"></div>
<div
class="hy-loading__text"
data-i18n="achievement.loading"
>
Loading...
</div>
</div>
</main>
</div>
@ -641,73 +649,6 @@
function validTab(value) {
return tabs[value] ? value : 'honors';
}
function isLocalOrigin() {
var host = window.location.hostname;
return (
host === '127.0.0.1' ||
host === 'localhost' ||
host === '::1'
);
}
function previewMode() {
if (!isLocalOrigin()) return '';
return String(
new URLSearchParams(window.location.search).get(
'preview'
) || ''
).toLowerCase();
}
function previewAchievement(index, unlocked) {
return {
definition: {
achievement_id: 'preview_' + index,
title: 'Pirple Pearl',
description:
'Complete the required achievement goal to collect this badge.',
display_config_json: JSON.stringify({
owner_count: 10,
}),
primary_badge_resource: {
name: 'Pirple Pearl',
preview_url:
ASSET +
(unlocked
? 'badge-gold.png'
: 'badge-silver.png'),
},
},
user_status: unlocked ? 'unlocked' : 'in_progress',
unlocked_at_ms: unlocked
? new Date('2026-01-01T00:00:00Z').getTime()
: 0,
};
}
function applyLocalPreview() {
var mode = previewMode();
if (!mode) return false;
var sample = [0, 1, 2, 3, 4, 5, 6, 7, 8].map(
function (_, index) {
return previewAchievement(
index + 1,
mode === 'unlocked' ||
(mode !== 'locked' &&
(index === 0 || index >= 3))
);
}
);
state.profile = {
name: 'Name',
avatar: ASSET + 'profile-avatar.png',
};
state.achievements.honors = mode === 'empty' ? [] : sample;
state.achievements.event = [];
state.loaded.honors = true;
state.loaded.event = true;
render();
if (mode === 'locked') openModal(sample[1]);
if (mode === 'unlocked') openModal(sample[0]);
return true;
}
function showToast(message) {
var toast = byId('toast');
toast.textContent = message;
@ -759,7 +700,7 @@
profile.displayUserId ||
profile.user_id ||
profile.userId ||
'Name'
''
);
}
function profileAvatar(profile) {
@ -822,17 +763,24 @@
node.src = fallback;
}
};
if (!next) {
node.removeAttribute('src');
return;
}
if (node.getAttribute('src') !== next) node.src = next;
}
function renderStaticFrame(node, src) {
if (!src) {
node.innerHTML = '';
return;
}
node.innerHTML =
'<img src="' + escapeHTML(src) + '" alt="">';
var img = node.querySelector('img');
img.onerror = function () {
img.onerror = null;
if (src === ASSET + 'header-frame.png') return;
state.profileFrameURL = ASSET + 'header-frame.png';
renderStaticFrame(node, ASSET + 'header-frame.png');
state.profileFrameURL = '';
renderStaticFrame(node, '');
};
}
function isDynamicEffect(url) {
@ -847,9 +795,7 @@
function renderProfileFrame(profile) {
var node = byId('profileFrame');
if (!node) return;
var url =
frameURL(profileFrameData(profile)) ||
ASSET + 'header-frame.png';
var url = frameURL(profileFrameData(profile));
if (state.profileFrameURL === url) return;
state.profileFrameURL = url;
if (state.profileFramePlayer) {
@ -863,8 +809,8 @@
waitForWindowLoad: false,
});
state.profileFramePlayer.play(url).catch(function () {
state.profileFrameURL = ASSET + 'header-frame.png';
renderStaticFrame(node, ASSET + 'header-frame.png');
state.profileFrameURL = '';
renderStaticFrame(node, '');
});
return;
}
@ -892,8 +838,8 @@
resource.preview_url ||
resource.asset_url ||
resource.animation_url ||
ASSET +
(unlocked ? 'badge-gold.png' : 'badge-silver.png')
resource.url ||
''
);
}
function isUnlocked(item) {
@ -907,7 +853,7 @@
function titleOf(item) {
var resource = resourceOf(item);
var def = definitionOf(item);
return def.title || resource.name || 'Pirple Pearl';
return def.title || resource.name || '';
}
function descriptionOf(item) {
var config = configOf(item);
@ -997,11 +943,7 @@
function renderHeader() {
var profile = state.profile || {};
byId('userName').textContent = profileName(profile);
setImage(
byId('profileAvatar'),
profileAvatar(profile),
ASSET + 'profile-avatar.png'
);
setImage(byId('profileAvatar'), profileAvatar(profile), '');
renderProfileFrame(profile);
byId('earnedText').innerHTML = earnedText(unlockedCount());
}
@ -1017,9 +959,9 @@
'" aria-label="' +
escapeHTML(titleOf(item)) +
'">' +
'<img src="' +
escapeHTML(img) +
'" alt="">' +
(img
? '<img src="' + escapeHTML(img) + '" alt="">'
: '') +
'<div class="badge-title">' +
escapeHTML(titleOf(item)) +
'</div>' +
@ -1201,30 +1143,28 @@
}
function renderCollectors(item) {
var custom = collectorAvatarsOf(item);
var avatars = custom.length
? custom
: [
ASSET + 'collector-1.png',
ASSET + 'collector-2.png',
ASSET + 'collector-3.png',
ASSET + 'collector-4.png',
ASSET + 'collector-5.png',
];
byId('collectorList').innerHTML =
avatars
custom
.slice(0, 5)
.map(function (src) {
return (
'<img src="' + escapeHTML(src) + '" alt="">'
);
})
.join('') + '<div class="collector-more">...</div>';
.join('') +
(custom.length
? '<div class="collector-more">...</div>'
: '');
}
function openModal(item) {
state.selected = item;
var unlocked = isUnlocked(item);
var resource = resourceOf(item);
byId('modalBadge').src = resourceImage(resource, unlocked);
setImage(
byId('modalBadge'),
resourceImage(resource, unlocked),
''
);
byId('modalBadge').classList.toggle('locked', !unlocked);
byId('modalTitle').textContent = titleOf(item);
byId('modalDesc').textContent = descriptionOf(item);
@ -1295,6 +1235,7 @@
byId('badgeGrid').addEventListener('click', function (event) {
var target = event.target.closest('[data-index]');
if (!target) return;
target.blur();
var list = state.achievements[state.tab] || [];
var item = list[Number(target.dataset.index)];
if (item) openModal(item);
@ -1314,7 +1255,6 @@
render();
window.HyAppBridge.ready({ page: 'achievement' });
if (applyLocalPreview()) return;
loadTab(state.tab).finally(function () {
loadUserProfile();
loadCounts();

BIN
common/png/loading-cat.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 KiB

View File

@ -103,3 +103,64 @@
--hy-theme-radius-pill: 0;
--hy-theme-radius-circle: 0;
}
.hy-loading {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
color: var(--hy-theme-muted);
text-align: center;
pointer-events: none;
}
.hy-loading.active,
.hy-loading.is-active {
display: flex;
}
.hy-loading__mascot {
width: 72px;
height: 72px;
background: url('./png/loading-cat.webp') center / contain no-repeat;
animation: hy-loading-float 1.6s ease-in-out infinite;
}
.hy-loading__spinner {
width: 24px;
height: 24px;
border: 2px solid rgba(125, 87, 199, 0.18);
border-top-color: var(--hy-theme-button);
border-radius: 50%;
animation: hy-loading-spin 0.86s linear infinite;
}
.hy-loading__text {
font-size: 12px;
font-weight: 700;
line-height: 16px;
}
@keyframes hy-loading-float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-6px);
}
}
@keyframes hy-loading-spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.hy-loading__mascot,
.hy-loading__spinner {
animation: none;
}
}