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
+
+
+
+
+
+
+
+
+
+
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;
+ }
+}