diff --git a/aslan/recharge-activity/assets/recharge-activity/bg-bottom.png b/aslan/recharge-activity/assets/recharge-activity/bg-bottom.png new file mode 100644 index 0000000..0f1c145 Binary files /dev/null and b/aslan/recharge-activity/assets/recharge-activity/bg-bottom.png differ diff --git a/aslan/recharge-activity/assets/recharge-activity/bg-mid-a.png b/aslan/recharge-activity/assets/recharge-activity/bg-mid-a.png new file mode 100644 index 0000000..89942b3 Binary files /dev/null and b/aslan/recharge-activity/assets/recharge-activity/bg-mid-a.png differ diff --git a/aslan/recharge-activity/assets/recharge-activity/bg-top.png b/aslan/recharge-activity/assets/recharge-activity/bg-top.png new file mode 100644 index 0000000..7c0de14 Binary files /dev/null and b/aslan/recharge-activity/assets/recharge-activity/bg-top.png differ diff --git a/aslan/recharge-activity/assets/recharge-activity/countdown-frame.png b/aslan/recharge-activity/assets/recharge-activity/countdown-frame.png new file mode 100644 index 0000000..5e10952 Binary files /dev/null and b/aslan/recharge-activity/assets/recharge-activity/countdown-frame.png differ diff --git a/aslan/recharge-activity/assets/recharge-activity/profile-avatar.png b/aslan/recharge-activity/assets/recharge-activity/profile-avatar.png new file mode 100644 index 0000000..6d86504 Binary files /dev/null and b/aslan/recharge-activity/assets/recharge-activity/profile-avatar.png differ diff --git a/aslan/recharge-activity/assets/recharge-activity/profile-frame.png b/aslan/recharge-activity/assets/recharge-activity/profile-frame.png new file mode 100644 index 0000000..b213d52 Binary files /dev/null and b/aslan/recharge-activity/assets/recharge-activity/profile-frame.png differ diff --git a/aslan/recharge-activity/assets/recharge-activity/receive-button-bg.png b/aslan/recharge-activity/assets/recharge-activity/receive-button-bg.png new file mode 100644 index 0000000..910bd26 Binary files /dev/null and b/aslan/recharge-activity/assets/recharge-activity/receive-button-bg.png differ diff --git a/aslan/recharge-activity/assets/recharge-activity/reward-card-frame.png b/aslan/recharge-activity/assets/recharge-activity/reward-card-frame.png new file mode 100644 index 0000000..c9880e1 Binary files /dev/null and b/aslan/recharge-activity/assets/recharge-activity/reward-card-frame.png differ diff --git a/aslan/recharge-activity/assets/recharge-activity/reward-icon.png b/aslan/recharge-activity/assets/recharge-activity/reward-icon.png new file mode 100644 index 0000000..cde45a5 Binary files /dev/null and b/aslan/recharge-activity/assets/recharge-activity/reward-icon.png differ diff --git a/aslan/recharge-activity/assets/recharge-activity/reward-section-a.png b/aslan/recharge-activity/assets/recharge-activity/reward-section-a.png new file mode 100644 index 0000000..c940598 Binary files /dev/null and b/aslan/recharge-activity/assets/recharge-activity/reward-section-a.png differ diff --git a/aslan/recharge-activity/assets/recharge-activity/reward-section-b.png b/aslan/recharge-activity/assets/recharge-activity/reward-section-b.png new file mode 100644 index 0000000..c940598 Binary files /dev/null and b/aslan/recharge-activity/assets/recharge-activity/reward-section-b.png differ diff --git a/aslan/recharge-activity/index.html b/aslan/recharge-activity/index.html new file mode 100644 index 0000000..8748808 --- /dev/null +++ b/aslan/recharge-activity/index.html @@ -0,0 +1,98 @@ + + + + + + Recharge Reward + + + + +
+
+
+ + + + + + + + +
+ + 01 + 01 + 01 + 01 + Days + Hours + Minutes + Seconds +
+ +
+ + +

nanfangjian

+

ID: 12345678

+

+ Accumlated Recharge:5998 +

