diff --git a/achievement/index.html b/achievement/index.html index b483442..f5705ba 100644 --- a/achievement/index.html +++ b/achievement/index.html @@ -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 @@ >
- + + >
-
Name
+
Earn 0 achievements
@@ -561,10 +562,17 @@ @@ -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 = ''; 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 + ? '' + : '') + '
' + escapeHTML(titleOf(item)) + '
' + @@ -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 ( '' ); }) - .join('') + '
...
'; + .join('') + + (custom.length + ? '
...
' + : ''); } 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(); diff --git a/common/png/loading-cat.webp b/common/png/loading-cat.webp new file mode 100644 index 0000000..bc69439 Binary files /dev/null and b/common/png/loading-cat.webp differ diff --git a/common/theme.css b/common/theme.css index 4661feb..8d369fe 100644 --- a/common/theme.css +++ b/common/theme.css @@ -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; + } +}