成就
This commit is contained in:
parent
1efd1b779c
commit
2d1bd5cb52
@ -222,6 +222,13 @@
|
|||||||
border: 1px solid var(--achievement-card-border);
|
border: 1px solid var(--achievement-card-border);
|
||||||
background: var(--achievement-card-purple);
|
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 {
|
.badge-card::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -300,20 +307,21 @@
|
|||||||
.footer-text {
|
.footer-text {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
.loading {
|
.achievement-loading {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 320px;
|
top: 320px;
|
||||||
z-index: 7;
|
z-index: 7;
|
||||||
display: none;
|
|
||||||
width: 375px;
|
width: 375px;
|
||||||
color: rgba(255, 255, 255, 0.72);
|
color: rgba(255, 255, 255, 0.72);
|
||||||
font-size: 12px;
|
|
||||||
line-height: 16px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
.loading.active {
|
.achievement-loading .hy-loading__mascot {
|
||||||
display: block;
|
width: 76px;
|
||||||
|
height: 76px;
|
||||||
|
}
|
||||||
|
.achievement-loading .hy-loading__spinner {
|
||||||
|
border-color: rgba(255, 255, 255, 0.22);
|
||||||
|
border-top-color: #ffffff;
|
||||||
}
|
}
|
||||||
.toast {
|
.toast {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -504,21 +512,14 @@
|
|||||||
></button>
|
></button>
|
||||||
</nav>
|
</nav>
|
||||||
<section class="profile" aria-label="User">
|
<section class="profile" aria-label="User">
|
||||||
<img
|
<img id="profileAvatar" class="profile-avatar" alt="" />
|
||||||
id="profileAvatar"
|
|
||||||
class="profile-avatar"
|
|
||||||
src="assets/figma/profile-avatar.png"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
id="profileFrame"
|
id="profileFrame"
|
||||||
class="profile-frame"
|
class="profile-frame"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
></div>
|
||||||
<img src="assets/figma/header-frame.png" alt="" />
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
<div id="userName" class="user-name">Name</div>
|
<div id="userName" class="user-name"></div>
|
||||||
<div id="earnedText" class="earned">
|
<div id="earnedText" class="earned">
|
||||||
Earn <strong id="earnedCount">0</strong> achievements
|
Earn <strong id="earnedCount">0</strong> achievements
|
||||||
</div>
|
</div>
|
||||||
@ -561,10 +562,17 @@
|
|||||||
</section>
|
</section>
|
||||||
<div
|
<div
|
||||||
id="loading"
|
id="loading"
|
||||||
class="loading"
|
class="hy-loading achievement-loading"
|
||||||
data-i18n="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>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
@ -641,73 +649,6 @@
|
|||||||
function validTab(value) {
|
function validTab(value) {
|
||||||
return tabs[value] ? value : 'honors';
|
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) {
|
function showToast(message) {
|
||||||
var toast = byId('toast');
|
var toast = byId('toast');
|
||||||
toast.textContent = message;
|
toast.textContent = message;
|
||||||
@ -759,7 +700,7 @@
|
|||||||
profile.displayUserId ||
|
profile.displayUserId ||
|
||||||
profile.user_id ||
|
profile.user_id ||
|
||||||
profile.userId ||
|
profile.userId ||
|
||||||
'Name'
|
''
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function profileAvatar(profile) {
|
function profileAvatar(profile) {
|
||||||
@ -822,17 +763,24 @@
|
|||||||
node.src = fallback;
|
node.src = fallback;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (!next) {
|
||||||
|
node.removeAttribute('src');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (node.getAttribute('src') !== next) node.src = next;
|
if (node.getAttribute('src') !== next) node.src = next;
|
||||||
}
|
}
|
||||||
function renderStaticFrame(node, src) {
|
function renderStaticFrame(node, src) {
|
||||||
|
if (!src) {
|
||||||
|
node.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
node.innerHTML =
|
node.innerHTML =
|
||||||
'<img src="' + escapeHTML(src) + '" alt="">';
|
'<img src="' + escapeHTML(src) + '" alt="">';
|
||||||
var img = node.querySelector('img');
|
var img = node.querySelector('img');
|
||||||
img.onerror = function () {
|
img.onerror = function () {
|
||||||
img.onerror = null;
|
img.onerror = null;
|
||||||
if (src === ASSET + 'header-frame.png') return;
|
state.profileFrameURL = '';
|
||||||
state.profileFrameURL = ASSET + 'header-frame.png';
|
renderStaticFrame(node, '');
|
||||||
renderStaticFrame(node, ASSET + 'header-frame.png');
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function isDynamicEffect(url) {
|
function isDynamicEffect(url) {
|
||||||
@ -847,9 +795,7 @@
|
|||||||
function renderProfileFrame(profile) {
|
function renderProfileFrame(profile) {
|
||||||
var node = byId('profileFrame');
|
var node = byId('profileFrame');
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
var url =
|
var url = frameURL(profileFrameData(profile));
|
||||||
frameURL(profileFrameData(profile)) ||
|
|
||||||
ASSET + 'header-frame.png';
|
|
||||||
if (state.profileFrameURL === url) return;
|
if (state.profileFrameURL === url) return;
|
||||||
state.profileFrameURL = url;
|
state.profileFrameURL = url;
|
||||||
if (state.profileFramePlayer) {
|
if (state.profileFramePlayer) {
|
||||||
@ -863,8 +809,8 @@
|
|||||||
waitForWindowLoad: false,
|
waitForWindowLoad: false,
|
||||||
});
|
});
|
||||||
state.profileFramePlayer.play(url).catch(function () {
|
state.profileFramePlayer.play(url).catch(function () {
|
||||||
state.profileFrameURL = ASSET + 'header-frame.png';
|
state.profileFrameURL = '';
|
||||||
renderStaticFrame(node, ASSET + 'header-frame.png');
|
renderStaticFrame(node, '');
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -892,8 +838,8 @@
|
|||||||
resource.preview_url ||
|
resource.preview_url ||
|
||||||
resource.asset_url ||
|
resource.asset_url ||
|
||||||
resource.animation_url ||
|
resource.animation_url ||
|
||||||
ASSET +
|
resource.url ||
|
||||||
(unlocked ? 'badge-gold.png' : 'badge-silver.png')
|
''
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function isUnlocked(item) {
|
function isUnlocked(item) {
|
||||||
@ -907,7 +853,7 @@
|
|||||||
function titleOf(item) {
|
function titleOf(item) {
|
||||||
var resource = resourceOf(item);
|
var resource = resourceOf(item);
|
||||||
var def = definitionOf(item);
|
var def = definitionOf(item);
|
||||||
return def.title || resource.name || 'Pirple Pearl';
|
return def.title || resource.name || '';
|
||||||
}
|
}
|
||||||
function descriptionOf(item) {
|
function descriptionOf(item) {
|
||||||
var config = configOf(item);
|
var config = configOf(item);
|
||||||
@ -997,11 +943,7 @@
|
|||||||
function renderHeader() {
|
function renderHeader() {
|
||||||
var profile = state.profile || {};
|
var profile = state.profile || {};
|
||||||
byId('userName').textContent = profileName(profile);
|
byId('userName').textContent = profileName(profile);
|
||||||
setImage(
|
setImage(byId('profileAvatar'), profileAvatar(profile), '');
|
||||||
byId('profileAvatar'),
|
|
||||||
profileAvatar(profile),
|
|
||||||
ASSET + 'profile-avatar.png'
|
|
||||||
);
|
|
||||||
renderProfileFrame(profile);
|
renderProfileFrame(profile);
|
||||||
byId('earnedText').innerHTML = earnedText(unlockedCount());
|
byId('earnedText').innerHTML = earnedText(unlockedCount());
|
||||||
}
|
}
|
||||||
@ -1017,9 +959,9 @@
|
|||||||
'" aria-label="' +
|
'" aria-label="' +
|
||||||
escapeHTML(titleOf(item)) +
|
escapeHTML(titleOf(item)) +
|
||||||
'">' +
|
'">' +
|
||||||
'<img src="' +
|
(img
|
||||||
escapeHTML(img) +
|
? '<img src="' + escapeHTML(img) + '" alt="">'
|
||||||
'" alt="">' +
|
: '') +
|
||||||
'<div class="badge-title">' +
|
'<div class="badge-title">' +
|
||||||
escapeHTML(titleOf(item)) +
|
escapeHTML(titleOf(item)) +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
@ -1201,30 +1143,28 @@
|
|||||||
}
|
}
|
||||||
function renderCollectors(item) {
|
function renderCollectors(item) {
|
||||||
var custom = collectorAvatarsOf(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 =
|
byId('collectorList').innerHTML =
|
||||||
avatars
|
custom
|
||||||
.slice(0, 5)
|
.slice(0, 5)
|
||||||
.map(function (src) {
|
.map(function (src) {
|
||||||
return (
|
return (
|
||||||
'<img src="' + escapeHTML(src) + '" alt="">'
|
'<img src="' + escapeHTML(src) + '" alt="">'
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.join('') + '<div class="collector-more">...</div>';
|
.join('') +
|
||||||
|
(custom.length
|
||||||
|
? '<div class="collector-more">...</div>'
|
||||||
|
: '');
|
||||||
}
|
}
|
||||||
function openModal(item) {
|
function openModal(item) {
|
||||||
state.selected = item;
|
state.selected = item;
|
||||||
var unlocked = isUnlocked(item);
|
var unlocked = isUnlocked(item);
|
||||||
var resource = resourceOf(item);
|
var resource = resourceOf(item);
|
||||||
byId('modalBadge').src = resourceImage(resource, unlocked);
|
setImage(
|
||||||
|
byId('modalBadge'),
|
||||||
|
resourceImage(resource, unlocked),
|
||||||
|
''
|
||||||
|
);
|
||||||
byId('modalBadge').classList.toggle('locked', !unlocked);
|
byId('modalBadge').classList.toggle('locked', !unlocked);
|
||||||
byId('modalTitle').textContent = titleOf(item);
|
byId('modalTitle').textContent = titleOf(item);
|
||||||
byId('modalDesc').textContent = descriptionOf(item);
|
byId('modalDesc').textContent = descriptionOf(item);
|
||||||
@ -1295,6 +1235,7 @@
|
|||||||
byId('badgeGrid').addEventListener('click', function (event) {
|
byId('badgeGrid').addEventListener('click', function (event) {
|
||||||
var target = event.target.closest('[data-index]');
|
var target = event.target.closest('[data-index]');
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
|
target.blur();
|
||||||
var list = state.achievements[state.tab] || [];
|
var list = state.achievements[state.tab] || [];
|
||||||
var item = list[Number(target.dataset.index)];
|
var item = list[Number(target.dataset.index)];
|
||||||
if (item) openModal(item);
|
if (item) openModal(item);
|
||||||
@ -1314,7 +1255,6 @@
|
|||||||
|
|
||||||
render();
|
render();
|
||||||
window.HyAppBridge.ready({ page: 'achievement' });
|
window.HyAppBridge.ready({ page: 'achievement' });
|
||||||
if (applyLocalPreview()) return;
|
|
||||||
loadTab(state.tab).finally(function () {
|
loadTab(state.tab).finally(function () {
|
||||||
loadUserProfile();
|
loadUserProfile();
|
||||||
loadCounts();
|
loadCounts();
|
||||||
|
|||||||
BIN
common/png/loading-cat.webp
Normal file
BIN
common/png/loading-cat.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 643 KiB |
@ -103,3 +103,64 @@
|
|||||||
--hy-theme-radius-pill: 0;
|
--hy-theme-radius-pill: 0;
|
||||||
--hy-theme-radius-circle: 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user