(function () { 'use strict'; var ASSET_BASE = './huwaa/assets/layers/'; var RANK_LIMIT = 5; var DAY_MS = 24 * 60 * 60 * 1000; var DESIGN_HEIGHT = { history: 2309, weekly: 2309, rewards: 3200 }; var DESIGN_FRAME = { history: '818:1999', weekly: '818:2066', rewards: '818:2132', }; var params = currentParams(); var useMock = params.get('mock') === '1'; var page = document.getElementById('weekStarPage'); var stage = document.getElementById('weekStarStage'); var stageWrap = document.getElementById('weekStarStageWrap'); var tabs = all('[data-tab-target]'); var panels = all('[data-panel]'); var state = { // Once the user switches sections, the hash is the canonical tab state. It // must win over a stale entry `?tab=` value while the hash-query token stays intact. tab: normalizeTab(tabFromHash() || params.get('tab')), current: null, weeklyEntries: [], historyCycles: [], loadingCurrent: true, loadingWeekly: true, loadingHistory: true, currentError: '', weeklyError: '', historyError: '', requestSerial: 0, }; function all(selector, root) { return Array.prototype.slice.call( (root || document).querySelectorAll(selector) ); } function currentParams() { if ( window.HyAppParams && window.HyAppParams.current && window.HyAppParams.current.raw ) { return window.HyAppParams.current.raw; } var result = new URLSearchParams(window.location.search || ''); var hash = window.location.hash || ''; if (hash.indexOf('?') >= 0) { new URLSearchParams(hash.split('?').slice(1).join('?')).forEach( function (value, key) { if (!result.has(key)) result.set(key, value); } ); } return result; } function text(value) { return String( value === undefined || value === null ? '' : value ).trim(); } function number(value, fallback) { var result = Number(value); return Number.isFinite(result) ? result : Number(fallback || 0); } function first(source, keys) { source = source || {}; for (var index = 0; index < keys.length; index += 1) { var value = source[keys[index]]; if (value !== undefined && value !== null && value !== '') { return value; } } return ''; } function envelope(payload) { if (payload && Object.prototype.hasOwnProperty.call(payload, 'data')) { return payload.data || {}; } return payload || {}; } function parseJSON(value) { if (!value || typeof value !== 'string') return {}; try { return JSON.parse(value); } catch (error) { return {}; } } function t(key, fallback, values) { var translated = window.HyAppI18n && window.HyAppI18n.t ? window.HyAppI18n.t(key, fallback) : fallback || key; Object.keys(values || {}).forEach(function (name) { translated = translated.replace( new RegExp('\\{' + name + '\\}', 'g'), String(values[name]) ); }); return translated; } function escapeHTML(value) { return String(value === undefined || value === null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function escapeAttr(value) { return escapeHTML(value); } function resolveAssetURL(value) { var raw = text(value); if (!raw) return ''; if ( /^(https?:)?\/\//i.test(raw) || /^(data|blob):/i.test(raw) || raw.charAt(0) === '/' || raw.indexOf('./') === 0 || raw.indexOf('../') === 0 ) { return raw; } // Backend resource records may return a path relative to the API host. Resolving // against the API base avoids silently requesting that path from this H5 directory. try { var base = window.HyAppAPI && window.HyAppAPI.baseURL ? window.HyAppAPI.baseURL() : window.location.origin + '/'; return new URL(raw, base).toString(); } catch (error) { return ''; } } function formatNumber(value) { var lang = window.HyAppI18n && window.HyAppI18n.lang ? window.HyAppI18n.lang() : 'en'; return new Intl.NumberFormat(lang).format(number(value)); } function formatScore(value) { var raw = text(value); if (!raw) return '0'; var numeric = Number(raw); return Number.isFinite(numeric) ? String(numeric) : raw; } function pad(value) { return String(value).padStart(2, '0'); } function timezoneOffsetHours(cycle) { var explicit = first(cycle, [ 'utc_offset_hours', 'utcOffsetHours', 'timezone_offset_hours', 'timezoneOffsetHours', ]); if (explicit !== '') return number(explicit, 3); var label = text(first(cycle, ['timezone', 'time_zone', 'timeZone'])); var match = label.match(/(?:utc|gmt)\s*([+-]\d+(?:\.\d+)?)/i); // The Figma campaign explicitly uses UTC+3. The API can override this through // its cycle metadata; the design offset is only the fallback display contract. return match ? number(match[1], 3) : 3; } function shiftedDate(value, offsetHours) { var timestamp = number(value); if (!timestamp) return null; return new Date(timestamp + offsetHours * 60 * 60 * 1000); } function formatDateTime(value, offsetHours) { var date = shiftedDate(value, offsetHours); if (!date) return '—'; return [ date.getUTCFullYear(), '-', pad(date.getUTCMonth() + 1), '-', pad(date.getUTCDate()), ' ', pad(date.getUTCHours()), ':', pad(date.getUTCMinutes()), ].join(''); } function formatDate(value, offsetHours) { var date = shiftedDate(value, offsetHours); if (!date) return '—'; return [ date.getUTCFullYear(), '/', pad(date.getUTCMonth() + 1), '/', pad(date.getUTCDate()), ].join(''); } function formatOffset(offsetHours) { return (offsetHours >= 0 ? '+' : '') + String(offsetHours); } function cycleRange(cycle) { cycle = cycle || {}; var custom = text( first(cycle, [ 'display_time', 'displayTime', 'time_range', 'timeRange', ]) ); if (custom) return custom; var offset = timezoneOffsetHours(cycle); return ( formatDateTime(first(cycle, ['start_ms', 'startMs']), offset) + '-' + formatDateTime(first(cycle, ['end_ms', 'endMs']), offset) + '(utc' + formatOffset(offset) + ')' ); } function historyRange(cycle) { cycle = cycle || {}; var offset = timezoneOffsetHours(cycle); return ( formatDate(first(cycle, ['start_ms', 'startMs']), offset) + '-' + formatDate(first(cycle, ['end_ms', 'endMs']), offset) ); } function currentCycle() { var data = envelope(state.current); return data.cycle || data.current_cycle || data.currentCycle || null; } function errorKey(error) { var status = number( first(error, ['status', 'status_code', 'statusCode']) ); var code = text( first(error, ['code', 'error_code', 'errorCode']) ).toUpperCase(); var message = text(error && error.message).toLowerCase(); if ( status === 401 || code === 'UNAUTHORIZED' || message.indexOf('unauthor') >= 0 ) { return 'huwaa_weekly_star.login_required'; } if (code === 'PROFILE_REQUIRED') { return 'huwaa_weekly_star.profile_required'; } return 'huwaa_weekly_star.request_failed'; } function normalizeGift(item) { item = item || {}; var resource = item.resource || item.gift || {}; var metadata = parseJSON( first(item, ['metadata_json', 'metadataJson']) || first(resource, ['metadata_json', 'metadataJson']) ); return { id: text( first(item, [ 'gift_id', 'giftId', 'resource_id', 'resourceId', 'id', ]) || first(resource, ['id', 'resource_id', 'resourceId']) ), name: text( first(item, ['display_name', 'displayName', 'name']) || first(resource, ['display_name', 'displayName', 'name']) ), image: resolveAssetURL( first(metadata, [ 'preview_url', 'previewUrl', 'asset_url', 'assetUrl', ]) || first(item, [ 'image_url', 'imageUrl', 'cover_url', 'coverUrl', 'preview_url', 'previewUrl', 'icon_url', 'iconUrl', 'asset_url', 'assetUrl', ]) || first(resource, [ 'preview_url', 'previewUrl', 'cover_url', 'coverUrl', 'asset_url', 'assetUrl', ]) ), }; } function normalizeEntries(entries) { if (!Array.isArray(entries)) return []; return entries.slice(0, RANK_LIMIT).map(function (entry, index) { entry = entry || {}; var user = entry.user || entry.profile || {}; var rank = number( first(entry, ['rank_no', 'rankNo', 'rank']), index + 1 ); var name = text( first(user, [ 'username', 'nickname', 'display_name', 'displayName', 'display_user_id', 'displayUserId', ]) || first(entry, ['username', 'name', 'user_id', 'userId']) ); return { rank: rank || index + 1, userID: text( first(entry, ['user_id', 'userId']) || first(user, ['user_id', 'userId', 'id']) ), name: name || t('huwaa_weekly_star.unknown_user', 'User'), score: first(entry, ['score', 'points', 'value']) || 0, avatar: resolveAssetURL( first(user, [ 'avatar', 'avatar_url', 'avatarUrl', 'profile_image', 'profileImage', ]) || first(entry, ['avatar', 'avatar_url', 'avatarUrl']) ), }; }); } function rewardImage(item, resource, metadata) { return resolveAssetURL( first(metadata, [ 'preview_url', 'previewUrl', 'asset_url', 'assetUrl', ]) || first(item, [ 'image', 'image_url', 'imageUrl', 'cover_url', 'coverUrl', 'preview_url', 'previewUrl', 'icon_url', 'iconUrl', 'asset_url', 'assetUrl', ]) || first(resource, [ 'preview_url', 'previewUrl', 'cover_url', 'coverUrl', 'asset_url', 'assetUrl', 'animation_url', 'animationUrl', ]) ); } function normalizeRewardItem(item, group) { item = item || {}; group = group || {}; var resource = item.resource || {}; var metadata = parseJSON( first(item, ['metadata_json', 'metadataJson']) || first(resource, ['metadata_json', 'metadataJson']) ); var walletType = text( first(item, ['wallet_asset_type', 'walletAssetType']) || first(resource, ['wallet_asset_type', 'walletAssetType']) ).replace(/_/g, ' '); var walletAmount = number( first(item, [ 'wallet_asset_amount', 'walletAssetAmount', 'amount', ]) || first(resource, ['wallet_asset_amount', 'walletAssetAmount']) ); var quantity = number(first(item, ['quantity'])); var durationMS = number(first(item, ['duration_ms', 'durationMs'])); var durationDays = number( first(item, [ 'duration_days', 'durationDays', 'valid_days', 'validDays', ]) || first(resource, ['duration_days', 'durationDays']) ); if (!durationDays && durationMS > 0) { // Gateway resource-group items expose validity as duration_ms. Keep the // conversion aligned with the backend's whole-day reward contract. durationDays = Math.floor(durationMS / DAY_MS); } var labelKey = text(first(item, ['i18n_key', 'i18nKey'])); var label = labelKey ? t(labelKey, '') : text( first(item, [ 'display_name', 'displayName', 'label', 'name', ]) || first(resource, [ 'display_name', 'displayName', 'name', ]) || walletType || first(group, ['display_name', 'displayName', 'name']) ); if (durationDays > 0) { label = t('huwaa_weekly_star.reward_days', '{name} ({days} days)', { name: label || t('huwaa_weekly_star.reward', 'Reward'), days: durationDays, }); } if (quantity > 1) { label = (label || t('huwaa_weekly_star.reward', 'Reward')) + ' ×' + formatNumber(quantity); } else if (walletAmount > 0) { label = (label || walletType || t('huwaa_weekly_star.reward', 'Reward')) + ' ×' + formatNumber(walletAmount); } return { label: label || t('huwaa_weekly_star.reward', 'Reward'), image: rewardImage(item, resource, metadata), }; } function normalizeRewardGroups(cycle) { cycle = cycle || {}; var source = cycle.rewards || cycle.reward_groups || cycle.rewardGroups || cycle.rank_rewards || cycle.rankRewards || []; if (!Array.isArray(source)) return []; return source .map(function (group, index) { group = group || {}; var nested = group.resource_group || group.resourceGroup || group.reward_resource_group || group.rewardResourceGroup || {}; var items = group.items || group.resources || nested.items || nested.resources || []; // Some endpoints flatten a single visual resource onto the rank reward. // It is only promoted when an actual image/name exists; a bare group ID // must remain an empty state rather than a fabricated usable reward. if (!Array.isArray(items)) items = []; if ( !items.length && (rewardImage(group, group.resource || {}, {}) || first(group, ['display_name', 'displayName', 'name'])) ) { items = [group]; } return { rank: number( first(group, ['rank_no', 'rankNo', 'rank']), index + 1 ), items: items.slice(0, 3).map(function (item) { return normalizeRewardItem(item, group); }), }; }) .filter(function (group) { return group.rank >= 1 && group.rank <= 3; }) .sort(function (left, right) { return left.rank - right.rank; }); } function renderGifts(cycle) { var source = cycle && Array.isArray(cycle.gifts) ? cycle.gifts : []; var gifts = source.slice(0, 3).map(normalizeGift); var lefts = [61, 411, 761]; var root = document.getElementById('weekStarGifts'); root.innerHTML = lefts .map(function (left, index) { var gift = gifts[index] || {}; var label = gift.name || t('huwaa_weekly_star.gift_label', 'Activity gift {index}', { index: index + 1, }); return [ '
', '', '', gift.image ? '' : '', '', '
', ].join(''); }) .join(''); bindImageFallbacks(root); } function medalForRank(rank) { if (rank === 1) return ASSET_BASE + 'rank-1.webp'; if (rank === 2) return ASSET_BASE + 'rank-2.webp'; if (rank === 3) return ASSET_BASE + 'rank-3.webp'; return ''; } function rankInitial(name) { var characters = Array.from(text(name)); return characters.length ? characters[0] : '?'; } function stateHTML(top, message, retry) { return [ '
', '', escapeHTML(message), '', retry ? '' : '', '
', ].join(''); } function isRetryable(error) { return error === 'huwaa_weekly_star.request_failed'; } function renderRankList(options) { var root = document.getElementById(options.rootID); var entries = normalizeEntries(options.entries); if (!entries.length) { var message = options.loading ? t('huwaa_weekly_star.loading', 'Loading…') : options.error ? t(options.error, 'Unable to load the ranking.') : t(options.emptyKey, options.emptyFallback); root.innerHTML = stateHTML( options.top, message, !options.loading && isRetryable(options.error) ); return; } root.innerHTML = entries .map(function (entry, index) { var medal = medalForRank(entry.rank); var tag = entry.userID ? 'button' : 'article'; var className = index % 2 === (options.firstDark ? 0 : 1) ? 'is-dark' : 'is-purple'; var aria = t( 'huwaa_weekly_star.rank_label', 'Rank {rank}, {name}, {score}', { rank: entry.rank, name: entry.name, score: formatScore(entry.score), } ); return [ '<', tag, tag === 'button' ? ' type="button"' : '', ' class="week-star-rank-row ', className, '" style="top:', options.top + index * 140, 'px"', entry.userID ? ' data-user-id="' + escapeAttr(entry.userID) + '"' : '', ' aria-label="', escapeAttr(aria), '">', medal ? '' : '', '', entry.avatar ? '' : '', '', '', '', escapeHTML(entry.name), '', '', escapeHTML(formatScore(entry.score)), '', '', ].join(''); }) .join(''); bindImageFallbacks(root); } function historyCycleData() { var record = state.historyCycles[0] || {}; return { cycle: record.cycle || record, entries: record.top_entries || record.topEntries || record.entries || record.leaderboard || [], }; } function renderHistory() { var history = historyCycleData(); document.getElementById('weekStarHistoryDate').textContent = history.cycle && first(history.cycle, ['start_ms', 'startMs']) && first(history.cycle, ['end_ms', 'endMs']) ? historyRange(history.cycle) : t( 'huwaa_weekly_star.previous_top_three', 'Previous Top 3 Records' ); renderRankList({ rootID: 'weekStarHistoryList', top: 1607, entries: history.entries, loading: state.loadingHistory, error: state.historyError, emptyKey: 'huwaa_weekly_star.no_history', emptyFallback: 'No previous ranking records.', firstDark: false, }); } function renderWeekly() { renderRankList({ rootID: 'weekStarWeeklyList', top: 1496, entries: state.weeklyEntries, loading: state.loadingWeekly, error: state.weeklyError, emptyKey: 'huwaa_weekly_star.no_ranking', emptyFallback: 'No ranking data yet.', firstDark: true, }); } function renderRewardItem(item) { return [ '
', '', '', item.image ? '' : '', '', '', escapeHTML(item.label), '', '
', ].join(''); } function renderRewards(cycle) { var root = document.getElementById('weekStarRewardGroups'); if (state.loadingCurrent) { root.innerHTML = stateHTML( 1705, t('huwaa_weekly_star.loading', 'Loading…'), false ); return; } if (state.currentError) { root.innerHTML = stateHTML( 1705, t(state.currentError, 'Unable to load rewards.'), isRetryable(state.currentError) ); return; } var groups = normalizeRewardGroups(cycle); if (!groups.length) { root.innerHTML = stateHTML( 1705, t('huwaa_weekly_star.no_rewards', 'No reward data yet.'), false ); return; } var positions = { 1: { heading: 1505, items: 1705 }, 2: { heading: 2068, items: 2203 }, 3: { heading: 2566, items: 2701 }, }; root.innerHTML = groups .map(function (group) { var position = positions[group.rank]; var itemsHTML = group.items.length ? group.items.map(renderRewardItem).join('') : '
' + escapeHTML( t( 'huwaa_weekly_star.no_rank_reward', 'No reward data.' ) ) + '
'; return [ '
', '

', '', 'TOP', group.rank, '', '

', '
', itemsHTML, '
', '
', ].join(''); }) .join(''); bindImageFallbacks(root); } function renderHeader() { var cycle = currentCycle(); var date = document.getElementById('weekStarDate'); if (state.loadingCurrent) { date.textContent = '—'; } else if (state.currentError) { date.textContent = '—'; } else if (cycle) { date.textContent = cycleRange(cycle); } else { date.textContent = t( 'huwaa_weekly_star.no_active_cycle', 'No active cycle' ); } renderGifts(cycle || {}); renderRewards(cycle || {}); } function bindImageFallbacks(root) { all('[data-image-fallback]', root).forEach(function (image) { if (image.dataset.fallbackBound === 'true') return; image.dataset.fallbackBound = 'true'; function fallback() { var type = image.getAttribute('data-image-fallback'); var parent = image.parentElement; if (type === 'avatar' && parent) { parent.classList.add('is-fallback'); } else if (parent) { parent.classList.remove('has-image'); } image.hidden = true; } image.addEventListener('error', fallback, { once: true }); if (image.complete && image.naturalWidth === 0) fallback(); }); } function renderAll() { renderHeader(); renderHistory(); renderWeekly(); setTab(state.tab, false, false); } function tabFromHash() { var hash = text((window.location.hash || '').replace(/^#/, '')); if (!hash) return ''; var route = hash.split('?')[0].replace(/^\/+|\/+$/g, ''); var pieces = route.split('/'); return pieces[pieces.length - 1]; } function normalizeTab(value) { return value === 'weekly' || value === 'rewards' || value === 'history' ? value : 'history'; } function replaceTabHash(tab) { if (!window.history || !window.history.replaceState) return; var hash = window.location.hash || ''; var hashQuery = hash.indexOf('?') >= 0 ? '?' + hash.split('?').slice(1).join('?') : ''; // Native containers sometimes inject the token after `#?...`; preserving that // suffix is mandatory because replacing the entire hash would log the user out. var next = window.location.pathname + window.location.search + '#' + tab + hashQuery; window.history.replaceState(null, '', next); } function updateStageHeight() { var viewportWidth = Math.min(window.innerWidth, 430); var height = DESIGN_HEIGHT[state.tab] || DESIGN_HEIGHT.history; var scaledHeight = Math.ceil((height * viewportWidth) / 1080); document.documentElement.style.setProperty( '--huwaa-week-star-scale', String(viewportWidth / 1080) ); stage.style.height = height + 'px'; stageWrap.style.height = scaledHeight + 'px'; page.style.minHeight = scaledHeight + 'px'; } function setTab(tab, updateURL, announce) { state.tab = normalizeTab(tab); page.setAttribute('data-tab', state.tab); stage.setAttribute('data-node-id', DESIGN_FRAME[state.tab]); tabs.forEach(function (button) { var active = button.getAttribute('data-tab-target') === state.tab; button.classList.toggle('is-active', active); button.setAttribute('aria-selected', active ? 'true' : 'false'); button.tabIndex = active ? 0 : -1; }); panels.forEach(function (panel) { panel.hidden = panel.getAttribute('data-panel') !== state.tab; }); updateStageHeight(); if (updateURL) replaceTabHash(state.tab); if (announce) { var selected = tabs.find(function (button) { return button.getAttribute('data-tab-target') === state.tab; }); document.getElementById('weekStarAnnouncer').textContent = selected ? selected.textContent.trim() : ''; } } function mockState() { var sample = ASSET_BASE + 'sample-gift.webp'; var mockEntries = [1, 2, 3, 4, 5].map(function (rank) { return { rank_no: rank, score: 337700, user: { id: 'mock-user-' + rank, username: rank === 1 ? 'hanneem' : 'star' + rank, avatar: '', }, }; }); var rewardGroups = [1, 2, 3].map(function (rank) { return { rank_no: rank, items: [1, 2, 3].map(function () { return { i18n_key: 'huwaa_weekly_star.frame', duration_days: 7, image_url: sample, }; }), }; }); return { current: { cycle: { cycle_id: 'figma-mock', start_ms: Date.UTC(2025, 11, 27, 21, 0, 0), end_ms: Date.UTC(2026, 0, 3, 20, 59, 0), utc_offset_hours: 3, gifts: [ { image_url: sample }, { image_url: sample }, { image_url: sample }, ], rewards: rewardGroups, }, }, entries: mockEntries, history: [ { cycle: { start_ms: Date.UTC(2025, 11, 6, 21, 0, 0), end_ms: Date.UTC(2025, 11, 14, 20, 59, 0), utc_offset_hours: 3, }, top_entries: mockEntries, }, ], }; } function loadMock() { var mock = mockState(); state.current = mock.current; state.weeklyEntries = mock.entries; state.historyCycles = mock.history; state.loadingCurrent = false; state.loadingWeekly = false; state.loadingHistory = false; state.currentError = ''; state.weeklyError = ''; state.historyError = ''; renderAll(); bridgeReady(); } function tokenAvailable() { return !!( window.HyAppAPI && window.HyAppAPI.getAccessToken && window.HyAppAPI.getAccessToken() ); } function loadReal() { var serial = ++state.requestSerial; state.loadingCurrent = true; state.loadingWeekly = true; state.loadingHistory = true; state.currentError = ''; state.weeklyError = ''; state.historyError = ''; renderAll(); if (!window.HyAppAPI || !window.HyAppAPI.weeklyStar) { state.loadingCurrent = false; state.loadingWeekly = false; state.loadingHistory = false; state.currentError = 'huwaa_weekly_star.request_failed'; state.weeklyError = state.currentError; state.historyError = state.currentError; renderAll(); bridgeReady(); return; } if (!tokenAvailable()) { // Production weekly-star endpoints require the authenticated user context. // Do not send three doomed requests or silently replace the response with mock data. state.loadingCurrent = false; state.loadingWeekly = false; state.loadingHistory = false; state.currentError = 'huwaa_weekly_star.login_required'; state.weeklyError = state.currentError; state.historyError = state.currentError; renderAll(); bridgeReady(); return; } // Current/leaderboard and history are intentionally isolated. A history outage // must not erase a successfully loaded current leaderboard, and vice versa. var currentFlow = window.HyAppAPI.weeklyStar .current() .then(function (payload) { if (serial !== state.requestSerial) return; var data = envelope(payload); state.current = data; state.weeklyEntries = data.top_entries || data.topEntries || []; state.loadingCurrent = false; state.loadingWeekly = false; state.currentError = ''; state.weeklyError = ''; renderHeader(); renderWeekly(); }) .catch(function (error) { if (serial !== state.requestSerial) return; state.current = null; state.weeklyEntries = []; state.loadingCurrent = false; state.loadingWeekly = false; state.currentError = errorKey(error); state.weeklyError = state.currentError; renderHeader(); renderWeekly(); }); var historyFlow = window.HyAppAPI.weeklyStar .history(1) .then(function (payload) { if (serial !== state.requestSerial) return; var data = envelope(payload); state.historyCycles = Array.isArray(data) ? data : data.cycles || data.items || data.list || []; state.historyError = ''; }) .catch(function (error) { if (serial !== state.requestSerial) return; state.historyCycles = []; state.historyError = errorKey(error); }) .then(function () { if (serial !== state.requestSerial) return; state.loadingHistory = false; renderHistory(); }); Promise.all([currentFlow, historyFlow]).then(function () { if (serial === state.requestSerial) bridgeReady(); }); } function bridgeReady() { if (!window.HyAppBridge || !window.HyAppBridge.ready) return; window.HyAppBridge.ready({ page: 'huwaa_weekly_star', app_code: 'huwaa', mock: useMock, }); } function activateRelativeTab(currentIndex, direction) { var nextIndex = (currentIndex + direction + tabs.length) % tabs.length; var next = tabs[nextIndex]; setTab(next.getAttribute('data-tab-target'), true, true); next.focus(); } function bindEvents() { tabs.forEach(function (button, index) { button.addEventListener('click', function () { setTab(button.getAttribute('data-tab-target'), true, true); }); button.addEventListener('keydown', function (event) { if (event.key === 'ArrowRight' || event.key === 'ArrowDown') { event.preventDefault(); activateRelativeTab(index, 1); } else if ( event.key === 'ArrowLeft' || event.key === 'ArrowUp' ) { event.preventDefault(); activateRelativeTab(index, -1); } else if (event.key === 'Home') { event.preventDefault(); setTab('history', true, true); tabs[0].focus(); } else if (event.key === 'End') { event.preventDefault(); setTab('rewards', true, true); tabs[tabs.length - 1].focus(); } }); }); document.addEventListener('click', function (event) { var retry = event.target.closest('[data-retry]'); if (retry) { loadReal(); return; } var row = event.target.closest('[data-user-id]'); if (row && window.HyAppBridge && window.HyAppBridge.openUser) { window.HyAppBridge.openUser(row.getAttribute('data-user-id')); } }); window.addEventListener('resize', updateStageHeight); window.addEventListener('hashchange', function () { setTab(normalizeTab(tabFromHash()), false, false); }); window.addEventListener('hyapp:i18n-ready', function () { // Dynamic API rows are not covered by common/i18n.js's static DOM pass. // Re-rendering keeps empty/error/reward labels synchronized after locale load. renderAll(); }); } function init() { bindEvents(); setTab(state.tab, false, false); if (useMock) loadMock(); else loadReal(); } init(); })();