(function () {
'use strict';
var DESIGN_WIDTH = 1080;
var BASE_STAGE_HEIGHT = 3273;
var SECTION_STEP = 720;
var MAX_VIEWPORT_WIDTH = 430;
var STATUS_PATH = '/api/v1/activities/cumulative-recharge-reward';
var appViewport = document.getElementById('appViewport');
var stageWrap = document.getElementById('stageWrap');
var stage = document.getElementById('stage');
var countdown = document.getElementById('countdown');
var rewardSections = document.getElementById('rewardSections');
var profileName = document.getElementById('profileName');
var profileID = document.getElementById('profileID');
var profileRecharge = document.getElementById('profileRecharge');
var profileAvatar = document.getElementById('profileAvatar');
var pageStatus = document.getElementById('pageStatus');
var countNodes = {
days: document.querySelector('[data-count="days"]'),
hours: document.querySelector('[data-count="hours"]'),
minutes: document.querySelector('[data-count="minutes"]'),
seconds: document.querySelector('[data-count="seconds"]'),
};
var currentData = null;
var designHeight = BASE_STAGE_HEIGHT;
var countdownEndAt = 0;
var timeOffsetMS = 0;
var countdownTimer = 0;
function queryParams() {
return new URLSearchParams(window.location.search || '');
}
function shouldUseMock(params) {
var value = String(params.get('mock') || '').toLowerCase();
// 设计数据只允许显式 mock=1/true;生产接口失败时不得回退到看似可用的虚假奖励。
return value === '1' || value === 'true';
}
function t(key, fallback) {
return window.HyAppI18n && typeof window.HyAppI18n.t === 'function'
? window.HyAppI18n.t(key, fallback)
: fallback || key;
}
function escapeHTML(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function firstValue(source, keys, fallback) {
source = source && typeof source === 'object' ? source : {};
for (var index = 0; index < keys.length; index += 1) {
var value = source[keys[index]];
if (value !== undefined && value !== null && value !== '')
return value;
}
return fallback;
}
function numberValue(source, keys, fallback) {
var value = Number(firstValue(source, keys, fallback));
return Number.isFinite(value) ? value : Number(fallback || 0);
}
function wholeUSD(minor) {
var value = Math.max(0, Number(minor || 0)) / 100;
return value % 1 === 0
? String(value)
: value.toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
}
function tierAmount(minor) {
return '$' + wholeUSD(minor);
}
function twoDigits(value) {
return String(Math.max(0, Math.floor(value))).padStart(2, '0');
}
function updateScale() {
var viewportWidth = Math.min(
window.innerWidth || 375,
MAX_VIEWPORT_WIDTH
);
var scale = viewportWidth / DESIGN_WIDTH;
var renderedHeight = designHeight * scale;
document.documentElement.style.setProperty(
'--fami-stage-scale',
String(scale)
);
document.documentElement.style.setProperty(
'--fami-stage-height',
designHeight + 'px'
);
document.documentElement.style.setProperty(
'--fami-background-bottom-top',
designHeight - 578 + 'px'
);
appViewport.style.minHeight = renderedHeight + 'px';
stageWrap.style.height = renderedHeight + 'px';
stage.style.height = designHeight + 'px';
}
function mockData(params) {
var rechargeValue = Number(params.get('recharge') || 5998);
var explicitEnd = Date.parse(
params.get('end_at') || params.get('endAt') || ''
);
var rewardItem = {
item_type: 'resource',
resource: {
name: 'Figma reward fixture',
preview_url: './fami/assets/mock-reward-822-2971.png',
},
};
return {
enabled: true,
user: {
username: params.get('name') || 'nanfangjian',
display_user_id:
params.get('uid') || params.get('id') || '12345678',
avatar:
params.get('avatar') ||
'./fami/assets/mock-avatar-822-2960.png',
},
period: {
// Figma 节点没有倒计时数字;只有显式 end_at 才展示动态数字,避免凭空猜一个验收值。
end_ms: Number.isFinite(explicitEnd) ? explicitEnd : 0,
},
progress: {
total_usd_minor: Math.max(0, Math.round(rechargeValue * 100)),
},
tiers: [
{
tier_id: 1,
threshold_usd_minor: 10000,
reached: true,
resource_group: { items: [rewardItem, rewardItem] },
},
{
tier_id: 2,
threshold_usd_minor: 10000,
reached: true,
resource_group: {
items: [rewardItem, rewardItem, rewardItem],
},
},
],
grants: [],
server_time_ms: Date.now(),
};
}
function resourcePreview(item) {
var resource = (item && item.resource) || {};
// preview_url 是 gateway 明确下发给静态 H5 的封面;asset_url 可能是动画,仅作为旧数据兜底。
return String(
firstValue(
resource,
[
'preview_url',
'previewUrl',
'asset_url',
'assetUrl',
'animation_url',
'animationUrl',
],
''
) || ''
);
}
function normalizeData(payload) {
payload = payload && payload.status ? payload.status : payload || {};
var config = payload.config || {};
var rawTiers = Array.isArray(payload.tiers)
? payload.tiers
: Array.isArray(config.tiers)
? config.tiers
: [];
var grants = Array.isArray(payload.grants) ? payload.grants : [];
var grantByTier = {};
grants.forEach(function (grant) {
var tierID = String(firstValue(grant, ['tier_id', 'tierId'], ''));
if (tierID) grantByTier[tierID] = grant;
});
var tiers = rawTiers
.slice()
.sort(function (a, b) {
return (
numberValue(a, ['sort_order', 'sortOrder'], 0) -
numberValue(b, ['sort_order', 'sortOrder'], 0)
);
})
.map(function (tier) {
var group = tier.resource_group || tier.resourceGroup || {};
var items = Array.isArray(group.items) ? group.items : [];
var tierID = String(
firstValue(tier, ['tier_id', 'tierId'], '')
);
var grant = tier.grant || grantByTier[tierID] || null;
return {
tierID: tierID,
thresholdUSDMinor: numberValue(
tier,
['threshold_usd_minor', 'thresholdUsdMinor'],
0
),
reached: Boolean(firstValue(tier, ['reached'], false)),
grant: grant,
cards: items.slice(0, 3).map(function (item) {
return {
name: firstValue(
(item && item.resource) || {},
['name'],
''
),
preview: resourcePreview(item),
};
}),
};
});
return {
enabled:
firstValue(
payload,
['enabled'],
firstValue(config, ['enabled'], true)
) !== false,
user: payload.user || {},
period: payload.period || {},
progress: payload.progress || {},
serverTimeMS: numberValue(
payload,
['server_time_ms', 'serverTimeMs'],
Date.now()
),
tiers: tiers,
};
}
function grantWasIssued(grant) {
if (!grant) return false;
var status = String(firstValue(grant, ['status'], '')).toLowerCase();
return (
['granted', 'success', 'succeeded', 'completed'].indexOf(status) >=
0
);
}
function cardPositions(count) {
if (count <= 1) return [425.5];
if (count === 2) return [201, 650];
return [160.5, 429.5, 698.5];
}
function renderCard(card, section, cardIndex, left) {
var media = card.preview
? '
'
: '';
return [
'',
'
',
media,
'',
escapeHTML(tierAmount(section.thresholdUSDMinor)),
'',
'',
].join('');
}
function renderSections(tiers) {
designHeight = Math.max(
BASE_STAGE_HEIGHT,
BASE_STAGE_HEIGHT + Math.max(0, tiers.length - 2) * SECTION_STEP
);
rewardSections.innerHTML = tiers
.map(function (tier, sectionIndex) {
var section = {
thresholdUSDMinor: tier.thresholdUSDMinor,
cardTop: sectionIndex === 0 ? 293 : 278,
};
var positions = cardPositions(tier.cards.length);
var actionKey = grantWasIssued(tier.grant)
? 'rechargeReward.claimed'
: 'rechargeReward.receive';
var actionFallback = grantWasIssued(tier.grant)
? 'Claimed'
: 'Receive';
return [
'',
'
',
'',
escapeHTML(
t(
'rechargeReward.rechargeTitle',
'Recharge {amount}'
).replace(
'{amount}',
tierAmount(tier.thresholdUSDMinor)
)
),
'
',
tier.cards
.map(function (card, cardIndex) {
return renderCard(
card,
section,
cardIndex,
positions[cardIndex]
);
})
.join(''),
// 累充奖励由 wallet 充值事件自动发放,gateway 没有 claim RPC;设计中的 Receive 仅显示服务端状态,禁止伪造领取请求。
'',
'

',
'
',
escapeHTML(t(actionKey, actionFallback)),
'',
'
',
'',
].join('');
})
.join('');
updateScale();
}
function renderProfile(data) {
var user = data.user || {};
var displayID = firstValue(
user,
['display_user_id', 'displayUserId', 'user_id', 'userId'],
''
);
var name = firstValue(user, ['username', 'name'], '');
var avatar = String(
firstValue(user, ['avatar', 'avatar_url', 'avatarUrl'], '') || ''
);
var totalMinor = numberValue(
data.progress || {},
['total_usd_minor', 'totalUsdMinor'],
0
);
profileName.textContent = name;
profileID.textContent = displayID
? t('rechargeReward.idPrefix', 'ID:') + ' ' + displayID
: '';
profileRecharge.textContent =
t('rechargeReward.accumulatedRecharge', 'Accumulated Recharge:') +
wholeUSD(totalMinor);
if (avatar) {
profileAvatar.src = avatar;
profileAvatar.hidden = false;
} else {
profileAvatar.removeAttribute('src');
profileAvatar.hidden = true;
}
}
function updateCountdown() {
if (!countdownEndAt) {
countdown.classList.remove('has-values');
Object.keys(countNodes).forEach(function (key) {
countNodes[key].textContent = '';
});
return;
}
var remaining = Math.max(
0,
countdownEndAt - (Date.now() - timeOffsetMS)
);
var totalSeconds = Math.floor(remaining / 1000);
countNodes.days.textContent = twoDigits(totalSeconds / 86400);
countNodes.hours.textContent = twoDigits((totalSeconds % 86400) / 3600);
countNodes.minutes.textContent = twoDigits((totalSeconds % 3600) / 60);
countNodes.seconds.textContent = twoDigits(totalSeconds % 60);
countdown.classList.add('has-values');
}
function render(data) {
currentData = normalizeData(data);
renderProfile(currentData);
renderSections(currentData.tiers);
timeOffsetMS = Date.now() - currentData.serverTimeMS;
countdownEndAt = numberValue(
currentData.period,
['end_ms', 'endMs'],
0
);
updateCountdown();
if (countdownTimer) window.clearInterval(countdownTimer);
if (countdownEndAt)
countdownTimer = window.setInterval(updateCountdown, 1000);
pageStatus.hidden = true;
pageStatus.textContent = '';
stage.setAttribute('aria-busy', 'false');
}
function loadStatus(params) {
if (shouldUseMock(params)) return Promise.resolve(mockData(params));
if (!window.HyAppAPI || typeof window.HyAppAPI.get !== 'function') {
return Promise.reject(new Error('api_unavailable'));
}
return window.HyAppAPI.get(STATUS_PATH);
}
function showLoadError(error) {
console.warn('[recharge-reward/fami] status load failed', error);
currentData = null;
rewardSections.innerHTML = '';
profileName.textContent = '';
profileID.textContent = '';
profileRecharge.textContent = '';
profileAvatar.hidden = true;
pageStatus.textContent = t('rechargeReward.loadFailed', 'Load failed');
pageStatus.hidden = false;
stage.setAttribute('aria-busy', 'false');
}
function init() {
var params = queryParams();
document.title = t('rechargeReward.pageTitle', 'Recharge Reward');
updateScale();
loadStatus(params).then(render).catch(showLoadError);
window.addEventListener('resize', updateScale);
window.addEventListener('hyapp:i18n-ready', function () {
document.title = t('rechargeReward.pageTitle', 'Recharge Reward');
if (currentData) {
// 语言包只影响 DOM 文案;复用已归一化的业务状态,不能再次按原始 API 结构解析而丢失奖励卡。
renderProfile(currentData);
renderSections(currentData.tiers);
updateCountdown();
} else if (!pageStatus.hidden)
pageStatus.textContent = t(
'rechargeReward.loadFailed',
'Load failed'
);
});
// 远程头像或奖励封面失效时只隐藏业务媒体,装饰框和真实金额状态仍保留,避免回退到伪造素材。
stage.addEventListener(
'error',
function (event) {
if (
event.target === profileAvatar ||
(event.target &&
event.target.classList.contains('reward-media'))
) {
event.target.hidden = true;
}
},
true
);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init, { once: true });
} else {
init();
}
})();