Compare commits

..

2 Commits

Author SHA1 Message Date
zhx
939acdfc31 Merge remote-tracking branch 'origin/codex/aslan-recharge-activity' 2026-07-09 17:30:02 +08:00
ooo
f514df9db1 add aslan recharge activity page 2026-07-07 18:02:14 +08:00
14 changed files with 701 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 KiB

View File

@ -0,0 +1,98 @@
<!doctype html>
<html lang="en" data-hy-app-code="aslan">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
/>
<title>Recharge Reward</title>
<link rel="stylesheet" href="../common/theme.css?v=20260707" />
<link rel="stylesheet" href="./style.css?v=20260707-modules" />
</head>
<body>
<div class="app-viewport" id="appViewport">
<main class="page" id="page">
<section
class="stage"
aria-label="Recharge Reward activity"
data-i18n-aria="rechargeReward.pageLabel"
>
<img class="bg bg-top" src="./assets/recharge-activity/bg-top.png" alt="" />
<img
class="bg bg-mid bg-mid-a"
src="./assets/recharge-activity/bg-mid-a.png"
alt=""
/>
<img
class="bg bg-mid bg-mid-b"
src="./assets/recharge-activity/bg-mid-a.png"
alt=""
/>
<img
class="bg bg-mid bg-mid-c"
src="./assets/recharge-activity/bg-mid-a.png"
alt=""
/>
<img
class="bg bg-mid bg-mid-d"
src="./assets/recharge-activity/bg-mid-a.png"
alt=""
/>
<img
class="bg bg-mid bg-mid-e"
src="./assets/recharge-activity/bg-mid-a.png"
alt=""
/>
<img
class="bg bg-bottom"
src="./assets/recharge-activity/bg-bottom.png"
alt=""
/>
<section class="countdown" aria-label="Activity countdown">
<img
class="countdown-frame"
src="./assets/recharge-activity/countdown-frame.png"
alt=""
/>
<strong class="count-number" data-count="days">01</strong>
<strong class="count-number" data-count="hours">01</strong>
<strong class="count-number" data-count="minutes">01</strong>
<strong class="count-number" data-count="seconds">01</strong>
<span class="count-label" data-i18n="rechargeReward.days">Days</span>
<span class="count-label" data-i18n="rechargeReward.hours">Hours</span>
<span class="count-label" data-i18n="rechargeReward.minutes">Minutes</span>
<span class="count-label" data-i18n="rechargeReward.seconds">Seconds</span>
</section>
<section class="profile-card" aria-label="My recharge status">
<img
class="profile-frame"
src="./assets/recharge-activity/profile-frame.png"
alt=""
/>
<img
class="profile-avatar"
src="./assets/recharge-activity/profile-avatar.png"
alt=""
data-profile-avatar
/>
<h1 class="profile-name" data-profile-name>nanfangjian</h1>
<p class="profile-id" data-profile-id>ID: 12345678</p>
<p class="profile-recharge" data-profile-recharge>
Accumlated Recharge:5998
</p>
</section>
<div class="reward-sections" id="rewardSections"></div>
</section>
</main>
</div>
<script>
document.documentElement.dataset.hyAppCode = 'aslan';
</script>
<script src="../common/i18n.js?v=20260707"></script>
<script src="./script.js?v=20260707-modules"></script>
</body>
</html>

View File

@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
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 [
'<div class="reward-card" style="left:',
card.left,
'px;top:',
card.top,
'px">',
'<img class="reward-frame" src="',
ASSET_BASE,
'reward-card-frame.png',
'" alt="" />',
'<img class="reward-icon" src="',
escapeHTML(card.icon),
'" alt="" />',
'<span class="reward-price">',
escapeHTML(card.price),
'</span>',
'</div>',
].join('');
}
function renderClaimButton(claim) {
var stateClass = claim.claimed ? ' is-claimed' : claim.claimable ? ' is-claimable' : ' is-disabled';
return [
'<button class="claim-button',
stateClass,
'" type="button"',
claim.claimable && !claim.claimed ? '' : ' disabled',
'>',
'<img class="claim-button-bg" src="',
ASSET_BASE,
'receive-button-bg.png" alt="" />',
'<span>',
escapeHTML(claim.text || 'Receive'),
'</span>',
'</button>',
].join('');
}
function renderRewards(rewards) {
rewardSections.innerHTML = rewards
.map(function (section) {
var title = t('rechargeReward.rechargeTitle', 'Recharge {amount}').replace(
'{amount}',
section.amount
);
return [
'<section class="reward-section" style="--section-top:',
section.top,
'px">',
'<img class="reward-section-bg" src="',
escapeHTML(section.background),
'" alt="" />',
'<div class="title-pill">',
'<span>',
escapeHTML(title),
'</span>',
'</div>',
section.cards
.map(function (card) {
return renderRewardCard(card);
})
.join(''),
renderClaimButton(section.claim || { text: 'Receive', claimable: false }),
'</section>',
].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();
})();

View File

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