(function () { var ASSET_BASE = './assets/recharge-activity/'; var viewport = document.getElementById('appViewport'); var page = document.getElementById('page'); var stage = document.querySelector('.stage'); var rewardSections = document.getElementById('rewardSections'); var bgBottom = document.querySelector('.bg-bottom'); 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 DESIGN_WIDTH = 1080; var VIEWPORT_WIDTH = 375; var MAX_PAGE_WIDTH = 430; var DEFAULT_DESIGN_HEIGHT = 3273; var designHeight = DEFAULT_DESIGN_HEIGHT; var countdownEndAt = resolveCountdownEndAt(); var countdownTimer = 0; var state = { user: { name: '', id: '', avatar: ASSET_BASE + 'profile-avatar.png', accumulatedRecharge: 0, }, rewards: [], claiming: {}, loading: true, error: '', }; var fallbackCards = [ { price: '$100', icon: ASSET_BASE + 'reward-icon.png', left: 111, top: 293, }, { price: '$100', icon: ASSET_BASE + 'reward-icon.png', left: 560, top: 293, }, ]; function getParams() { return new URLSearchParams(window.location.search || ''); } function t(key, fallback) { return window.HyAppI18n && window.HyAppI18n.t ? window.HyAppI18n.t(key, fallback) : fallback || key; } function escapeHTML(value) { return String(value || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function twoDigits(value) { return String(Math.max(0, value)).padStart(2, '0'); } function showToast(message) { if (window.HyAppToast && window.HyAppToast.show) { window.HyAppToast.show(message); return; } window.dispatchEvent( new CustomEvent('hyapp:toast', { detail: { message: message } }) ); } function numeric(value, fallback) { var number = Number(value); return Number.isFinite(number) ? number : fallback || 0; } function parseJSON(value) { if (!value || typeof value !== 'string') return null; try { return JSON.parse(value); } catch (_) { return null; } } function firstValue() { for (var i = 0; i < arguments.length; i += 1) { var value = arguments[i]; if (value !== undefined && value !== null && value !== '') return value; } return ''; } function formatUSD(value) { var amount = numeric(value, 0); return ( '$' + amount.toLocaleString('en-US', { maximumFractionDigits: 2, minimumFractionDigits: amount % 1 === 0 ? 0 : 2, }) ); } function monthlyResetEndAt() { var now = new Date(); return new Date( now.getFullYear(), now.getMonth() + 1, 1, 0, 0, 0, 0 ).getTime(); } function activityTimeEndAt(activityTime) { var matches = String(activityTime || '').match( /(\d{1,2})\s*\/\s*(\d{1,2})\s*$/ ); if (!matches) return 0; var now = new Date(); var day = Number(matches[1]); var month = Number(matches[2]); if (!Number.isFinite(day) || !Number.isFinite(month)) return 0; // 后端月累充返回的 activityTime 是 DD/MM - DD/MM,不带年份;结束日次日 00:00 就是月度清空点。 var endAt = new Date( now.getFullYear(), month - 1, day + 1, 0, 0, 0, 0 ).getTime(); return Number.isFinite(endAt) && endAt > Date.now() ? endAt : 0; } function resolveCountdownEndAt(statusData) { var params = getParams(); var explicitEnd = Date.parse( params.get('end_at') || params.get('endAt') || '' ); if (Number.isFinite(explicitEnd) && explicitEnd > Date.now()) return explicitEnd; var hours = Number(params.get('hours')); if (Number.isFinite(hours) && hours > 0) return Date.now() + hours * 60 * 60 * 1000; return ( activityTimeEndAt(statusData && statusData.activityTime) || monthlyResetEndAt() ); } function getPageWidth() { var viewportWidth = window.innerWidth || document.documentElement.clientWidth || VIEWPORT_WIDTH; return Math.min(Math.max(viewportWidth, 1), MAX_PAGE_WIDTH); } function updateScale() { var pageWidth = getPageWidth(); var designScale = pageWidth / DESIGN_WIDTH; var scaledHeight = Math.ceil(designHeight * designScale); // 视觉稿仍以 1080 坐标布局,只把整张背景和内容按真实 WebView 宽度等比缩放,避免 412px 等机型露出页面两侧空白。 document.documentElement.style.setProperty( '--page-width', pageWidth + 'px' ); document.documentElement.style.setProperty( '--design-scale', String(designScale) ); viewport.style.minHeight = scaledHeight + 'px'; viewport.style.height = scaledHeight + 'px'; page.style.minHeight = scaledHeight + 'px'; page.style.height = scaledHeight + 'px'; document.body.style.minHeight = Math.max(scaledHeight, window.innerHeight || 0) + 'px'; } function updateCountdown() { var diff = Math.max(0, countdownEndAt - Date.now()); var totalSeconds = Math.floor(diff / 1000); var days = Math.floor(totalSeconds / 86400); var hours = Math.floor((totalSeconds % 86400) / 3600); var minutes = Math.floor((totalSeconds % 3600) / 60); var seconds = totalSeconds % 60; countNodes.days.textContent = twoDigits(days); countNodes.hours.textContent = twoDigits(hours); countNodes.minutes.textContent = twoDigits(minutes); countNodes.seconds.textContent = twoDigits(seconds); } function normalizeUser(profile, statusData) { profile = profile || {}; var params = getParams(); return { name: firstValue( profile.userNickname, profile.username, profile.name, params.get('name'), 'Aslan' ), id: String( firstValue( profile.specialId, profile.displayUserId, profile.display_user_id, profile.account, profile.id, profile.userId, params.get('uid'), params.get('id'), '' ) ), avatar: firstValue( profile.userAvatar, profile.avatar, profile.headImg, profile.headImage, params.get('avatar'), ASSET_BASE + 'profile-avatar.png' ), accumulatedRecharge: firstValue( statusData && statusData.monthlyRecharge, params.get('recharge'), 0 ), }; } function rewardProps(resource) { var group = resource && resource.activityResource ? resource.activityResource.propsGroup || {} : {}; return Array.isArray(group.activityRewardProps) ? group.activityRewardProps : []; } function rule(resource) { return resource && resource.activityResource ? resource.activityResource.rule || {} : {}; } function thresholdAmount(resource) { var data = parseJSON(rule(resource).jsonData) || {}; return numeric(data.quantity, 0); } function cardLayout(count, index) { if (count >= 4) { return ( [ { left: 70.5, top: 278 }, { left: 292.5, top: 278 }, { left: 514.5, top: 278 }, { left: 736.5, top: 278 }, ][index] || { left: 736.5, top: 278 } ); } if (count === 3) { return ( [ { left: 70.5, top: 278 }, { left: 339.5, top: 278 }, { left: 608.5, top: 278 }, ][index] || { left: 608.5, top: 278 } ); } return ( [ { left: 111, top: 293 }, { left: 560, top: 293 }, ][index] || { left: 560, top: 293 } ); } function normalizeCard(item, threshold, count, index) { var pos = cardLayout(count, index); return { price: item && item.amount ? formatUSD(item.amount) : formatUSD(threshold), icon: (item && item.cover) || ASSET_BASE + 'reward-icon.png', left: pos.left, top: pos.top, }; } function normalizeReward(resource, index) { var currentRule = rule(resource); var ruleId = String(currentRule.id || resource.ruleId || ''); var threshold = thresholdAmount(resource); var props = rewardProps(resource).slice(0, 4); var receiveStatus = String(resource.receiveStatus || '').toUpperCase(); var claimed = receiveStatus === 'RECEIVED'; var claimable = receiveStatus === 'NOT_RECEIVED'; var cardCount = props.length || fallbackCards.length; return { ruleId: ruleId, amount: formatUSD(threshold), top: 1776 + index * 690, background: index % 2 === 0 ? ASSET_BASE + 'reward-section-a.png' : ASSET_BASE + 'reward-section-b.png', claim: { text: claimed ? t('rechargeReward.claimed', 'Claimed') : t('rechargeReward.claim', 'Claim'), claimable: claimable, claimed: claimed, loading: Boolean(state.claiming[ruleId]), }, cards: props.length ? props.map(function (item, cardIndex) { return normalizeCard( item, threshold, cardCount, cardIndex ); }) : fallbackCards.map(function (card) { return Object.assign({}, card, { price: formatUSD(threshold), }); }), }; } function normalizeRewards(statusData) { var resources = statusData && Array.isArray(statusData.activityResources) ? statusData.activityResources : []; return resources .map(function (item, index) { return normalizeReward(item, index); }) .filter(function (item) { return item.ruleId; }); } function renderProfile(user) { document.querySelector('[data-profile-avatar]').src = user.avatar || ASSET_BASE + 'profile-avatar.png'; document.querySelector('[data-profile-name]').textContent = user.name || 'Aslan'; document.querySelector('[data-profile-id]').textContent = t('rechargeReward.idPrefix', 'ID:') + ' ' + (user.id || '-'); document.querySelector('[data-profile-recharge]').textContent = t('rechargeReward.accumulatedRecharge', 'Accumulated Recharge:') + formatUSD(user.accumulatedRecharge || 0); } function renderRewardCard(card) { return [ '
',
'