(function () { var DESIGN_WIDTH = 1080; var VIEWPORT_WIDTH = 375; var DESIGN_HEIGHT = 4620; var STORAGE_KEY = 'hy_agency_opening_end_at'; var viewport = document.getElementById('appViewport'); var stageWrap = document.getElementById('stageWrap'); var countdownTimer = 0; var countdownEndAt = 0; var timeOffsetMS = 0; var statusLoadedFromAPI = false; var state = null; 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 applyButton = document.getElementById('applyButton'); var rewardRows = document.getElementById('rewardRows'); var rulesList = document.getElementById('rulesList'); var fallbackRewards = [ { opening_revenue: '500K', reward_coins: '50K' }, { opening_revenue: '1M', reward_coins: '100K' }, { opening_revenue: '3M', reward_coins: '300K' }, { opening_revenue: '5M', reward_coins: '500K' }, { opening_revenue: '10M', reward_coins: '500K' }, ]; function params() { return new URLSearchParams(window.location.search || ''); } function t(key, fallback) { return window.HyAppI18n && window.HyAppI18n.t ? window.HyAppI18n.t(key, fallback) : fallback || key; } function twoDigits(value) { return String(Math.max(0, value)).padStart(2, '0'); } function serverNow() { return Date.now() - timeOffsetMS; } function updateViewportHeight() { var scale = Math.min(window.innerWidth / VIEWPORT_WIDTH, 1); var visibleHeight = Math.ceil( DESIGN_HEIGHT * (VIEWPORT_WIDTH / DESIGN_WIDTH) ); document.documentElement.style.setProperty( '--app-scale', String(scale) ); stageWrap.style.height = visibleHeight + 'px'; viewport.style.minHeight = visibleHeight + 'px'; document.body.style.minHeight = Math.ceil(visibleHeight * scale) + 'px'; } function fallbackEndAt() { var queryEnd = Date.parse( params().get('end_at') || params().get('endAt') || '' ); if (Number.isFinite(queryEnd) && queryEnd > Date.now()) return queryEnd; var cached = Number(window.localStorage.getItem(STORAGE_KEY)); if (Number.isFinite(cached) && cached > Date.now()) return cached; var fallback = Date.now() + 4 * 86400 * 1000 + 4 * 3600 * 1000 + 4 * 60 * 1000 + 4000; window.localStorage.setItem(STORAGE_KEY, String(fallback)); return fallback; } function updateCountdown() { var diff = Math.max(0, countdownEndAt - serverNow()); var totalSeconds = Math.floor(diff / 1000); countNodes.days.textContent = twoDigits( Math.floor(totalSeconds / 86400) ); countNodes.hours.textContent = twoDigits( Math.floor((totalSeconds % 86400) / 3600) ); countNodes.minutes.textContent = twoDigits( Math.floor((totalSeconds % 3600) / 60) ); countNodes.seconds.textContent = twoDigits(totalSeconds % 60); } function firstValue(source, keys, fallback) { if (!source) return fallback; 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 normalizeStatus(raw) { var data = raw || {}; var qualification = data.qualification || data.qualifications || data.agency || {}; var record = data.record || data.opening_record || data.openingRecord || {}; var application = data.application || data.opening_application || data.openingApplication || {}; var joined = Boolean( firstValue(data, ['joined', 'has_joined', 'hasJoined'], false) ); var eligible = firstValue( data, ['eligible', 'can_apply', 'canApply'], true ); var ineligibleReason = String( firstValue( data, ['ineligible_reason', 'ineligibleReason'], '' ) || '' ); var hostCount = Number( firstValue(qualification, ['host_count', 'hostCount'], 12) ) || 0; var hostRequirement = Number( firstValue( qualification, ['host_requirement', 'hostRequirement'], 10 ) ) || 10; return { serverTimeMS: Number( firstValue( data, ['server_time_ms', 'serverTimeMs'], Date.now() ) ) || Date.now(), endAt: Number( firstValue( data.period || data.activity_period || {}, ['end_ms', 'endMs'], firstValue(data, ['period_end_ms', 'periodEndMs'], 0) ) ) || fallbackEndAt(), qualification: { identity: firstValue( qualification, ['identity', 'agency_identity', 'agencyIdentity'], 'Agency' ), hostCount: hostCount, hostRequirement: hostRequirement, openingStatus: firstValue( qualification, [ 'opening_status', 'openingStatus', 'status_text', 'statusText', ], joined ? t('agencyOpening.joined', 'Joined') : t('agencyOpening.notJoined', 'Not Joined') ), ineligibleReason: ineligibleReason, eligible: Boolean(eligible) && !joined, }, rewards: Array.isArray( data.rewards || data.reward_tiers || data.rewardTiers ) ? data.rewards || data.reward_tiers || data.rewardTiers : fallbackRewards, rules: Array.isArray(data.rules) ? data.rules : null, record: { guildName: firstValue( record, ['guild_name', 'guildName', 'agency_name', 'agencyName'], 'Royal Family' ), created: firstValue( record, ['created_at', 'createdAt', 'created'], '2026-06-01' ), revenue: firstValue( record, ['opening_revenue', 'openingRevenue', 'revenue'], '100K' ), reward: firstValue( record, ['reward', 'reward_coins', 'rewardCoins'], '500K' ), scoreEndMS: Number( firstValue( record, ['score_end_ms', 'scoreEndMs'], firstValue( application, ['score_end_ms', 'scoreEndMs'], 0 ) ) ) || 0, }, application: application, joined: joined, }; } function mockStatus() { var query = params(); return normalizeStatus({ period: { end_ms: fallbackEndAt() }, qualification: { agency_identity: query.get('identity') || 'Agency', host_count: query.get('hosts') || 12, host_requirement: query.get('host_requirement') || 10, opening_status: query.get('status') || 'Not Joined', }, record: { guild_name: query.get('guild') || 'Royal Family', created_at: query.get('created') || '2026-06-01', opening_revenue: query.get('revenue') || '100K', reward: query.get('reward') || '500K', }, joined: query.get('joined') === '1', eligible: query.get('eligible') !== '0', rewards: fallbackRewards, server_time_ms: Date.now(), }); } function fetchStatus() { if (params().get('mock') === '1' || !window.HyAppAPI) { statusLoadedFromAPI = false; return Promise.resolve(mockStatus()); } return window.HyAppAPI.get('/api/v1/activities/agency-opening-event') .then(function (data) { statusLoadedFromAPI = true; return normalizeStatus(data); }) .catch(function (error) { // 新活动页先独立上线,接口未部署或活动未配置时继续渲染 Figma 默认态,避免线上 WebView 出现空白页。 console.warn('agency opening event status fallback', error); statusLoadedFromAPI = false; return mockStatus(); }); } function renderRewards(rewards) { rewardRows.innerHTML = ( rewards && rewards.length ? rewards : fallbackRewards ) .map(function (item) { var revenue = firstValue( item, [ 'opening_revenue', 'openingRevenue', 'threshold_coin_spent', 'thresholdCoinSpent', 'revenue', ], '500K' ); var reward = firstValue( item, ['reward_coins', 'rewardCoins', 'reward'], '50K' ); return [ '
', '', escapeHTML(formatCoinLabel(revenue)), '', '', escapeHTML(reward), '', '
', ].join(''); }) .join(''); } function renderRules(rules) { if (!rules || !rules.length) return; rulesList.innerHTML = rules .map(function (rule) { return '
  • ' + escapeHTML(rule) + '
  • '; }) .join(''); } function applyStatus(data) { state = data || mockStatus(); timeOffsetMS = Date.now() - state.serverTimeMS; countdownEndAt = state.endAt; var identityQualified = String(state.qualification.identity || '') .toLowerCase() .indexOf('agency') >= 0 && state.qualification.ineligibleReason !== 'active_agency_owner_required'; var hostQualified = Number(state.qualification.hostCount || 0) > Number(state.qualification.hostRequirement || 0); var statusQualified = !state.joined && String(state.qualification.openingStatus || '').toLowerCase() !== 'disabled'; document.getElementById('agencyIdentityText').textContent = state.qualification.identity; document.getElementById('hostCountText').textContent = state.qualification.hostCount + ' > ' + state.qualification.hostRequirement; document.getElementById('openingStatusText').textContent = state.qualification.openingStatus; setQualified('agencyIdentityCard', identityQualified); setQualified('hostCountCard', hostQualified); setQualified('openingStatusCard', statusQualified); document.getElementById('eligibilityText').textContent = state .qualification.eligible ? t('agencyOpening.eligible', 'Eligible To Apply') : t('agencyOpening.notEligible', 'Not Eligible'); document.getElementById('recordGuildName').textContent = state.record.guildName; document.getElementById('recordCreated').textContent = state.record.created; document.getElementById('recordRevenue').textContent = state.record.revenue; document.getElementById('recordReward').textContent = state.record.reward; applyButton.classList.toggle('is-joined', state.joined); applyButton.querySelector('span').textContent = state.joined ? t('agencyOpening.applied', 'Applied') : t('agencyOpening.apply', 'Apply Opening Event'); renderRewards(state.rewards); renderRules(state.rules); updateCountdown(); if (countdownTimer) window.clearInterval(countdownTimer); countdownTimer = window.setInterval(updateCountdown, 1000); } function setQualified(id, qualified) { var node = document.getElementById(id); if (!node) return; node.classList.toggle('is-qualified', Boolean(qualified)); } function showToast(message) { if (window.HyAppToast && window.HyAppToast.show) { window.HyAppToast.show(message); return; } window.dispatchEvent( new CustomEvent('hyapp:toast', { detail: { message: message } }) ); } function commandID() { var random = Math.random().toString(36).slice(2); if (window.crypto && window.crypto.getRandomValues) { var bytes = new Uint32Array(2); window.crypto.getRandomValues(bytes); random = bytes[0].toString(36) + bytes[1].toString(36); } return 'agency_opening_' + Date.now().toString(36) + '_' + random; } function submitApply() { if ( !state || state.joined || applyButton.classList.contains('is-loading') ) return; if (!state.qualification.eligible) { showToast(t('agencyOpening.notEligible', 'Not Eligible')); return; } applyButton.classList.add('is-loading'); // 申请动作按 command_id 做点击级幂等;如果后端接口还没上线,页面只更新本地展示态,不伪造服务端成功事实。 var request = statusLoadedFromAPI ? window.HyAppAPI.post( '/api/v1/activities/agency-opening-event/apply', { command_id: commandID(), } ) : Promise.resolve({ joined: true, qualification: { opening_status: t( 'agencyOpening.pendingReview', 'Pending Review' ), }, }); request .then(function (data) { var next = Object.assign({}, state); var payload = data || {}; var payloadQualification = payload.qualification || payload.qualifications || {}; next.joined = true; next.qualification = Object.assign({}, state.qualification, { openingStatus: firstValue( payloadQualification, [ 'opening_status', 'openingStatus', 'status_text', 'statusText', ], t('agencyOpening.pendingReview', 'Pending Review') ), }); next.qualification.eligible = false; applyStatus(next); showToast( t('agencyOpening.applySuccess', 'Application submitted.') ); }) .catch(function (error) { console.warn('agency opening event apply failed', error); showToast( t( 'agencyOpening.applyFailed', 'Application failed. Try again later.' ) ); }) .finally(function () { applyButton.classList.remove('is-loading'); }); } function escapeHTML(value) { return String(value || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function formatCoinLabel(value) { var amount = Number(value); if (!Number.isFinite(amount) || amount <= 0) return value; if (amount % 1000000 === 0) return amount / 1000000 + 'M'; if (amount % 1000 === 0) return amount / 1000 + 'K'; return String(amount); } function init() { document.title = t( 'agencyOpening.pageTitle', 'New Agency Opening Event' ); updateViewportHeight(); fetchStatus().then(applyStatus); applyButton.addEventListener('click', submitApply); window.addEventListener('resize', updateViewportHeight); window.addEventListener('hyapp:i18n-ready', function () { document.title = t( 'agencyOpening.pageTitle', 'New Agency Opening Event' ); if (state) applyStatus(state); }); } init(); })();