+
+ +
+
+
+
+ + + + + diff --git a/aslan/recharge-activity/script.js b/aslan/recharge-activity/script.js new file mode 100644 index 0000000..eca233b --- /dev/null +++ b/aslan/recharge-activity/script.js @@ -0,0 +1,204 @@ +(function () { + var ASSET_BASE = './assets/recharge-activity/'; + var viewport = document.getElementById('appViewport'); + var rewardSections = document.getElementById('rewardSections'); + 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 DESIGN_HEIGHT = 3273; + var VIEWPORT_WIDTH = 375; + var countdownEndAt = resolveCountdownEndAt(); + var activityData = { + user: { + name: 'nanfangjian', + id: '12345678', + avatar: ASSET_BASE + 'profile-avatar.png', + accumulatedRecharge: '5998', + }, + rewards: [ + { + amount: '$100', + top: 1776, + background: ASSET_BASE + 'reward-section-a.png', + claim: { text: 'Receive', claimable: true, claimed: false }, + cards: [ + { price: '$100', icon: ASSET_BASE + 'reward-icon.png', left: 111, top: 293 }, + { price: '$100', icon: ASSET_BASE + 'reward-icon.png', left: 560, top: 293 }, + ], + }, + { + amount: '$100', + top: 2466, + background: ASSET_BASE + 'reward-section-b.png', + claim: { text: 'Receive', claimable: true, claimed: false }, + cards: [ + { price: '$100', icon: ASSET_BASE + 'reward-icon.png', left: 70.5, top: 278 }, + { price: '$100', icon: ASSET_BASE + 'reward-icon.png', left: 339.5, top: 278 }, + { price: '$100', icon: ASSET_BASE + 'reward-icon.png', left: 608.5, top: 278 }, + ], + }, + ], + }; + + 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 resolveCountdownEndAt() { + 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; + + // This page is intentionally interface-free for now; when an API is added, + // use the server period end time here and do not recover failed requests with mock data. + return Date.now() + 25 * 60 * 60 * 1000 + 61 * 1000; + } + + function updateScale() { + var scale = Math.min(window.innerWidth / VIEWPORT_WIDTH, 1); + var scaledHeight = Math.ceil(DESIGN_HEIGHT * (VIEWPORT_WIDTH / DESIGN_WIDTH)); + + document.documentElement.style.setProperty('--app-scale', String(scale)); + viewport.style.minHeight = scaledHeight + 'px'; + document.body.style.minHeight = Math.ceil(scaledHeight * scale) + '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 renderProfile(user) { + document.querySelector('[data-profile-avatar]').src = user.avatar; + document.querySelector('[data-profile-name]').textContent = user.name; + document.querySelector('[data-profile-id]').textContent = + t('rechargeReward.idPrefix', 'ID:') + ' ' + user.id; + document.querySelector('[data-profile-recharge]').textContent = + t('rechargeReward.accumulatedRecharge', 'Accumlated Recharge:') + + user.accumulatedRecharge; + } + + function renderRewardCard(card) { + return [ + '
', + '', + '', + '', + escapeHTML(card.price), + '', + '
', + ].join(''); + } + + function renderClaimButton(claim) { + var stateClass = claim.claimed ? ' is-claimed' : claim.claimable ? ' is-claimable' : ' is-disabled'; + return [ + '', + ].join(''); + } + + function renderRewards(rewards) { + rewardSections.innerHTML = rewards + .map(function (section) { + var title = t('rechargeReward.rechargeTitle', 'Recharge {amount}').replace( + '{amount}', + section.amount + ); + return [ + '
', + '', + '
', + '', + escapeHTML(title), + '', + '
', + section.cards + .map(function (card) { + return renderRewardCard(card); + }) + .join(''), + renderClaimButton(section.claim || { text: 'Receive', claimable: false }), + '
', + ].join(''); + }) + .join(''); + } + + function renderPage() { + document.title = t('rechargeReward.pageTitle', 'Recharge Reward'); + renderProfile(activityData.user); + renderRewards(activityData.rewards); + } + + function init() { + updateScale(); + updateCountdown(); + renderPage(); + + window.setInterval(updateCountdown, 1000); + window.addEventListener('resize', updateScale); + window.addEventListener('hyapp:i18n-ready', renderPage); + } + + init(); +})(); diff --git a/aslan/recharge-activity/style.css b/aslan/recharge-activity/style.css new file mode 100644 index 0000000..8d4e9fa --- /dev/null +++ b/aslan/recharge-activity/style.css @@ -0,0 +1,399 @@ +* { + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; +} + +:root { + --app-scale: 1; + --design-scale: 0.3472222222; +} + +html, +body { + margin: 0; + width: 100%; + min-height: 100%; + overflow-x: hidden; + background: var(--hy-theme-bg); + color: #f6e0b2; + font-family: + Arial, + "Helvetica Neue", + Helvetica, + sans-serif; +} + +body { + min-height: 1137px; +} + +.app-viewport { + position: relative; + width: 375px; + min-height: 1137px; + margin: 0 auto; + transform: scale(var(--app-scale)); + transform-origin: top center; +} + +.page { + position: relative; + width: 375px; + min-height: 1137px; + overflow: hidden; + background: #1b100d; +} + +.stage { + position: absolute; + left: 0; + top: 0; + width: 1080px; + height: 3273px; + overflow: hidden; + transform: scale(var(--design-scale)); + transform-origin: top left; + background: #1b100d; +} + +.stage > * { + position: absolute; + z-index: 1; +} + +.bg { + left: 0; + z-index: 0; + width: 1080px; + object-fit: fill; + pointer-events: none; + user-select: none; +} + +.bg-top { + top: 0; + height: 1588px; +} + +.bg-mid { + height: 649px; + object-fit: cover; + object-position: center center; +} + +.bg-mid-a { + top: 1000px; +} + +.bg-mid-b { + top: 1400px; +} + +.bg-mid-c { + top: 1800px; +} + +.bg-mid-d { + top: 2200px; +} + +.bg-mid-e { + top: 2600px; +} + +.bg-bottom { + top: 2695px; + height: 578px; +} + +.countdown { + left: 90px; + top: 696px; + width: 900px; + height: 378px; +} + +.countdown-frame { + position: absolute; + inset: 0; + width: 900px; + height: 378px; + object-fit: fill; + pointer-events: none; +} + +.count-number { + position: absolute; + top: 156px; + width: 96px; + height: 74px; + color: #ffce95; + font-size: 60px; + font-weight: 900; + line-height: 74px; + letter-spacing: 0; + text-align: center; + text-shadow: + 0 2px 0 #47200d, + 0 0 14px rgba(255, 210, 135, 0.58); +} + +.count-number[data-count="days"] { + left: 97px; +} + +.count-number[data-count="hours"] { + left: 301px; +} + +.count-number[data-count="minutes"] { + left: 506px; +} + +.count-number[data-count="seconds"] { + left: 706px; +} + +.count-label { + position: absolute; + top: 274px; + width: 150px; + height: 30px; + color: #100800; + font-size: 22px; + font-weight: 800; + line-height: 30px; + letter-spacing: 0; + text-align: center; + white-space: nowrap; + transform: translateX(-50%); +} + +.count-label:nth-of-type(1) { + left: 145px; +} + +.count-label:nth-of-type(2) { + left: 349px; +} + +.count-label:nth-of-type(3) { + left: 554px; +} + +.count-label:nth-of-type(4) { + left: 754px; +} + +.profile-card { + left: 90px; + top: 1089px; + width: 900px; + height: 672px; +} + +.profile-frame { + position: absolute; + inset: 0; + width: 900px; + height: 672px; + object-fit: fill; + pointer-events: none; +} + +.profile-avatar { + position: absolute; + left: 334px; + top: 157px; + width: 232px; + height: 232px; + border-radius: 50%; + object-fit: cover; + pointer-events: none; +} + +.profile-name, +.profile-id, +.profile-recharge { + position: absolute; + left: 72px; + width: 756px; + margin: 0; + color: #f6e0b2; + letter-spacing: 0; + text-align: center; + text-shadow: 0 3px 8px rgba(0, 0, 0, 0.72); +} + +.profile-name { + top: 425px; + font-size: 38px; + font-weight: 800; + line-height: 48px; +} + +.profile-id { + top: 476px; + font-size: 26px; + font-weight: 700; + line-height: 34px; +} + +.profile-recharge { + top: 520px; + font-size: 36px; + font-weight: 800; + line-height: 46px; +} + +.reward-sections { + left: 0; + top: 0; + width: 1080px; + height: 3273px; +} + +.reward-section { + position: absolute; + left: 90px; + top: var(--section-top); + width: 900px; +} + +.title-pill { + position: absolute; + left: 0; + top: 0; + z-index: 4; + width: 900px; + height: 180px; +} + +.reward-section-bg { + position: absolute; + inset: 0; + z-index: 1; + width: 900px; + height: 675px; + object-fit: fill; + pointer-events: none; +} + +.title-pill span { + position: absolute; + left: 0; + top: 95px; + z-index: 4; + width: 900px; + color: #f6e0b2; + font-size: 40px; + font-weight: 900; + line-height: 48px; + letter-spacing: 0; + text-align: center; + text-shadow: + 0 2px 0 #4f2306, + 0 0 14px rgba(255, 207, 113, 0.45); +} + +.reward-card { + position: absolute; + z-index: 3; + width: 229px; + height: 251px; + color: #f6e0b2; + text-align: center; +} + +.reward-frame { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: fill; + pointer-events: none; +} + +.reward-icon { + position: absolute; + left: 18px; + top: 19px; + width: 193px; + height: 193px; + object-fit: contain; + pointer-events: none; +} + +.reward-price { + position: absolute; + left: 0; + top: 196px; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 32px; + color: #f6e0b2; + font-size: 26px; + font-weight: 900; + line-height: 32px; + letter-spacing: 0; + text-align: center; + text-shadow: + 0 2px 0 #3a1704, + 0 0 10px rgba(255, 207, 113, 0.44); +} + +.claim-button { + position: absolute; + left: 250.5px; + top: 538px; + z-index: 4; + width: 399px; + height: 133px; + border: 0; + padding: 0; + background: transparent; + color: #fff8d2; + font: inherit; + letter-spacing: 0; + text-align: center; + cursor: pointer; +} + +.claim-button:disabled { + cursor: default; + opacity: 0.68; +} + +.claim-button-bg { + position: absolute; + inset: 0; + width: 399px; + height: 133px; + object-fit: fill; + pointer-events: none; +} + +.claim-button span { + position: absolute; + left: 0; + top: 47px; + width: 399px; + height: 40px; + color: #fff8d2; + font-size: 40px; + font-weight: 900; + line-height: 40px; + text-shadow: + 0 2px 0 #7d2606, + 0 0 10px rgba(255, 230, 128, 0.68); + pointer-events: none; +} + +@media (min-width: 431px) { + html, + body { + background: #100806; + } +}