371 lines
14 KiB
JavaScript
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, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
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();
|
|
}
|
|
})();
|