2026-07-17 11:54:43 +08:00

965 lines
34 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(function () {
'use strict';
var DESIGN_WIDTH = 1080;
var MAX_PAGE_WIDTH = 768;
var ASSET_BASE = './huwaa/assets/layers/';
var PODIUM_ASSETS = {
gold: 'podium-gold.webp',
silver: 'podium-silver.webp',
bronze: 'podium-bronze.webp',
};
var CROWN_ASSETS = {
1: 'crown-gold.webp',
2: 'crown-silver.webp',
3: 'crown-bronze.webp',
};
var STAGE_HEIGHTS = {
plaza: 4283,
rank: 3733,
rewards: 4782,
};
var PANEL_LAYOUT = {
plazaFirstTop: 1677,
plazaFirstHeight: 1168,
plazaSecondTop: 2904,
plazaSecondHeight: 1142,
rankPanelTop: 2484,
rankPanelHeight: 1142,
rewardFirstTop: 1677,
rewardPanelHeight: 960,
rewardPanelGap: 56,
};
var viewport = document.getElementById('huwaaCpViewport');
var page = document.getElementById('huwaaCpPage');
var stage = document.getElementById('huwaaCpStage');
var stageWrap = document.getElementById('huwaaCpStageWrap');
var tabContent = document.getElementById('huwaaCpTabContent');
var copyright = document.querySelector('.huwaa-cp-copyright');
var announcement = document.getElementById('huwaaCpAnnouncement');
var tabs = Array.prototype.slice.call(
document.querySelectorAll('.huwaa-cp-tab')
);
var requestGeneration = 0;
var countdownEndAt = 0;
var countdownTimer = 0;
var serverTimeOffsetMS = 0;
var activeTab = resolveInitialTab();
var activityData = {
statusState: 'loading',
plazaState: 'loading',
rank: [],
previousRows: [],
plazaRows: [],
rewards: [],
enabled: true,
};
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, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function safeImageURL(value) {
var source = String(value || '').trim();
if (!source) return '';
if (/^(https?:|data:image\/|blob:|\.\.?\/)/i.test(source)) {
return escapeHTML(source);
}
return '';
}
function resolveInitialTab() {
try {
var requested = new URLSearchParams(window.location.search).get(
'tab'
);
if (['plaza', 'rank', 'rewards'].indexOf(requested) >= 0) {
return requested;
}
} catch (_) {
// 无 URLSearchParams 的旧 WebView 使用设计稿默认的 CP Rank不影响核心接口加载。
}
return 'rank';
}
function isLocalRuntime() {
try {
var params = new URLSearchParams(window.location.search);
var hostname = String(window.location.hostname || '').toLowerCase();
return (
hostname === 'localhost' ||
hostname === '127.0.0.1' ||
hostname === '::1' ||
params.get('env') === 'local'
);
} catch (_) {
return false;
}
}
function isExplicitMockMode() {
try {
return (
isLocalRuntime() &&
new URLSearchParams(window.location.search).get('mock') === '1'
);
} catch (_) {
return false;
}
}
function twoDigits(value) {
return String(Math.max(0, Number(value) || 0)).padStart(2, '0');
}
function resolveExplicitEndAt() {
if (!isLocalRuntime()) return 0;
try {
var params = new URLSearchParams(window.location.search);
var parsed = Date.parse(
params.get('end_at') || params.get('endAt') || ''
);
return Number.isFinite(parsed) && parsed > Date.now() ? parsed : 0;
} catch (_) {
return 0;
}
}
function nowMS() {
return Date.now() + serverTimeOffsetMS;
}
function getTimeParts() {
var diff =
countdownEndAt > 0 ? Math.max(0, countdownEndAt - nowMS()) : 0;
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 initials(name) {
var normalized = String(name || '').trim();
return normalized ? normalized.charAt(0).toUpperCase() : 'CP';
}
function renderAvatar(className, avatar, name) {
var imageURL = safeImageURL(avatar);
return [
'<span class="huwaa-cp-avatar ',
className,
'" aria-hidden="true">',
imageURL
? '<img src="' + imageURL + '" alt="" loading="lazy" />'
: '<span>' + escapeHTML(initials(name)) + '</span>',
'</span>',
].join('');
}
function renderPodiumAvatar(className, avatar, name) {
var imageURL = safeImageURL(avatar);
return [
'<span class="huwaa-cp-podium-avatar ',
className,
'" aria-hidden="true">',
imageURL
? '<img src="' + imageURL + '" alt="" loading="lazy" />'
: '<span>' + escapeHTML(initials(name)) + '</span>',
'</span>',
].join('');
}
function renderState(state, retryScope) {
var key =
state === 'error'
? 'cp.error'
: state === 'loading'
? 'cp.loading'
: state === 'disabled'
? 'cp.disabled'
: state === 'unavailable'
? 'cp.unavailable'
: 'cp.empty';
var fallback =
state === 'error'
? 'Unable to load activity data.'
: state === 'loading'
? 'Loading...'
: state === 'disabled'
? 'This activity is not available.'
: state === 'unavailable'
? 'No data source is available yet.'
: 'No data yet.';
return [
'<div class="huwaa-cp-state ',
state === 'loading' ? 'is-loading' : '',
'" role="status" aria-live="polite">',
'<span>',
escapeHTML(t(key, fallback)),
'</span>',
retryScope
? '<button class="huwaa-cp-retry" type="button" data-retry="' +
escapeHTML(retryScope) +
'">' +
escapeHTML(t('cp.retry', 'Retry')) +
'</button>'
: '',
'</div>',
].join('');
}
function renderPanel(options) {
var classes = ['huwaa-cp-panel'];
if (options.className) classes.push(options.className);
return [
'<section class="',
classes.join(' '),
'"',
options.ariaLabel
? ' aria-label="' + escapeHTML(options.ariaLabel) + '"'
: '',
' style="top:',
options.top,
'px;height:',
options.height,
'px">',
'<div class="huwaa-cp-panel-body">',
options.body,
'</div>',
'</section>',
].join('');
}
function renderPanelHeader(
titleKey,
titleFallback,
subtitleKey,
subtitleFallback
) {
return [
'<h2 class="huwaa-cp-panel-title">',
escapeHTML(t(titleKey, titleFallback)),
'</h2>',
subtitleKey
? '<p class="huwaa-cp-panel-subtitle">' +
escapeHTML(t(subtitleKey, subtitleFallback)) +
'</p>'
: '',
].join('');
}
function renderCPRow(row) {
var center =
row.type === 'gift'
? '<img class="huwaa-cp-gift" src="' +
ASSET_BASE +
'gift-plane.webp" alt="" aria-hidden="true" />'
: [
'<img class="huwaa-cp-score-heart" src="',
ASSET_BASE,
'score-heart.webp" alt="" aria-hidden="true" />',
'<strong class="huwaa-cp-score">',
escapeHTML(row.score),
'</strong>',
].join('');
return [
'<article class="huwaa-cp-row">',
renderAvatar('is-left', row.leftAvatar, row.leftName),
renderAvatar('is-right', row.rightAvatar, row.rightName),
'<span class="huwaa-cp-name is-left">',
escapeHTML(row.leftName),
'</span>',
'<span class="huwaa-cp-name is-right">',
escapeHTML(row.rightName),
'</span>',
'<img class="huwaa-cp-deco-line is-left" src="',
ASSET_BASE,
'deco-line-left.webp" alt="" aria-hidden="true" />',
'<img class="huwaa-cp-deco-line is-right" src="',
ASSET_BASE,
'deco-line-right.webp" alt="" aria-hidden="true" />',
center,
'</article>',
].join('');
}
function rowsOrState(rows, state, retryScope) {
if (rows.length) {
return (
'<div class="huwaa-cp-list">' +
rows.map(renderCPRow).join('') +
'</div>'
);
}
return renderState(
state === 'ready' ? 'empty' : state,
state === 'error' ? retryScope : ''
);
}
function renderPreviousPanel(top, height) {
var body =
renderPanelHeader(
'cp.previousWeekTopTitle',
'Previous Top 3',
'cp.previousWeekTopSubtitle',
'(Top CPs from the previous completed week.)'
) +
rowsOrState(
activityData.previousRows,
activityData.statusState,
'status'
);
return renderPanel({ top: top, height: height, body: body });
}
function renderPlaza() {
var firstBody =
renderPanelHeader(
'cp.huwaaPlazaGiftTitle',
'Gifts exchanged between CPs ≥ 5000 gold will be displayed here',
'cp.plazaGiftSubtitle',
'(Display the last 10 records, sorted in ascending order.)'
) +
rowsOrState(activityData.plazaRows, activityData.plazaState, '');
return [
renderPanel({
top: PANEL_LAYOUT.plazaFirstTop,
height: PANEL_LAYOUT.plazaFirstHeight,
className: 'is-gift-panel',
body: firstBody,
}),
renderPreviousPanel(
PANEL_LAYOUT.plazaSecondTop,
PANEL_LAYOUT.plazaSecondHeight
),
].join('');
}
function renderPodiumPair(item, place) {
if (!item) return '';
var level = place === 1 ? 'gold' : place === 2 ? 'silver' : 'bronze';
var accessibleLabel = [
t('cp.rankPlace', 'Rank'),
String(place),
item.leftName,
item.rightName,
].join(' ');
return [
'<article class="huwaa-cp-podium is-',
level,
'" aria-label="',
escapeHTML(accessibleLabel),
'">',
renderPodiumAvatar('is-left', item.leftAvatar, item.leftName),
renderPodiumAvatar('is-right', item.rightAvatar, item.rightName),
'<img class="huwaa-cp-podium-art" src="',
ASSET_BASE,
PODIUM_ASSETS[level],
'" alt="" aria-hidden="true" />',
'<span class="huwaa-cp-podium-name is-left">',
escapeHTML(item.leftName),
'</span>',
'<span class="huwaa-cp-podium-name is-right">',
escapeHTML(item.rightName),
'</span>',
'</article>',
].join('');
}
function renderRank() {
var podium = '';
if (activityData.rank.length) {
var rankAt = function (place) {
return activityData.rank.find(function (item) {
return item.place === place;
});
};
podium = [
'<section class="huwaa-cp-rank-podium" aria-label="',
escapeHTML(t('cp.rankPodiumLabel', 'Top CP ranking')),
'">',
renderPodiumPair(rankAt(1), 1),
renderPodiumPair(rankAt(2), 2),
renderPodiumPair(rankAt(3), 3),
'</section>',
].join('');
} else {
var rankStateKey =
activityData.statusState === 'error'
? 'cp.error'
: activityData.statusState === 'loading'
? 'cp.loading'
: activityData.statusState === 'disabled'
? 'cp.disabled'
: 'cp.empty';
var rankStateFallback =
activityData.statusState === 'error'
? 'Unable to load activity data.'
: activityData.statusState === 'loading'
? 'Loading...'
: activityData.statusState === 'disabled'
? 'This activity is not available.'
: 'No data yet.';
podium = [
'<p class="huwaa-cp-rank-empty" role="status" aria-live="polite">',
escapeHTML(t(rankStateKey, rankStateFallback)),
'</p>',
].join('');
}
return (
podium +
renderPreviousPanel(
PANEL_LAYOUT.rankPanelTop,
PANEL_LAYOUT.rankPanelHeight
)
);
}
function rewardName(item) {
var resourceName =
item.resourceName ||
item.name ||
item.resource_name ||
localizedResourceType(item.resource_type || item.resourceType) ||
t('cp.reward', 'Reward');
var duration = Number(item.duration_days || item.durationDays || 0);
var quantity = Number(item.quantity || 0);
if (duration > 0) {
return (
resourceName + '*' + duration + ' ' + t('cp.daysLong', 'days')
);
}
if (quantity > 0) return resourceName + '*' + quantity;
return resourceName;
}
function localizedResourceType(value) {
var type = String(value || '')
.trim()
.toLowerCase();
var keys = {
avatar_frame: ['cp.rewardAvatarFrame', 'Avatar frame'],
profile_card: ['cp.rewardProfileCard', 'Profile card'],
coin: ['cp.rewardCoins', 'Coins'],
vehicle: ['cp.rewardVehicle', 'Vehicle'],
chat_bubble: ['cp.rewardChatBubble', 'Chat bubble'],
badge: ['cp.rewardBadge', 'Badge'],
floating_screen: ['cp.rewardFloatingScreen', 'Floating screen'],
gift: ['cp.rewardGift', 'Gift'],
mic_seat_icon: ['cp.rewardMicSeatIcon', 'Mic seat icon'],
mic_seat_animation: [
'cp.rewardMicSeatAnimation',
'Mic seat animation',
],
emoji_pack: ['cp.rewardEmojiPack', 'Emoji pack'],
vip_trial_card: ['cp.rewardVipTrialCard', 'VIP trial card'],
};
return keys[type] ? t(keys[type][0], keys[type][1]) : '';
}
function rewardNote(item) {
return String(item.description || item.note || '').trim();
}
function renderRewardItem(item) {
var cover = safeImageURL(item.coverUrl || item.cover_url);
return [
'<article class="huwaa-cp-reward-card">',
'<div class="huwaa-cp-reward-icon">',
cover
? '<img class="huwaa-cp-reward-cover" src="' +
cover +
'" alt="" loading="lazy" />'
: '',
'<img class="huwaa-cp-reward-frame" src="',
ASSET_BASE,
'reward-frame.webp" alt="" aria-hidden="true" />',
'</div>',
'<div class="huwaa-cp-reward-name">',
escapeHTML(item.displayName || rewardName(item)),
'</div>',
item.note || rewardNote(item)
? '<div class="huwaa-cp-reward-note">' +
escapeHTML(item.note || rewardNote(item)) +
'</div>'
: '',
'</article>',
].join('');
}
function renderRewardPanel(section, index) {
var tierNo = Number(section.rankNo || index + 1);
if (tierNo < 1 || tierNo > 3) tierNo = index + 1;
var top =
PANEL_LAYOUT.rewardFirstTop +
index *
(PANEL_LAYOUT.rewardPanelHeight + PANEL_LAYOUT.rewardPanelGap);
var items = Array.isArray(section.items) ? section.items : [];
var tierLabels = {
1: t('cp.rewardTierGold', 'First-place rewards'),
2: t('cp.rewardTierSilver', 'Second-place rewards'),
3: t('cp.rewardTierBronze', 'Third-place rewards'),
};
var body = [
'<img class="huwaa-cp-reward-crown" src="',
ASSET_BASE,
CROWN_ASSETS[tierNo] || CROWN_ASSETS[3],
'" alt="" aria-hidden="true" />',
items.length
? '<div class="huwaa-cp-reward-grid">' +
items.map(renderRewardItem).join('') +
'</div>'
: renderState('empty', ''),
].join('');
return renderPanel({
top: top,
height: PANEL_LAYOUT.rewardPanelHeight,
className: 'huwaa-cp-reward-panel',
ariaLabel: tierLabels[tierNo],
body: body,
});
}
function renderRewards() {
if (!activityData.rewards.length) {
var state =
activityData.statusState === 'ready'
? 'empty'
: activityData.statusState;
return renderPanel({
top: PANEL_LAYOUT.rewardFirstTop,
height: PANEL_LAYOUT.rewardPanelHeight,
className: 'huwaa-cp-reward-panel',
body: renderState(state, state === 'error' ? 'status' : ''),
});
}
return [1, 2, 3]
.map(function (rankNo) {
return (
activityData.rewards.find(function (section) {
return section.rankNo === rankNo;
}) || { rankNo: rankNo, items: [] }
);
})
.map(renderRewardPanel)
.join('');
}
function formatScore(value) {
var number = Number(value || 0);
if (!Number.isFinite(number)) return '0';
try {
return number.toLocaleString(document.documentElement.lang || 'en');
} catch (_) {
return String(number);
}
}
function normalizePairUser(user) {
var source = user || {};
var userID = source.user_id || source.userId || '';
var displayID = source.display_user_id || source.displayUserId || '';
var name = source.username || source.name || displayID || userID || '';
if (!name && !source.avatar) return null;
return {
name: name || 'CP',
avatar: source.avatar || source.avatar_url || '',
};
}
function normalizeRankEntry(entry, fallbackPlace) {
var source = entry || {};
var userA = normalizePairUser(source.user_a || source.userA);
var userB = normalizePairUser(source.user_b || source.userB);
if (!userA || !userB) return null;
return {
type: 'score',
place: Number(source.rank || fallbackPlace || 0),
score: formatScore(source.score || source.intimacy_value || 0),
leftName: userA.name,
rightName: userB.name,
leftAvatar: userA.avatar,
rightAvatar: userB.avatar,
};
}
function normalizeRewardSection(section, fallbackRankNo) {
var source = section || {};
var group = source.resource_group || source.resourceGroup || {};
var groupItems = Array.isArray(group.items) ? group.items : [];
var items = Array.isArray(source.items) ? source.items : [];
return {
rankNo: Number(
source.rank_no || source.rankNo || fallbackRankNo || 0
),
items: items.map(function (item, index) {
var groupItem = groupItems[index] || {};
var resource = groupItem.resource || {};
return {
resourceName: String(resource.name || '').trim(),
resource_type:
item.resource_type ||
item.resourceType ||
resource.resource_type ||
resource.resourceType ||
'',
duration_days: item.duration_days || item.durationDays || 0,
quantity: item.quantity || groupItem.quantity || 0,
coverUrl:
item.cover_url ||
item.coverUrl ||
resource.preview_url ||
resource.previewUrl ||
resource.asset_url ||
resource.assetUrl ||
'',
};
}),
};
}
function applyActivityStatus(status) {
var source = status || {};
var serverTimeMS = Number(
source.server_time_ms || source.serverTimeMS || 0
);
if (serverTimeMS > 0) {
serverTimeOffsetMS = serverTimeMS - Date.now();
}
activityData.enabled = source.enabled !== false;
if (!activityData.enabled) {
countdownEndAt = 0;
activityData.rank = [];
activityData.previousRows = [];
activityData.rewards = [];
activityData.statusState = 'disabled';
return;
}
var endMS = Number(
(source.period && (source.period.end_ms || source.period.endMS)) ||
source.end_ms ||
source.endMS ||
0
);
countdownEndAt = endMS > nowMS() ? endMS : 0;
activityData.rank = Array.isArray(source.entries)
? source.entries
.map(function (entry, index) {
return normalizeRankEntry(entry, index + 1);
})
.filter(Boolean)
.sort(function (left, right) {
return left.place - right.place;
})
.slice(0, 3)
: [];
activityData.previousRows = Array.isArray(source.previous_entries)
? source.previous_entries
.map(function (entry, index) {
return normalizeRankEntry(entry, index + 1);
})
.filter(Boolean)
.slice(0, 3)
: [];
activityData.rewards = Array.isArray(source.rewards)
? source.rewards
.map(function (section, index) {
return normalizeRewardSection(section, index + 1);
})
.filter(function (section) {
return section.rankNo >= 1 && section.rankNo <= 3;
})
.sort(function (left, right) {
return left.rankNo - right.rankNo;
})
: [];
activityData.statusState = 'ready';
}
function applyMockData() {
var pairs = [
['Avery', 'Noor', 126580],
['Mina', 'Elif', 118420],
['Lina', 'Sofia', 109730],
['Dara', 'Maya', 98760],
];
function pair(row, index, type) {
return {
type: type || 'score',
place: index + 1,
score: formatScore(row[2]),
rawScore: row[2],
leftName: row[0],
rightName: row[1],
leftAvatar: '',
rightAvatar: '',
};
}
activityData.rank = pairs.slice(0, 3).map(function (row, index) {
return pair(row, index, 'score');
});
activityData.previousRows = pairs
.slice(0, 3)
.map(function (row, index) {
return pair(row, index, 'score');
});
activityData.plazaRows = pairs.map(function (row, index) {
return pair(row, index, 'gift');
});
activityData.rewards = ['gold', 'silver', 'bronze'].map(
function (_, tier) {
return {
rankNo: tier + 1,
items: [
{
displayName: t('cp.rewardMedal7', 'Medal*7 days'),
note:
tier === 0
? t(
'cp.rewardMedalNote',
'(Permanent medal after getting 3 times)'
)
: '',
},
{
displayName: t(
'cp.rewardHonorBanner7',
'Honor banner*7 days'
),
},
{
displayName: t(
'cp.rewardHeadwear7',
'Headwear*7days'
),
},
{ displayName: t('cp.rewardMount7', 'Mount*7days') },
{
displayName: t(
'cp.rewardCoins20000',
'Coins*20000'
),
},
{ displayName: t('cp.rewardDuke7', 'Duke*7days') },
],
};
}
);
activityData.statusState = 'ready';
activityData.plazaState = 'ready';
activityData.enabled = true;
serverTimeOffsetMS = 0;
countdownEndAt = Date.now() + 2 * 86400000 + 2 * 3600000 + 2 * 60000;
}
function loadData(scope) {
var generation = ++requestGeneration;
if (isExplicitMockMode()) {
applyMockData();
render();
return Promise.resolve();
}
if (!scope || scope === 'status') {
activityData.statusState = 'loading';
activityData.rank = [];
activityData.previousRows = [];
activityData.rewards = [];
}
// 设计稿的 Plaza 是“单笔 CP 送礼事件流”;当前公开 intimacy-leaderboard 是累计亲密榜,
// 两者排序和业务含义不同。在后端事件 feed 上线前保持明确不可用态,绝不把累计榜伪装成送礼记录。
activityData.plazaState = 'unavailable';
activityData.plazaRows = [];
render();
var statusRequest =
window.HyAppAPI && window.HyAppAPI.cpWeeklyRank
? window.HyAppAPI.cpWeeklyRank.status({ limit: 3 })
: Promise.reject(new Error('cp_weekly_rank_api_unavailable'));
// allSettled 统一承接未来可并行加入的 Plaza 事件 feedgeneration 防止重试后的旧周榜响应覆盖新状态。
return Promise.allSettled([statusRequest]).then(function (results) {
if (generation !== requestGeneration) return;
if (results[0].status === 'fulfilled') {
applyActivityStatus(results[0].value);
} else {
activityData.statusState = 'error';
}
render();
});
}
function applyTabState() {
tabs.forEach(function (tab) {
var isActive = tab.getAttribute('data-tab') === activeTab;
tab.classList.toggle('is-active', isActive);
tab.setAttribute('aria-selected', String(isActive));
tab.setAttribute('tabindex', isActive ? '0' : '-1');
var image = tab.querySelector('img');
if (image) {
image.src =
ASSET_BASE +
(isActive ? 'tab-active.webp' : 'tab-inactive.webp');
}
});
var activeButton = tabs.find(function (tab) {
return tab.getAttribute('data-tab') === activeTab;
});
tabContent.setAttribute(
'aria-labelledby',
activeButton ? activeButton.id : 'huwaaCpTabRank'
);
page.setAttribute('data-tab', activeTab);
}
function getPageWidth() {
var viewportWidth =
window.innerWidth ||
document.documentElement.clientWidth ||
DESIGN_WIDTH;
return Math.min(Math.max(viewportWidth, 1), MAX_PAGE_WIDTH);
}
function updateStageHeight() {
var designHeight = STAGE_HEIGHTS[activeTab] || STAGE_HEIGHTS.rank;
// Rewards 的完整 4782px 高度仅对应三档奖励都存在的设计态。加载失败、活动关闭或确实为空时
// 只有首块状态面板;按面板底部收拢页面,避免把不存在的二、三档内容伪装成长段空白。
if (activeTab === 'rewards' && !activityData.rewards.length) {
designHeight =
PANEL_LAYOUT.rewardFirstTop +
PANEL_LAYOUT.rewardPanelHeight +
160;
}
var pageWidth = getPageWidth();
var designScale = pageWidth / DESIGN_WIDTH;
stage.style.height = designHeight + 'px';
stage.style.minHeight = designHeight + 'px';
document.documentElement.style.setProperty(
'--huwaa-cp-page-width',
pageWidth + 'px'
);
document.documentElement.style.setProperty(
'--huwaa-cp-design-scale',
String(designScale)
);
stageWrap.style.height = Math.ceil(designHeight * designScale) + 'px';
stageWrap.style.minHeight = stageWrap.style.height;
copyright.style.top = designHeight - 77 + 'px';
document.body.style.minHeight = stageWrap.style.height;
viewport.style.minHeight = stageWrap.style.height;
page.style.minHeight = stageWrap.style.height;
}
function render() {
document.title = t('cp.pageTitle', 'Last Weekly CP');
tabContent.innerHTML =
activeTab === 'plaza'
? renderPlaza()
: activeTab === 'rewards'
? renderRewards()
: renderRank();
applyTabState();
updateStageHeight();
updateCountdown();
}
function updateURLTab() {
try {
var nextURL = new URL(window.location.href);
nextURL.searchParams.set('tab', activeTab);
window.history.replaceState(null, '', nextURL.toString());
} catch (_) {
// tab 切换本身不依赖 URLhistory 被宿主禁用时仅放弃可分享状态参数。
}
}
function selectTab(nextTab, shouldFocus) {
if (['plaza', 'rank', 'rewards'].indexOf(nextTab) < 0) return;
activeTab = nextTab;
updateURLTab();
render();
var activeButton = tabs.find(function (tab) {
return tab.getAttribute('data-tab') === activeTab;
});
if (shouldFocus && activeButton) activeButton.focus();
announcement.textContent =
activeButton && activeButton.querySelector('span')
? activeButton.querySelector('span').textContent
: '';
}
function bindInteractions() {
tabs.forEach(function (tab, index) {
tab.addEventListener('click', function () {
selectTab(tab.getAttribute('data-tab') || 'rank', false);
});
tab.addEventListener('keydown', function (event) {
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')
return;
event.preventDefault();
var delta = event.key === 'ArrowRight' ? 1 : -1;
var nextIndex = (index + delta + tabs.length) % tabs.length;
selectTab(tabs[nextIndex].getAttribute('data-tab'), true);
});
});
tabContent.addEventListener('click', function (event) {
var retry = event.target.closest('[data-retry]');
if (retry) loadData(retry.getAttribute('data-retry') || 'status');
});
}
function init() {
countdownEndAt = resolveExplicitEndAt();
bindInteractions();
render();
loadData();
countdownTimer = window.setInterval(updateCountdown, 1000);
window.addEventListener('resize', updateStageHeight);
window.addEventListener('hyapp:i18n-ready', function () {
if (isExplicitMockMode()) applyMockData();
render();
});
}
window.addEventListener('beforeunload', function () {
requestGeneration += 1;
if (countdownTimer) window.clearInterval(countdownTimer);
});
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();