hyapp-h5/activity/cp/script.js
2026-06-08 19:16:41 +08:00

371 lines
14 KiB
JavaScript

(function () {
var DESIGN_WIDTH = 1080;
var VIEWPORT_WIDTH = 375;
var STORAGE_KEY = 'hy_cp_activity_end_at';
var stageHeights = {
plaza: 4283,
rank: 3670,
rewards: 4797,
};
var activeTab = 'plaza';
var countdownEndAt = 0;
var countdownTimer = 0;
var viewport = document.getElementById('appViewport');
var page = document.getElementById('page');
var stage = document.querySelector('.stage');
var stageWrap = document.getElementById('stageWrap');
var tabContent = document.getElementById('tabContent');
var copyright = document.querySelector('.copyright');
var tabs = Array.prototype.slice.call(document.querySelectorAll('.tab'));
var mockData = {
plaza: {
exchangeRows: buildRows('gift', 4),
previousRows: buildRows('score', 4),
},
rank: [
{ place: 1, score: '1,234,567', leftName: 'Namename', rightName: 'Namename' },
{ place: 2, score: '987,654', leftName: 'Namename', rightName: 'Namename' },
{ place: 3, score: '876,543', leftName: 'Namename', rightName: 'Namename' },
],
rewards: [
{
crown: 'crown-gold.png',
items: [
{ key: 'cp.rewardMedal7', noteKey: 'cp.rewardMedalNote' },
{ key: 'cp.rewardHonorBanner7' },
{ key: 'cp.rewardHeadwear7' },
{ key: 'cp.rewardMount7' },
{ key: 'cp.rewardCoins20000' },
{ key: 'cp.rewardDuke7' },
],
},
{
crown: 'crown-silver.png',
items: [
{ key: 'cp.rewardMedal7', noteKey: 'cp.rewardMedalNote' },
{ key: 'cp.rewardHonorBanner7' },
{ key: 'cp.rewardMount7' },
{ key: 'cp.rewardCoins20000' },
{ key: 'cp.rewardMarquis7' },
],
},
{
crown: 'crown-bronze.png',
items: [
{ key: 'cp.rewardMedal7', noteKey: 'cp.rewardMedalNote' },
{ key: 'cp.rewardHonorBanner7' },
{ key: 'cp.rewardCoins10000' },
{ key: 'cp.rewardCount7' },
],
},
],
};
function buildRows(type, count) {
var rows = [];
for (var index = 0; index < count; index += 1) {
rows.push({
type: type,
leftName: 'Namename',
rightName: 'Namename',
score: '1234567',
});
}
return rows;
}
function t(key, fallback) {
if (window.HyAppI18n && window.HyAppI18n.t) {
return window.HyAppI18n.t(key, fallback);
}
return fallback || key;
}
function escapeHTML(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function twoDigits(value) {
return String(Math.max(0, value)).padStart(2, '0');
}
function resolveFallbackEndAt() {
var params = new URLSearchParams(window.location.search || '');
var explicitEnd = Date.parse(params.get('end_at') || params.get('endAt') || '');
if (Number.isFinite(explicitEnd) && explicitEnd > Date.now()) return explicitEnd;
try {
var cached = Number(window.localStorage.getItem(STORAGE_KEY));
if (Number.isFinite(cached) && cached > Date.now()) return cached;
var fallback = Date.now() + 2 * 86400 * 1000 + 2 * 3600 * 1000 + 2 * 60 * 1000 + 2 * 1000;
window.localStorage.setItem(STORAGE_KEY, String(fallback));
return fallback;
} catch (error) {
return Date.now() + 2 * 86400 * 1000 + 2 * 3600 * 1000 + 2 * 60 * 1000 + 2 * 1000;
}
}
function getTimeParts() {
var diff = Math.max(0, countdownEndAt - Date.now());
var totalSeconds = Math.floor(diff / 1000);
return {
days: Math.floor(totalSeconds / 86400),
hours: Math.floor((totalSeconds % 86400) / 3600),
minutes: Math.floor((totalSeconds % 3600) / 60),
seconds: totalSeconds % 60,
};
}
function updateCountdown() {
var parts = getTimeParts();
Object.keys(parts).forEach(function (key) {
document.querySelectorAll('[data-count="' + key + '"]').forEach(function (node) {
node.textContent = twoDigits(parts[key]);
});
});
}
function renderPanel(className, top, height, bodyHTML) {
return [
'<section class="ornate-panel ',
className,
'" style="top:',
top,
'px;height:',
height,
'px">',
'<img class="panel-top" src="./assets/panel-top.png" alt="" aria-hidden="true" />',
'<div class="panel-mid" aria-hidden="true"></div>',
'<img class="panel-bottom" src="./assets/panel-bottom.png" alt="" aria-hidden="true" />',
'<div class="panel-body">',
bodyHTML,
'</div>',
'</section>',
].join('');
}
function renderCPRow(row) {
var center = row.type === 'score'
? [
'<img class="score-heart" src="./assets/score-heart.png" alt="" aria-hidden="true" />',
'<strong class="score-value">',
escapeHTML(row.score),
'</strong>',
].join('')
: [
'<div class="gift-center">',
'<img src="./assets/gift-plane.png" alt="" aria-hidden="true" />',
'</div>',
].join('');
return [
'<article class="cp-row">',
'<div class="avatar avatar-left"><img src="./assets/avatar-sample.png" alt="" /></div>',
'<div class="avatar avatar-right"><img src="./assets/avatar-sample.png" alt="" /></div>',
'<span class="name name-left">',
escapeHTML(row.leftName),
'</span>',
'<span class="name name-right">',
escapeHTML(row.rightName),
'</span>',
'<img class="deco-line deco-left" src="./assets/deco-line.png" alt="" aria-hidden="true" />',
'<img class="deco-line deco-right" src="./assets/deco-line.png" alt="" aria-hidden="true" />',
center,
'</article>',
].join('');
}
function renderPlaza() {
var firstBody = [
'<h2 class="panel-title">',
t('cp.plazaGiftTitle', 'Gifts exchanged between CPs ≥ 5000 gold will be displayed here'),
'</h2>',
'<p class="panel-subtitle">',
t('cp.plazaGiftSubtitle', '(Display the last 10 records, sorted in ascending order.)'),
'</p>',
'<div class="cp-list">',
mockData.plaza.exchangeRows.map(renderCPRow).join(''),
'</div>',
].join('');
var secondBody = [
'<h2 class="panel-title">',
t('cp.previousNo1Title', 'The previous number one position'),
'</h2>',
'<p class="panel-subtitle">',
t('cp.plazaGiftSubtitle', '(Display the last 10 records, sorted in ascending order.)'),
'</p>',
'<div class="cp-list">',
mockData.plaza.previousRows.map(renderCPRow).join(''),
'</div>',
].join('');
return [
renderPanel('plaza-panel-a', 1816, 1152, firstBody),
renderPanel('plaza-panel-b', 3024, 1126, secondBody),
].join('');
}
function renderInlineCountdown() {
var units = [
['days', 'cp.days', 'D'],
['hours', 'cp.hours', 'H'],
['minutes', 'cp.minutes', 'M'],
['seconds', 'cp.seconds', 'S'],
];
return [
'<div class="countdown-inline">',
units
.map(function (unit) {
return [
'<div class="time-unit">',
'<img src="./assets/countdown-heart.png" alt="" aria-hidden="true" />',
'<strong data-count="',
unit[0],
'">02</strong>',
'<span>',
t(unit[1], unit[2]),
'</span>',
'</div>',
].join('');
})
.join(''),
'</div>',
].join('');
}
function renderPodiumCard(item) {
var crown = item.place === 1 ? 'rank-crown-gold.png' : item.place === 2 ? 'rank-crown-silver.png' : 'crown-bronze.png';
return [
'<article class="podium-card rank-',
item.place,
'">',
'<img class="podium-bg" src="./assets/rank-wing-heart.png" alt="" aria-hidden="true" />',
'<img class="podium-crown" src="./assets/',
crown,
'" alt="" aria-hidden="true" />',
'<div class="avatar podium-avatar left"><img src="./assets/avatar-sample.png" alt="" /></div>',
'<div class="avatar podium-avatar right"><img src="./assets/avatar-sample.png" alt="" /></div>',
'<div class="podium-names"><span>',
escapeHTML(item.leftName),
'</span><span>',
escapeHTML(item.rightName),
'</span></div>',
'</article>',
].join('');
}
function renderRankRow(item) {
return renderCPRow({
type: 'score',
leftName: item.leftName,
rightName: item.rightName,
score: item.score,
});
}
function renderRank() {
var podium = [
'<section class="rank-podium">',
mockData.rank.map(renderPodiumCard).join(''),
'</section>',
].join('');
var panelBody = [renderInlineCountdown(), '<div class="cp-list">', mockData.rank.concat(mockData.rank.slice(0, 1)).map(renderRankRow).join(''), '</div>'].join('');
return podium + renderPanel('rank-panel', 2430, 1107, panelBody);
}
function renderRewardItem(item) {
return [
'<article class="reward-card">',
'<div class="reward-icon-box"><img src="./assets/reward-frame.png" alt="" aria-hidden="true" /></div>',
'<div class="reward-name">',
t(item.key, item.key),
'</div>',
item.noteKey ? '<div class="reward-note">' + t(item.noteKey, '') + '</div>' : '',
'</article>',
].join('');
}
function renderRewardPanel(section, index) {
var className = ['reward-panel', 'reward-' + ['a', 'b', 'c'][index]].join(' ');
var top = [1816, 2784, 3752][index];
var body = [
'<img class="reward-crown" src="./assets/',
section.crown,
'" alt="" aria-hidden="true" />',
'<div class="reward-grid">',
section.items.map(renderRewardItem).join(''),
'</div>',
].join('');
return renderPanel(className, top, 912, body);
}
function renderRewards() {
return mockData.rewards.map(renderRewardPanel).join('');
}
function render() {
document.title = t('cp.pageTitle', 'Last Weekly CP');
tabContent.innerHTML = activeTab === 'rank' ? renderRank() : activeTab === 'rewards' ? renderRewards() : renderPlaza();
applyTabState();
updateStageHeight();
updateCountdown();
}
function applyTabState() {
tabs.forEach(function (tab) {
var isActive = tab.getAttribute('data-tab') === activeTab;
tab.classList.toggle('is-active', isActive);
tab.querySelector('img').src = isActive ? './assets/tab-active.png' : './assets/tab-inactive.png';
});
page.setAttribute('data-tab', activeTab);
}
function updateStageHeight() {
var designHeight = stageHeights[activeTab] || stageHeights.plaza;
stage.style.minHeight = designHeight + 'px';
stage.style.height = designHeight + 'px';
stageWrap.style.height = Math.ceil(designHeight * (VIEWPORT_WIDTH / DESIGN_WIDTH)) + 'px';
copyright.style.top = designHeight - 77 + 'px';
var scale = Math.min(window.innerWidth / VIEWPORT_WIDTH, 1);
document.documentElement.style.setProperty('--app-scale', String(scale));
document.body.style.minHeight = Math.ceil(stageWrap.offsetHeight * scale) + 'px';
viewport.style.minHeight = stageWrap.style.height;
page.style.minHeight = stageWrap.style.height;
}
function bindTabs() {
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
activeTab = tab.getAttribute('data-tab') || 'plaza';
render();
});
});
}
function init() {
countdownEndAt = resolveFallbackEndAt();
bindTabs();
render();
countdownTimer = window.setInterval(updateCountdown, 1000);
window.addEventListener('resize', updateStageHeight);
window.addEventListener('hyapp:i18n-ready', render);
}
window.addEventListener('beforeunload', function () {
if (countdownTimer) window.clearInterval(countdownTimer);
});
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();