hyapp-h5/rank/index.html
2026-05-29 19:55:17 +08:00

1462 lines
54 KiB
HTML

<!doctype html>
<html lang="en">
<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>Ranking</title>
<style>
* {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
:root {
--app-scale: 1;
}
html,
body {
margin: 0;
width: 100%;
min-height: 100%;
overflow-x: hidden;
background: #130a2b;
font-family: Arial, Helvetica, sans-serif;
color: #fff;
}
button {
border: 0;
padding: 0;
background: transparent;
color: inherit;
font: inherit;
cursor: pointer;
outline: none;
}
.app-viewport {
position: relative;
width: 375px;
min-height: 812px;
margin: 0 auto;
transform: scale(var(--app-scale));
transform-origin: top center;
}
.page {
position: relative;
width: 375px;
min-height: 812px;
margin: 0;
overflow-x: hidden;
background: var(--page-bg, #7c2e33);
}
.rank-page {
padding-bottom: 126px;
}
.rank-bg {
position: absolute;
left: -79px;
top: var(--bg-top, -208px);
width: 533px;
height: 801px;
object-fit: cover;
filter: var(--bg-filter, none);
pointer-events: none;
}
.rank-bg.charm-layer {
opacity: 0.72;
filter: none;
}
.top-shade {
position: absolute;
inset: 0 0 auto;
height: 156px;
pointer-events: none;
background: linear-gradient(
180deg,
var(--shade, #3a1110),
rgba(58, 17, 16, 0)
);
}
.nav {
position: relative;
height: 88px;
z-index: 2;
}
.back {
position: absolute;
left: 16px;
top: 10px;
width: 24px;
height: 24px;
}
.back::before {
content: '';
position: absolute;
left: 8px;
top: 5px;
width: 12px;
height: 12px;
border-left: 2px solid #fff;
border-bottom: 2px solid #fff;
transform: rotate(45deg);
}
.help {
position: absolute;
right: 18px;
top: 11px;
width: 18px;
height: 18px;
border: 1px solid rgba(255, 255, 255, 0.72);
border-radius: 50%;
color: rgba(255, 255, 255, 0.86);
font-size: 13px;
line-height: 17px;
text-align: center;
}
.tabs {
position: absolute;
left: 60px;
right: 58px;
top: 13px;
display: grid;
grid-template-columns: repeat(3, 1fr);
align-items: start;
}
.tab {
position: relative;
height: 32px;
font-size: 16px;
line-height: 16px;
color: #e2e2e2;
text-align: center;
text-transform: capitalize;
}
.tab.active {
color: #ffc219;
font-weight: 600;
}
.tab.active::after {
content: '';
position: absolute;
left: 50%;
bottom: 1px;
width: 16px;
height: 3px;
margin-left: -8px;
border-radius: 31px;
background: #ffc219;
}
.periods {
position: absolute;
left: 65px;
top: 48px;
width: 258px;
height: 29px;
border: 1px solid #ffc376;
border-radius: 15px;
background: #23180a;
z-index: 2;
}
.period {
position: absolute;
top: 0;
width: 86px;
height: 29px;
color: #fff;
font-size: 12px;
line-height: 29px;
text-align: center;
}
.period[data-period='today'] {
left: 0;
}
.period[data-period='week'] {
left: 86px;
}
.period[data-period='month'] {
left: 172px;
}
.period.active {
color: #492307;
font-weight: 600;
}
.period.active::before {
content: '';
position: absolute;
left: -6px;
top: -5px;
width: 92px;
height: 36px;
background: url('assets/period-active.png') center/100% 100%
no-repeat;
z-index: -1;
}
.podium {
position: relative;
z-index: 1;
height: 351px;
margin-top: -44px;
}
.podium-item {
position: absolute;
text-align: center;
}
.podium-item .avatar {
position: absolute;
border-radius: 50%;
object-fit: cover;
}
.podium-frame {
position: absolute;
pointer-events: none;
}
.podium-name {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 128px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 12px;
line-height: 12px;
text-transform: capitalize;
}
.badges {
position: absolute;
display: flex;
gap: 4px;
justify-content: center;
}
.badges img {
width: 12px;
height: 12px;
}
.score-pill {
position: absolute;
left: 50%;
transform: translateX(-50%);
min-width: 116px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
gap: 3px;
font-size: 10px;
line-height: 10px;
background: linear-gradient(
90deg,
rgba(35, 24, 10, 0),
var(--score-bg-soft, rgba(35, 24, 10, 0.72)) 16%,
var(--score-bg, #23180a) 33%,
var(--score-bg, #23180a) 65%,
var(--score-bg-soft, rgba(35, 24, 10, 0.72)) 80%,
rgba(35, 24, 10, 0)
);
}
.coin {
width: 12px;
height: 12px;
object-fit: cover;
flex: 0 0 auto;
}
.top1 {
left: 87px;
top: 17px;
width: 200px;
height: 223px;
}
.top1 .podium-frame {
left: 0;
top: 0;
width: 200px;
height: 200px;
}
.top1 .avatar {
left: 60px;
top: 60px;
width: 80px;
height: 80px;
}
.top1 .podium-name {
top: 175px;
}
.top1 .badges {
left: 71px;
top: 191px;
}
.top1 .score-pill {
top: 205px;
}
.top2,
.top3 {
top: 198px;
width: 154px;
height: 181px;
}
.top2 {
left: 10px;
}
.top3 {
right: 10px;
}
.top2 .podium-frame,
.top3 .podium-frame {
left: 0;
top: 0;
width: 154px;
height: 154px;
}
.top2 .avatar,
.top3 .avatar {
left: 45px;
top: 45px;
width: 64px;
height: 64px;
}
.top2 .podium-name,
.top3 .podium-name {
top: 137px;
}
.top2 .badges,
.top3 .badges {
left: 47px;
top: 153px;
}
.top2 .score-pill,
.top3 .score-pill {
top: 167px;
}
.list {
position: relative;
z-index: 1;
padding: 44px 16px 16px;
}
.rank-row {
width: 100%;
height: 52px;
margin-bottom: 8px;
display: grid;
grid-template-columns: 26px 40px minmax(0, 1fr) 74px;
align-items: center;
gap: 0;
border: 1px solid #ffc376;
border-radius: 4px;
background: var(--row-bg, rgba(35, 24, 10, 0.8));
text-align: left;
}
.rank-no {
font-size: 12px;
text-align: center;
}
.row-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
object-fit: cover;
}
.row-main {
min-width: 0;
padding-left: 8px;
text-align: left;
}
.row-name {
max-width: 172px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 12px;
line-height: 14px;
}
.row-badges {
display: flex;
gap: 4px;
margin-top: 5px;
}
.row-badges img {
width: 12px;
height: 12px;
}
.row-score {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 3px;
padding-right: 18px;
font-size: 12px;
white-space: nowrap;
}
.row-score .coin {
width: 14px;
height: 14px;
}
.my-rank {
position: fixed;
left: 50%;
bottom: 0;
z-index: 40;
width: min(375px, 100vw);
height: calc(80px + env(safe-area-inset-bottom));
padding-bottom: env(safe-area-inset-bottom);
transform: translateX(-50%);
border: 1px solid #fcd98f;
border-bottom: 0;
border-radius: 10px 10px 0 0;
background: linear-gradient(
180deg,
var(--mine-a, #bb1a1c),
var(--mine-b, #601718)
);
box-shadow: inset 0 0 4px #000;
overflow: hidden;
}
.my-rank[hidden] {
display: none;
}
.my-rank::before {
content: '';
position: absolute;
inset: 0;
opacity: 0.08;
background:
radial-gradient(
circle at 50% -20%,
#fff 0,
transparent 35%
),
repeating-radial-gradient(
circle at 20px 20px,
rgba(255, 255, 255, 0.5) 0 1px,
transparent 2px 16px
);
pointer-events: none;
}
.my-rank .rank-row {
position: absolute;
left: 16px;
right: 16px;
top: 12px;
margin: 0;
border-color: transparent;
background: transparent;
}
.home-indicator {
position: absolute;
left: 50%;
bottom: 9px;
z-index: 5;
width: 134px;
height: 5px;
margin-left: -67px;
border-radius: 100px;
background: #fff;
}
.rank-home-indicator {
position: fixed;
z-index: 41;
}
.rank-home-indicator[hidden] {
display: none;
}
.empty {
min-height: 180px;
opacity: 0;
}
.rank-state {
position: relative;
z-index: 1;
min-height: 180px;
display: flex;
align-items: center;
justify-content: center;
padding: 24px 16px;
color: rgba(255, 255, 255, 0.72);
font-size: 12px;
line-height: 16px;
text-align: center;
}
.loading-overlay {
position: absolute;
inset: 0;
min-height: 812px;
z-index: 20;
display: none;
align-items: center;
justify-content: center;
background: rgba(19, 10, 43, 0.32);
backdrop-filter: blur(1px);
pointer-events: auto;
}
.loading-overlay.active {
display: flex;
}
.cat-loader {
width: 72px;
height: 72px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: rgba(0, 0, 0, 0.18);
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.28);
}
.cat-loader svg {
width: 54px;
height: 54px;
overflow: visible;
animation: cat-float 1.1s ease-in-out infinite;
}
.cat-loader .cat-line {
fill: none;
stroke: #fff;
stroke-width: 3.4;
stroke-linecap: round;
stroke-linejoin: round;
}
.cat-loader .cat-eye {
transform-origin: center;
animation: cat-blink 1.6s ease-in-out infinite;
}
.cat-loader .cat-whisker {
opacity: 0.9;
animation: cat-whisker 1.1s ease-in-out infinite;
}
@keyframes cat-float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-5px);
}
}
@keyframes cat-blink {
0%,
72%,
100% {
transform: scaleY(1);
}
80%,
88% {
transform: scaleY(0.18);
}
}
@keyframes cat-whisker {
0%,
100% {
opacity: 0.62;
}
50% {
opacity: 1;
}
}
.note-page {
display: none;
min-height: 812px;
background: #130a2b;
overflow: hidden;
}
.note-page.active {
display: block;
}
.rank-page.hidden {
display: none;
}
.note-bg {
position: absolute;
left: -13px;
top: -253px;
width: 568px;
height: 1065px;
object-fit: cover;
}
.note-nav {
position: relative;
height: 60px;
z-index: 2;
}
.note-title {
position: absolute;
top: 13px;
left: 0;
right: 0;
font-size: 18px;
line-height: 18px;
text-align: center;
font-weight: 600;
}
.note-tabs {
position: relative;
z-index: 2;
height: 45px;
display: grid;
grid-template-columns: repeat(3, 1fr);
padding: 0 24px;
}
.note-tab {
position: relative;
height: 32px;
font-size: 18px;
line-height: 18px;
color: rgba(255, 255, 255, 0.5);
text-transform: capitalize;
text-align: left;
}
.note-tab:nth-child(2) {
text-align: center;
}
.note-tab:nth-child(3) {
text-align: right;
}
.note-tab.active {
color: #fff;
}
.note-tab.active::after {
content: '';
position: absolute;
left: 50%;
bottom: 0;
width: 16px;
height: 3px;
margin-left: -8px;
border-radius: 31px;
background: #fff;
}
.note-copy {
position: relative;
z-index: 2;
margin: 2px 24px 0;
width: 327px;
min-height: 56px;
font-size: 16px;
line-height: 20px;
}
.reward-panel {
position: relative;
z-index: 2;
margin: 14px 24px 0;
width: 327px;
height: 474px;
border: 2px solid #fff09b;
background: #2a1351;
box-shadow: inset 0 0 80px rgba(104, 68, 180, 0.7);
}
.reward-panel::before,
.reward-panel::after {
content: '';
position: absolute;
top: -12px;
width: 163px;
height: 27px;
background: url('assets/note-border.png') center/100% 100%
no-repeat;
}
.reward-panel::before {
left: 0;
}
.reward-panel::after {
right: 0;
transform: scaleX(-1);
}
.reward-title {
margin-top: 26px;
font-size: 20px;
line-height: 20px;
font-weight: 800;
text-align: center;
color: #fff;
text-shadow: 0 2px 0 #be1e21;
}
.reward-periods {
position: relative;
width: 295px;
height: 36px;
margin: 16px auto 0;
border-radius: 8px;
background: rgba(255, 255, 255, 0.08);
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.reward-period {
height: 32px;
margin: 2px;
border-radius: 8px;
font-size: 14px;
line-height: 32px;
text-align: center;
}
.reward-period.active {
background: #fff;
color: #71179a;
}
.reward-grid {
width: 295px;
margin: 16px auto 0;
display: grid;
grid-template-columns: repeat(2, 140px);
gap: 8px 15px;
}
.reward-card {
height: 166px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.08);
text-align: center;
overflow: hidden;
}
.reward-card h3 {
margin: 8px 0 8px;
font-size: 16px;
line-height: 16px;
color: #fff;
text-shadow: 0 2px 0 #be1e21;
}
.reward-card img.reward {
width: 80px;
height: 80px;
object-fit: contain;
}
.reward-name {
margin-top: 4px;
font-size: 14px;
line-height: 14px;
}
.reward-value {
margin-top: 6px;
display: flex;
justify-content: center;
align-items: center;
gap: 2px;
font-size: 12px;
line-height: 12px;
}
</style>
</head>
<body>
<div id="appViewport" class="app-viewport">
<main id="rankPage" class="page rank-page">
<img
id="rankBg"
class="rank-bg"
src="assets/wealth-bg.png"
alt=""
/>
<img
id="rankBgCharm"
class="rank-bg charm-layer"
src="assets/charm-bg-b.png"
alt=""
hidden
/>
<div class="top-shade"></div>
<section class="nav">
<button
class="back"
id="backButton"
aria-label="Back"
></button>
<div class="tabs" id="boardTabs">
<button class="tab active" data-board="sent">
Wealth
</button>
<button class="tab" data-board="room">Room</button>
<button class="tab" data-board="received">Charm</button>
</div>
<button class="help" id="helpButton" aria-label="Note">
?
</button>
<div class="periods" id="periodTabs">
<button class="period active" data-period="today">
Daily
</button>
<button class="period" data-period="week">
Weekly
</button>
<button class="period" data-period="month">
Monthly
</button>
</div>
</section>
<section class="podium" id="podium"></section>
<section class="list" id="rankList"></section>
<section class="my-rank" id="myRank" hidden></section>
<div
class="home-indicator rank-home-indicator"
id="rankHomeIndicator"
hidden
></div>
</main>
<main id="notePage" class="page note-page">
<img class="note-bg" src="assets/note-bg.png" alt="" />
<section class="note-nav">
<button
class="back"
id="noteBackButton"
aria-label="Back"
></button>
<div class="note-title">Note</div>
</section>
<section class="note-tabs" id="noteTabs">
<button class="note-tab active" data-board="sent">
Wealth
</button>
<button class="note-tab" data-board="room">Room</button>
<button class="note-tab" data-board="received">
Charm
</button>
</section>
<div class="note-copy" id="noteCopy"></div>
<section class="reward-panel">
<div class="reward-title">Ranking Rewards</div>
<div class="reward-periods" id="rewardPeriods">
<button
class="reward-period active"
data-period="today"
>
Today
</button>
<button class="reward-period" data-period="week">
This Week
</button>
<button class="reward-period" data-period="month">
Monthly
</button>
</div>
<div class="reward-grid" id="rewardGrid"></div>
</section>
<div class="home-indicator"></div>
</main>
<div id="loadingOverlay" class="loading-overlay" aria-hidden="true">
<div class="cat-loader" role="status" aria-label="Loading">
<svg viewBox="0 0 64 64" aria-hidden="true">
<path class="cat-line" d="M17 27 13 12 25 20" />
<path class="cat-line" d="M47 27 51 12 39 20" />
<path
class="cat-line"
d="M14 34c0-12 8-20 18-20s18 8 18 20c0 11-8 18-18 18S14 45 14 34Z"
/>
<path class="cat-line cat-eye" d="M25 33h.1" />
<path class="cat-line cat-eye" d="M39 33h.1" />
<path class="cat-line" d="M32 39v2" />
<path class="cat-line" d="M27 44c2 2 8 2 10 0" />
<path class="cat-line cat-whisker" d="M21 39h-8" />
<path class="cat-line cat-whisker" d="M22 43l-8 3" />
<path class="cat-line cat-whisker" d="M43 39h8" />
<path class="cat-line cat-whisker" d="M42 43l8 3" />
</svg>
</div>
</div>
</div>
<script src="../common/api.js"></script>
<script src="../common/toast.js"></script>
<script src="../common/params.js"></script>
<script src="../common/jsbridge.js"></script>
<script>
(function () {
var ASSETS = {
sent: {
bg: 'assets/wealth-bg.png',
bg2: '',
bgTop: '-208px',
page: '#7c2e33',
shade: '#3a1110',
row: 'rgba(35,24,10,.8)',
score: '#23180a',
mineA: '#bb1a1c',
mineB: '#601718',
},
room: {
bg: 'assets/wealth-bg.png',
bg2: '',
bgTop: '-208px',
bgFilter:
'hue-rotate(175deg) saturate(1.35) brightness(.7)',
page: '#110f3d',
shade: '#18103a',
row: 'rgba(9,13,35,.8)',
score: '#090d23',
mineA: '#1a4abb',
mineB: '#040513',
},
received: {
bg: 'assets/charm-bg-a.png',
bg2: 'assets/charm-bg-b.png',
bgTop: '-208px',
page: '#110f3d',
shade: '#2f103a',
row: 'rgba(34,9,35,.8)',
score: '#1a0923',
mineA: '#551abb',
mineB: '#1a0a2b',
},
};
var state = {
board: 'sent',
period: 'today',
page: 1,
pageSize: 20,
total: 0,
items: [],
myRank: null,
loading: false,
loaded: false,
error: null,
};
var sampleNames = [
'Namenamename',
'Namenamename',
'Namenamename',
'Namenamename',
'Namenamename',
'Namenamename',
'Namenamename',
'Namenamename',
'Namenamename',
];
var noteCopy = {
sent: 'The ranking is based on the total number of coins users have spent on gifts.',
room: 'The ranking is based on the total number of coins received by rooms.',
received:
'The ranking is based on the total number of coins users have received from gifts.',
};
function byId(id) {
return document.getElementById(id);
}
function queryValue(name) {
return new URLSearchParams(window.location.search).get(
name
);
}
function mockEnabled() {
return queryValue('mock') === '1';
}
function money(value) {
var num = Number(value || 0);
if (num >= 1000000)
return (
(num / 1000000).toFixed(
num % 1000000 === 0 ? 0 : 1
) + 'm'
);
if (num >= 1000)
return (
(num / 1000).toFixed(num % 1000 === 0 ? 0 : 1) + 'k'
);
return String(num);
}
function userName(item) {
return (
(item.user &&
(item.user.username ||
item.user.display_user_id ||
item.user.user_id)) ||
(item.room &&
(item.room.title ||
item.room.room_short_id ||
item.room.room_id)) ||
item.room_id ||
item.user_id ||
'Namenamename'
);
}
function avatar(item, size) {
if (item.user && item.user.avatar) return item.user.avatar;
if (item.room && item.room.cover_url)
return item.room.cover_url;
if (size === 'large')
return 'assets/default-avatar-large.png';
if (size === 'mid') return 'assets/default-avatar-mid.png';
return 'assets/default-avatar-small.png';
}
function sampleItems() {
return sampleNames.map(function (name, index) {
var rank = index + 1;
var base = rank <= 3 ? 123400 : 102400;
if (state.board === 'room') {
return {
rank: rank,
room_id: 'room_' + rank,
gift_value: base,
room: { room_id: 'room_' + rank, title: name },
};
}
return {
rank: rank,
user_id: String(10000 + rank),
gift_value: base,
user: {
user_id: String(10000 + rank),
username: name,
},
};
});
}
function badges() {
var html = '';
for (var i = 0; i < 4; i += 1)
html += '<img src="assets/vip.png" alt="">';
return html;
}
function score(value, className) {
return (
'<div class="' +
className +
'"><img class="coin" src="assets/coin.png" alt="">' +
money(value) +
'</div>'
);
}
function normalizeItem(raw) {
var item = raw || {};
var user = item.user || {};
var room = item.room || {};
var userId = item.user_id || user.user_id || '';
var roomId = item.room_id || room.room_id || '';
return {
rank: Number(item.rank || 0),
user_id: String(userId || ''),
room_id: String(roomId || ''),
gift_value: Number(
item.gift_value || item.giftValue || item.value || 0
),
gift_count: Number(
item.gift_count || item.giftCount || 0
),
transaction_count: Number(
item.transaction_count || item.transactionCount || 0
),
last_gift_at_ms: Number(
item.last_gift_at_ms || item.lastGiftAtMs || 0
),
user: userId
? {
user_id: String(userId),
display_user_id:
user.display_user_id ||
user.displayUserId ||
'',
username:
user.username ||
user.nickname ||
user.name ||
'',
avatar:
user.avatar ||
user.avatar_url ||
user.avatarUrl ||
'',
}
: null,
room: roomId
? {
room_id: String(roomId),
room_short_id:
room.room_short_id ||
room.roomShortId ||
'',
title: room.title || room.name || '',
cover_url:
room.cover_url ||
room.coverUrl ||
room.room_avatar ||
room.roomAvatar ||
room.avatar ||
'',
}
: null,
};
}
function normalizePayload(data, reset) {
var items = (
data && Array.isArray(data.items) ? data.items : []
)
.map(normalizeItem)
.filter(function (item) {
return item.rank > 0;
});
state.total = Number(
(data && data.total) || items.length || 0
);
state.page = Number((data && data.page) || state.page);
state.pageSize = Number(
(data && (data.page_size || data.pageSize)) ||
state.pageSize
);
state.myRank =
data && data.my_rank
? normalizeItem(data.my_rank)
: null;
state.items = reset ? items : state.items.concat(items);
state.loaded = true;
state.error = null;
}
function visibleItems() {
if (state.items.length) return state.items;
return mockEnabled() ? sampleItems() : [];
}
function renderPodium() {
var items = visibleItems();
var top = [items[0], items[1], items[2]];
var html = '';
if (top[0]) {
html +=
'<button class="podium-item top1" data-index="0"><img class="avatar" src="' +
avatar(top[0], 'large') +
'" alt=""><img class="podium-frame" src="assets/top-frame.png" alt=""><div class="podium-name">' +
userName(top[0]) +
'</div><div class="badges">' +
badges() +
'</div>' +
score(top[0].gift_value, 'score-pill') +
'</button>';
}
if (top[1]) {
html +=
'<button class="podium-item top2" data-index="1"><img class="avatar" src="' +
avatar(top[1], 'mid') +
'" alt=""><img class="podium-frame" src="assets/side-frame-blue.png" alt=""><div class="podium-name">' +
userName(top[1]) +
'</div><div class="badges">' +
badges() +
'</div>' +
score(top[1].gift_value, 'score-pill') +
'</button>';
}
if (top[2]) {
html +=
'<button class="podium-item top3" data-index="2"><img class="avatar" src="' +
avatar(top[2], 'mid') +
'" alt=""><img class="podium-frame" src="assets/side-frame-red.png" alt=""><div class="podium-name">' +
userName(top[2]) +
'</div><div class="badges">' +
badges() +
'</div>' +
score(top[2].gift_value, 'score-pill') +
'</button>';
}
byId('podium').innerHTML = html;
}
function row(item, mine) {
if (!item) return '';
return (
'<button class="rank-row" data-rank="' +
item.rank +
'"><div class="rank-no">' +
item.rank +
'</div><img class="row-avatar" src="' +
avatar(item, 'small') +
'" alt=""><div class="row-main"><div class="row-name">' +
userName(item) +
'</div><div class="row-badges">' +
badges() +
'</div></div>' +
score(item.gift_value, 'row-score') +
'</button>'
);
}
function renderList() {
var items = visibleItems();
var myRank = byId('myRank');
var rankHomeIndicator = byId('rankHomeIndicator');
if (!items.length) {
byId('rankList').innerHTML =
'<div class="rank-state">' +
(state.error
? 'Network error'
: state.loading || !state.loaded
? 'Loading'
: 'No ranking data') +
'</div>';
myRank.innerHTML = '';
myRank.hidden = true;
rankHomeIndicator.hidden = true;
return;
}
byId('rankList').innerHTML =
items
.slice(3)
.map(function (item) {
return row(item);
})
.join('') + '<div class="empty"></div>';
myRank.innerHTML = row(state.myRank || items[3] || items[0]);
myRank.hidden = false;
rankHomeIndicator.hidden = false;
}
function applyTheme() {
var cfg = ASSETS[state.board];
document.documentElement.style.setProperty(
'--page-bg',
cfg.page
);
document.documentElement.style.setProperty(
'--shade',
cfg.shade
);
document.documentElement.style.setProperty(
'--row-bg',
cfg.row
);
document.documentElement.style.setProperty(
'--score-bg',
cfg.score
);
document.documentElement.style.setProperty(
'--score-bg-soft',
cfg.score.replace(')', ', .72)').replace('rgb', 'rgba')
);
document.documentElement.style.setProperty(
'--mine-a',
cfg.mineA
);
document.documentElement.style.setProperty(
'--mine-b',
cfg.mineB
);
document.documentElement.style.setProperty(
'--bg-top',
cfg.bgTop
);
document.documentElement.style.setProperty(
'--bg-filter',
cfg.bgFilter || 'none'
);
byId('rankBg').src = cfg.bg;
byId('rankBgCharm').hidden = !cfg.bg2;
if (cfg.bg2) byId('rankBgCharm').src = cfg.bg2;
}
function syncTabs() {
document
.querySelectorAll('[data-board]')
.forEach(function (node) {
node.classList.toggle(
'active',
node.dataset.board === state.board
);
});
document
.querySelectorAll('[data-period]')
.forEach(function (node) {
node.classList.toggle(
'active',
node.dataset.period === state.period
);
});
}
function activePage() {
return byId('notePage').classList.contains('active')
? byId('notePage')
: byId('rankPage');
}
function resizeApp() {
var designWidth = 375;
var scale = Math.min(window.innerWidth / designWidth, 1.25);
var page = activePage();
var height = Math.max(812, page.scrollHeight);
document.documentElement.style.setProperty(
'--app-scale',
String(scale)
);
byId('appViewport').style.height = height + 'px';
document.body.style.minHeight =
Math.ceil(height * scale) + 'px';
}
function mountFixedRankBar() {
var myRank = byId('myRank');
var rankHomeIndicator = byId('rankHomeIndicator');
if (myRank && myRank.parentNode !== document.body) {
document.body.appendChild(myRank);
}
if (
rankHomeIndicator &&
rankHomeIndicator.parentNode !== document.body
) {
document.body.appendChild(rankHomeIndicator);
}
}
function scheduleResize() {
window.requestAnimationFrame(resizeApp);
}
function syncLoading() {
var overlay = byId('loadingOverlay');
overlay.classList.toggle('active', state.loading);
overlay.setAttribute(
'aria-hidden',
state.loading ? 'false' : 'true'
);
}
function render() {
applyTheme();
syncTabs();
syncLoading();
renderPodium();
renderList();
scheduleResize();
}
function load(reset) {
if (state.loading) return Promise.resolve();
if (reset) {
state.page = 1;
state.items = [];
state.total = 0;
state.myRank = null;
state.loaded = false;
state.error = null;
render();
}
if (mockEnabled()) {
state.loaded = true;
state.error = null;
return Promise.resolve(render());
}
if (!window.HyAppAPI || !window.HyAppAPI.getAccessToken()) {
state.loaded = true;
state.error = new Error('missing_token');
return Promise.resolve(render());
}
state.loading = true;
state.error = null;
render();
return window.HyAppAPI.get(
'/api/v1/activities/user-leaderboards',
{
board_type: state.board,
period: state.period,
page: state.page,
page_size: state.pageSize,
}
)
.then(function (data) {
normalizePayload(data, reset);
render();
})
.catch(function (error) {
state.loaded = true;
state.error = error;
render();
})
.finally(function () {
state.loading = false;
render();
});
}
function openItem(item) {
if (!item) return;
if (state.board === 'room') {
window.HyAppBridge.openRoom(
item.room_id || (item.room && item.room.room_id)
);
} else {
window.HyAppBridge.openUser(
item.user_id || (item.user && item.user.user_id)
);
}
}
function showNote() {
byId('rankPage').classList.add('hidden');
byId('notePage').classList.add('active');
byId('myRank').hidden = true;
byId('rankHomeIndicator').hidden = true;
byId('noteCopy').textContent = noteCopy[state.board];
renderRewards();
scheduleResize();
window.HyAppBridge.openRankNote(state.board);
}
function hideNote() {
byId('notePage').classList.remove('active');
byId('rankPage').classList.remove('hidden');
renderList();
scheduleResize();
}
function renderRewards() {
byId('noteCopy').textContent = noteCopy[state.board];
byId('rewardGrid').innerHTML = [1, 2, 3, 4]
.map(function () {
return '<div class="reward-card"><h3>Top1</h3><img class="reward" src="assets/reward.png" alt=""><div class="reward-name">Namename</div><div class="reward-value"><img class="coin" src="assets/coin.png" alt="">10000/7days</div></div>';
})
.join('');
scheduleResize();
}
byId('boardTabs').addEventListener('click', function (event) {
var target = event.target.closest('[data-board]');
if (!target || target.dataset.board === state.board) return;
state.board = target.dataset.board;
load(true);
});
byId('noteTabs').addEventListener('click', function (event) {
var target = event.target.closest('[data-board]');
if (!target) return;
state.board = target.dataset.board;
syncTabs();
renderRewards();
});
byId('periodTabs').addEventListener('click', function (event) {
var target = event.target.closest('[data-period]');
if (!target || target.dataset.period === state.period)
return;
state.period = target.dataset.period;
load(true);
});
byId('rewardPeriods').addEventListener(
'click',
function (event) {
var target = event.target.closest('[data-period]');
if (!target) return;
state.period = target.dataset.period;
syncTabs();
}
);
byId('podium').addEventListener('click', function (event) {
var target = event.target.closest('[data-index]');
if (target)
openItem(visibleItems()[Number(target.dataset.index)]);
});
byId('rankList').addEventListener('click', function (event) {
var target = event.target.closest('[data-rank]');
if (!target) return;
var item = visibleItems().find(function (candidate) {
return String(candidate.rank) === target.dataset.rank;
});
openItem(item);
});
byId('myRank').addEventListener('click', function () {
openItem(state.myRank);
});
byId('backButton').addEventListener('click', function () {
window.HyAppBridge.back();
});
byId('helpButton').addEventListener('click', showNote);
byId('noteBackButton').addEventListener('click', hideNote);
mountFixedRankBar();
window.addEventListener('scroll', function () {
if (
!state.total ||
state.items.length >= state.total ||
state.loading
)
return;
if (
window.innerHeight + window.scrollY >
document.body.scrollHeight - 180
) {
state.page += 1;
load(false);
}
});
window.addEventListener('resize', resizeApp);
window.addEventListener('orientationchange', scheduleResize);
render();
window.HyAppBridge.ready({ page: 'rank' });
load(true).finally(function () {
window.HyAppParams.loadUser().catch(function () {
return null;
});
});
})();
</script>
</body>
</html>