1292 lines
49 KiB
HTML
1292 lines
49 KiB
HTML
<!doctype html>
|
||
<html lang="en" translate="no">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="google" content="notranslate" />
|
||
<meta
|
||
name="viewport"
|
||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
|
||
/>
|
||
<title>Level</title>
|
||
<link rel="stylesheet" href="../common/theme.css" />
|
||
<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;
|
||
color: #fff;
|
||
font-family: Arial, Helvetica, sans-serif;
|
||
}
|
||
button {
|
||
border: 0;
|
||
padding: 0;
|
||
background: transparent;
|
||
color: inherit;
|
||
font: inherit;
|
||
cursor: pointer;
|
||
outline: none;
|
||
}
|
||
.app-viewport {
|
||
position: relative;
|
||
width: 375px;
|
||
height: 826px;
|
||
margin: 0 auto;
|
||
transform: scale(var(--app-scale));
|
||
transform-origin: top center;
|
||
}
|
||
.page {
|
||
position: relative;
|
||
width: 375px;
|
||
height: 826px;
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
overscroll-behavior: contain;
|
||
scrollbar-width: none;
|
||
background: #130a2b;
|
||
}
|
||
.page::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
.page-bg {
|
||
position: absolute;
|
||
left: -28px;
|
||
top: -297px;
|
||
width: 599px;
|
||
height: 1123px;
|
||
object-fit: cover;
|
||
opacity: 0.54;
|
||
pointer-events: none;
|
||
}
|
||
.sticky-header {
|
||
position: sticky;
|
||
left: 0;
|
||
top: 0;
|
||
z-index: 10;
|
||
width: 375px;
|
||
height: 98px;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(19, 10, 43, 0.96) 0%,
|
||
rgba(19, 10, 43, 0.72) 78%,
|
||
rgba(19, 10, 43, 0) 100%
|
||
);
|
||
backdrop-filter: blur(4px);
|
||
}
|
||
.nav {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
z-index: 5;
|
||
width: 375px;
|
||
height: 44px;
|
||
}
|
||
.back {
|
||
position: absolute;
|
||
left: 16px;
|
||
top: 10px;
|
||
width: 24px;
|
||
height: 24px;
|
||
}
|
||
.back::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 7px;
|
||
top: 5px;
|
||
width: 13px;
|
||
height: 13px;
|
||
border-left: 2px solid #fff;
|
||
border-bottom: 2px solid #fff;
|
||
transform: rotate(45deg);
|
||
}
|
||
.title {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 13px;
|
||
width: 375px;
|
||
font-size: 18px;
|
||
line-height: 18px;
|
||
font-weight: 500;
|
||
text-align: center;
|
||
text-transform: capitalize;
|
||
}
|
||
.tabs {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 58px;
|
||
z-index: 5;
|
||
width: 375px;
|
||
height: 30px;
|
||
}
|
||
.tab {
|
||
position: absolute;
|
||
top: 0;
|
||
width: 92px;
|
||
height: 30px;
|
||
color: rgba(255, 255, 255, 0.5);
|
||
font-size: 18px;
|
||
line-height: 18px;
|
||
font-weight: 700;
|
||
text-align: center;
|
||
text-transform: capitalize;
|
||
}
|
||
.tab[data-track='wealth'] {
|
||
left: 16px;
|
||
}
|
||
.tab[data-track='charm'] {
|
||
left: 142px;
|
||
}
|
||
.tab[data-track='game'] {
|
||
left: 266px;
|
||
}
|
||
.tab.active {
|
||
color: #fff;
|
||
opacity: 1;
|
||
}
|
||
.tab.active::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 24px;
|
||
width: 16px;
|
||
height: 3px;
|
||
margin-left: -8px;
|
||
border-radius: 31px;
|
||
background: #fff;
|
||
}
|
||
.level-card {
|
||
position: absolute;
|
||
left: 16px;
|
||
top: 111px;
|
||
z-index: 2;
|
||
width: 343px;
|
||
height: 111px;
|
||
overflow: hidden;
|
||
background: transparent;
|
||
}
|
||
.level-card-bg {
|
||
display: block;
|
||
width: 343px;
|
||
height: 111px;
|
||
object-fit: cover;
|
||
}
|
||
.card-decor {
|
||
position: absolute;
|
||
left: 24px;
|
||
top: 99px;
|
||
z-index: 3;
|
||
width: 164px;
|
||
height: 27px;
|
||
object-fit: cover;
|
||
}
|
||
.card-decor.right {
|
||
left: 188px;
|
||
transform: rotate(180deg) scaleY(-1);
|
||
}
|
||
.level-name {
|
||
position: absolute;
|
||
left: 40px;
|
||
top: 133px;
|
||
z-index: 4;
|
||
font-size: 16px;
|
||
line-height: 16px;
|
||
font-weight: 900;
|
||
text-transform: capitalize;
|
||
color: var(--title-fill, #fff);
|
||
text-shadow: 0 2px 0 var(--title-shadow);
|
||
}
|
||
.avatar-wrap {
|
||
position: absolute;
|
||
left: 259px;
|
||
top: 126px;
|
||
z-index: 4;
|
||
width: 80px;
|
||
height: 80px;
|
||
}
|
||
.avatar {
|
||
position: absolute;
|
||
left: 11px;
|
||
top: 11px;
|
||
width: 58px;
|
||
height: 58px;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
.avatar-frame {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 80px;
|
||
height: 80px;
|
||
object-fit: cover;
|
||
}
|
||
.progress {
|
||
position: absolute;
|
||
left: 40px;
|
||
top: 171px;
|
||
z-index: 4;
|
||
width: 182px;
|
||
height: 38px;
|
||
color: var(--progress-text);
|
||
}
|
||
.lv-current,
|
||
.lv-next {
|
||
position: absolute;
|
||
top: 7px;
|
||
font-size: 12px;
|
||
line-height: 12px;
|
||
text-transform: capitalize;
|
||
}
|
||
.lv-current {
|
||
left: 0;
|
||
}
|
||
.lv-next {
|
||
left: 159px;
|
||
}
|
||
.progress-percent {
|
||
position: absolute;
|
||
left: 67px;
|
||
top: 0;
|
||
width: 45px;
|
||
font-size: 8px;
|
||
line-height: 8px;
|
||
text-align: center;
|
||
}
|
||
.progress-track {
|
||
position: absolute;
|
||
left: 24px;
|
||
top: 12px;
|
||
width: 133px;
|
||
height: 4px;
|
||
border-radius: 3px;
|
||
background: var(--progress-track);
|
||
}
|
||
.progress-fill {
|
||
width: 0;
|
||
height: 4px;
|
||
border-radius: 3px;
|
||
background: var(--progress-fill);
|
||
}
|
||
.need {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 27px;
|
||
width: 220px;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
font-size: 8px;
|
||
line-height: 8px;
|
||
color: var(--need-text);
|
||
}
|
||
.level-table {
|
||
position: absolute;
|
||
left: 16px;
|
||
top: 238px;
|
||
z-index: 2;
|
||
width: 343px;
|
||
height: 318px;
|
||
overflow: hidden;
|
||
border-radius: 12px;
|
||
background: rgba(67, 0, 255, 0.11);
|
||
}
|
||
.table-head {
|
||
position: absolute;
|
||
top: 16px;
|
||
height: 16px;
|
||
font-size: 16px;
|
||
line-height: 16px;
|
||
font-weight: 500;
|
||
text-align: center;
|
||
text-transform: capitalize;
|
||
white-space: nowrap;
|
||
}
|
||
.head-range {
|
||
left: 19px;
|
||
width: 49px;
|
||
}
|
||
.head-icons {
|
||
left: 124px;
|
||
width: 42px;
|
||
}
|
||
.head-frame {
|
||
left: 226px;
|
||
width: 96px;
|
||
}
|
||
#tierRows {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: 44px;
|
||
bottom: 0;
|
||
z-index: 2;
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
overscroll-behavior: contain;
|
||
scrollbar-width: none;
|
||
}
|
||
#tierRows::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
.tier-row {
|
||
position: relative;
|
||
width: 343px;
|
||
height: 54px;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||
}
|
||
.tier-row:first-child {
|
||
border-top: 0;
|
||
}
|
||
.range {
|
||
position: absolute;
|
||
left: 8px;
|
||
top: 16px;
|
||
width: 72px;
|
||
font-size: 14px;
|
||
line-height: 14px;
|
||
text-align: center;
|
||
}
|
||
.badge-icon {
|
||
position: absolute;
|
||
left: 113px;
|
||
top: 11px;
|
||
width: 62px;
|
||
height: 24px;
|
||
object-fit: contain;
|
||
}
|
||
.frame-icon {
|
||
position: absolute;
|
||
left: 250px;
|
||
top: 4px;
|
||
width: 46px;
|
||
height: 46px;
|
||
object-fit: contain;
|
||
}
|
||
.tier-asset-empty {
|
||
position: absolute;
|
||
top: 18px;
|
||
color: rgba(255, 255, 255, 0.42);
|
||
font-size: 12px;
|
||
line-height: 12px;
|
||
}
|
||
.tier-asset-empty.badge {
|
||
left: 137px;
|
||
}
|
||
.tier-asset-empty.frame {
|
||
left: 267px;
|
||
}
|
||
.tier-empty {
|
||
display: flex;
|
||
height: 100%;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: rgba(255, 255, 255, 0.58);
|
||
font-size: 13px;
|
||
}
|
||
.upgrade-title {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 574px;
|
||
z-index: 3;
|
||
width: 375px;
|
||
color: var(--upgrade-title);
|
||
font-size: 20px;
|
||
line-height: 20px;
|
||
font-weight: 700;
|
||
text-align: center;
|
||
}
|
||
.upgrade-list {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 616px;
|
||
z-index: 3;
|
||
width: 375px;
|
||
height: 98px;
|
||
}
|
||
.upgrade-item {
|
||
position: absolute;
|
||
top: 0;
|
||
width: 119px;
|
||
text-align: center;
|
||
}
|
||
.upgrade-one {
|
||
left: 128px;
|
||
width: 120px;
|
||
}
|
||
.upgrade-three:nth-child(1) {
|
||
left: 1px;
|
||
}
|
||
.upgrade-three:nth-child(2) {
|
||
left: 128px;
|
||
}
|
||
.upgrade-three:nth-child(3) {
|
||
left: 255px;
|
||
}
|
||
.upgrade-icon {
|
||
display: block;
|
||
width: 54px;
|
||
height: 54px;
|
||
margin: 0 auto;
|
||
object-fit: contain;
|
||
}
|
||
.upgrade-name {
|
||
margin-top: 10px;
|
||
font-size: 14px;
|
||
line-height: 14px;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
}
|
||
.upgrade-exp {
|
||
margin-top: 8px;
|
||
font-size: 14px;
|
||
line-height: 14px;
|
||
white-space: nowrap;
|
||
}
|
||
.consume {
|
||
position: absolute;
|
||
left: 43px;
|
||
top: 736px;
|
||
z-index: 4;
|
||
width: 289px;
|
||
height: 50px;
|
||
overflow: hidden;
|
||
border: 1.5px solid #fff09b;
|
||
background: #2a1351;
|
||
color: #fff;
|
||
font-size: 18px;
|
||
line-height: 49px;
|
||
font-weight: 700;
|
||
text-align: center;
|
||
}
|
||
.consume-bg {
|
||
position: absolute;
|
||
inset: -26px -38px;
|
||
width: 363px;
|
||
height: 104px;
|
||
object-fit: cover;
|
||
opacity: 0.8;
|
||
mix-blend-mode: plus-lighter;
|
||
pointer-events: none;
|
||
}
|
||
.consume.gold .consume-bg {
|
||
inset: -38px -79px;
|
||
width: 447px;
|
||
height: 127px;
|
||
opacity: 1;
|
||
mix-blend-mode: normal;
|
||
}
|
||
.consume span {
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
.corner {
|
||
position: absolute;
|
||
z-index: 3;
|
||
width: 16px;
|
||
height: 16px;
|
||
object-fit: contain;
|
||
}
|
||
.corner.c1 {
|
||
left: 0;
|
||
top: 0;
|
||
}
|
||
.corner.c2 {
|
||
right: 0;
|
||
top: 0;
|
||
transform: rotate(90deg);
|
||
}
|
||
.corner.c3 {
|
||
right: 0;
|
||
bottom: 0;
|
||
transform: rotate(180deg);
|
||
}
|
||
.corner.c4 {
|
||
left: 0;
|
||
bottom: 0;
|
||
transform: rotate(270deg);
|
||
}
|
||
.hit {
|
||
position: absolute;
|
||
z-index: 7;
|
||
border-radius: 8px;
|
||
}
|
||
.hit-consume {
|
||
left: 43px;
|
||
top: 736px;
|
||
width: 289px;
|
||
height: 50px;
|
||
}
|
||
.loading-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
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-line {
|
||
fill: none;
|
||
stroke: #fff;
|
||
stroke-width: 3.4;
|
||
stroke-linecap: round;
|
||
stroke-linejoin: round;
|
||
}
|
||
.cat-eye {
|
||
transform-origin: center;
|
||
animation: cat-blink 1.6s ease-in-out infinite;
|
||
}
|
||
.cat-whisker {
|
||
opacity: 0.9;
|
||
animation: cat-whisker 1.1s ease-in-out infinite;
|
||
}
|
||
.toast {
|
||
position: fixed;
|
||
left: 50%;
|
||
bottom: calc(34px * var(--app-scale));
|
||
z-index: 60;
|
||
max-width: 300px;
|
||
transform: translateX(-50%);
|
||
padding: 8px 12px;
|
||
border-radius: 16px;
|
||
background: rgba(0, 0, 0, 0.62);
|
||
color: #fff;
|
||
font-size: 12px;
|
||
line-height: 16px;
|
||
text-align: center;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 0.18s ease;
|
||
}
|
||
.toast.show {
|
||
opacity: 1;
|
||
}
|
||
@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;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="appViewport" class="app-viewport">
|
||
<main id="page" class="page">
|
||
<img id="pageBg" class="page-bg" alt="" />
|
||
<header class="sticky-header">
|
||
<nav class="nav" aria-label="Level navigation">
|
||
<button
|
||
id="backButton"
|
||
class="back"
|
||
aria-label="Back"
|
||
></button>
|
||
<div class="title">Level</div>
|
||
</nav>
|
||
<div id="tabs" class="tabs">
|
||
<button class="tab" data-track="wealth">Wealth</button>
|
||
<button class="tab" data-track="charm">Charm</button>
|
||
<button class="tab" data-track="game">Game</button>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="level-card" aria-label="Current level">
|
||
<img id="cardBg" class="level-card-bg" alt="" />
|
||
</section>
|
||
<img id="decorLeft" class="card-decor" alt="" />
|
||
<img id="decorRight" class="card-decor right" alt="" />
|
||
<div id="levelName" class="level-name">Level 0</div>
|
||
<div class="avatar-wrap">
|
||
<img id="avatar" class="avatar" alt="" />
|
||
<img id="avatarFrame" class="avatar-frame" alt="" />
|
||
</div>
|
||
<div class="progress">
|
||
<div id="lvCurrent" class="lv-current">Lv.0</div>
|
||
<div id="progressPercent" class="progress-percent">0%</div>
|
||
<div class="progress-track">
|
||
<div id="progressFill" class="progress-fill"></div>
|
||
</div>
|
||
<div id="lvNext" class="lv-next">Lv.1</div>
|
||
<div id="needText" class="need">
|
||
You need 12,345,600 more experience points
|
||
</div>
|
||
</div>
|
||
|
||
<section class="level-table" aria-label="Level tiers">
|
||
<div class="table-head head-range">Range</div>
|
||
<div class="table-head head-icons">Icons</div>
|
||
<div class="table-head head-frame">Profile Frame</div>
|
||
<div id="tierRows"></div>
|
||
</section>
|
||
|
||
<div id="upgradeTitle" class="upgrade-title">
|
||
How to upgrade?
|
||
</div>
|
||
<div id="upgradeList" class="upgrade-list"></div>
|
||
<button
|
||
id="consumeButton"
|
||
class="consume"
|
||
aria-label="To consume"
|
||
></button>
|
||
<button
|
||
id="consumeHit"
|
||
class="hit hit-consume"
|
||
aria-label="To consume"
|
||
></button>
|
||
<div
|
||
id="loadingOverlay"
|
||
class="loading-overlay"
|
||
aria-hidden="true"
|
||
>
|
||
<div class="cat-loader" aria-label="Loading">
|
||
<svg viewBox="0 0 64 64" role="img" aria-hidden="true">
|
||
<path
|
||
class="cat-line"
|
||
d="M15 31 C15 19 24 14 32 14 C40 14 49 19 49 31 C49 43 42 50 32 50 C22 50 15 43 15 31 Z"
|
||
/>
|
||
<path class="cat-line" d="M20 18 L16 7 L27 14" />
|
||
<path class="cat-line" d="M44 18 L48 7 L37 14" />
|
||
<path class="cat-line cat-eye" d="M25 31 L25 32" />
|
||
<path class="cat-line cat-eye" d="M39 31 L39 32" />
|
||
<path class="cat-line" d="M32 35 L29 38 L35 38 Z" />
|
||
<path class="cat-line cat-whisker" d="M11 32 H23" />
|
||
<path
|
||
class="cat-line cat-whisker"
|
||
d="M10 40 L23 37"
|
||
/>
|
||
<path class="cat-line cat-whisker" d="M41 32 H53" />
|
||
<path
|
||
class="cat-line cat-whisker"
|
||
d="M41 37 L54 40"
|
||
/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
<div id="toast" class="toast"></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 A = 'assets/component/';
|
||
var tracks = {
|
||
charm: {
|
||
bg: A + 'bg-charm.webp',
|
||
cardBg: A + 'card-charm.png',
|
||
decor: A + 'decor-charm.png',
|
||
avatar: A + 'avatar-charm.webp',
|
||
frame: A + 'avatar-frame-charm.webp',
|
||
card: '#130828',
|
||
texture: "url('" + A + "button-purple.webp')",
|
||
textureOpacity: 0.26,
|
||
glow: 'linear-gradient(176deg, #462a78 36%, rgba(59,36,100,0) 63%)',
|
||
titleFill: '#ffffff',
|
||
titleShadow: '#be1e21',
|
||
titleGradient:
|
||
'linear-gradient(135deg, #fff 27%, #f4cbff 49%, #fff 72%)',
|
||
progress: '#fff',
|
||
progressTrack: 'rgba(255,255,255,.2)',
|
||
progressFill: '#fff',
|
||
need: 'rgba(255,255,255,.6)',
|
||
upgrade: '#f718e9',
|
||
button: 'purple',
|
||
upgrades: [
|
||
{
|
||
icon: A + 'upgrade-gift-charm.svg',
|
||
name: 'Receive a gift',
|
||
exp: '1 Coins = 1 EXP',
|
||
},
|
||
],
|
||
},
|
||
wealth: {
|
||
bg: A + 'bg-wealth.webp',
|
||
cardBg: A + 'card-wealth.png',
|
||
decor: A + 'decor-wealth.png',
|
||
avatar: A + 'avatar-wealth.webp',
|
||
frame: A + 'avatar-frame-wealth.webp',
|
||
card: '#ffa85c',
|
||
texture: "url('" + A + "button-gold.webp')",
|
||
textureOpacity: 0.41,
|
||
glow: 'linear-gradient(165deg, rgba(255,242,130,.62) 35%, rgba(255,242,130,0) 48%), linear-gradient(-12deg, rgba(255,242,130,.55) 26%, rgba(255,242,130,0) 28%)',
|
||
titleFill: '#fff7e8',
|
||
titleShadow: '#be561e',
|
||
titleGradient:
|
||
'linear-gradient(135deg, #fff 27%, #ffdfcb 49%, #fff 72%)',
|
||
progress: '#b7420c',
|
||
progressTrack: 'rgba(183,66,12,.2)',
|
||
progressFill: '#b7420c',
|
||
need: 'rgba(183,66,12,.6)',
|
||
upgrade: '#ffbe5f',
|
||
button: 'gold',
|
||
upgrades: [
|
||
{
|
||
icon: A + 'upgrade-gift-wealth.svg',
|
||
name: 'Send a Gift',
|
||
exp: '1 Coins = 1 EXP',
|
||
},
|
||
{
|
||
icon: A + 'upgrade-car-wealth.svg',
|
||
name: 'Buy a car',
|
||
exp: '1 Coins = 1 EXP',
|
||
},
|
||
{
|
||
icon: A + 'upgrade-avatar-wealth.svg',
|
||
name: 'Buy Avatar Frame',
|
||
exp: '1 Coins = 1 EXP',
|
||
},
|
||
],
|
||
},
|
||
game: {
|
||
bg: A + 'bg-game.webp',
|
||
cardBg: A + 'card-game.png',
|
||
decor: A + 'decor-game.png',
|
||
avatar: A + 'avatar-game.webp',
|
||
frame: A + 'avatar-frame-game.webp',
|
||
card: '#118624',
|
||
texture: "url('" + A + "button-purple.webp')",
|
||
textureOpacity: 0.38,
|
||
glow: 'linear-gradient(165deg, rgba(255,242,130,.34) 35%, rgba(255,242,130,0) 48%), linear-gradient(-12deg, rgba(255,242,130,.3) 26%, rgba(255,242,130,0) 28%)',
|
||
titleFill: '#f2fff2',
|
||
titleShadow: '#5ba626',
|
||
titleGradient:
|
||
'linear-gradient(135deg, #fff 27%, #cbffd7 49%, #fff 72%)',
|
||
progress: '#fff',
|
||
progressTrack: 'rgba(255,255,255,.2)',
|
||
progressFill: '#fff',
|
||
need: 'rgba(255,255,255,.6)',
|
||
upgrade: '#ffbe5f',
|
||
button: 'purple',
|
||
upgrades: [
|
||
{
|
||
icon: A + 'upgrade-game.svg',
|
||
name: 'Earned through gameplay',
|
||
exp: '1 Coins = 1 EXP',
|
||
},
|
||
],
|
||
},
|
||
};
|
||
var state = {
|
||
track:
|
||
new URLSearchParams(window.location.search).get(
|
||
'track'
|
||
) || 'charm',
|
||
loading: false,
|
||
details: {},
|
||
userAvatar: '',
|
||
toastTimer: 0,
|
||
};
|
||
|
||
function byId(id) {
|
||
return document.getElementById(id);
|
||
}
|
||
function track() {
|
||
return tracks[state.track] ? state.track : 'charm';
|
||
}
|
||
function fmt(n) {
|
||
return Number(n || 0).toLocaleString('en-US');
|
||
}
|
||
function ratio(d) {
|
||
var o = d && d.overview ? d.overview : {};
|
||
var cur = Number(o.current_level_required_value || 0);
|
||
var next = Number(o.next_level_required_value || 0);
|
||
var total = Number(o.total_value || 0);
|
||
if (!d || !d.overview) return 0;
|
||
if (next <= cur) return total >= cur && cur > 0 ? 1 : 0;
|
||
return Math.max(
|
||
0,
|
||
Math.min(1, (total - cur) / (next - cur))
|
||
);
|
||
}
|
||
function profileFromOverview(current) {
|
||
var user = current && current.user;
|
||
return (user && (user.profile || user.user || user)) || {};
|
||
}
|
||
function profileAvatar(profile) {
|
||
return String(
|
||
(profile &&
|
||
(profile.avatar ||
|
||
profile.user_avatar ||
|
||
profile.userAvatar ||
|
||
profile.avatar_url ||
|
||
profile.avatarUrl ||
|
||
profile.profile_avatar ||
|
||
profile.profileAvatar)) ||
|
||
''
|
||
);
|
||
}
|
||
function applyUserProfile(current) {
|
||
state.userAvatar = profileAvatar(
|
||
profileFromOverview(current)
|
||
);
|
||
render();
|
||
}
|
||
function setAvatarSource(src, fallback) {
|
||
var avatar = byId('avatar');
|
||
if (!avatar) return;
|
||
var fallbackSrc = fallback || '';
|
||
var nextSrc = src || fallbackSrc;
|
||
avatar.onerror = function () {
|
||
if (
|
||
fallbackSrc &&
|
||
avatar.getAttribute('src') !== fallbackSrc
|
||
) {
|
||
avatar.src = fallbackSrc;
|
||
}
|
||
};
|
||
if (avatar.getAttribute('src') !== nextSrc)
|
||
avatar.src = nextSrc;
|
||
}
|
||
function resizeApp() {
|
||
var scale = Math.min(window.innerWidth / 375, 1.25);
|
||
var viewportHeight = Math.max(
|
||
1,
|
||
window.innerHeight / scale
|
||
);
|
||
document.documentElement.style.setProperty(
|
||
'--app-scale',
|
||
String(scale)
|
||
);
|
||
// 页面自身承接滚动,标题和 Tab 才能在 WebView 可视区内真正 sticky;
|
||
// 不再让整个 826px 设计稿随 document 滚动而把顶部导航带走。
|
||
byId('appViewport').style.height = viewportHeight + 'px';
|
||
byId('page').style.height = viewportHeight + 'px';
|
||
document.body.style.minHeight = window.innerHeight + 'px';
|
||
}
|
||
function syncLoading() {
|
||
var overlay = byId('loadingOverlay');
|
||
overlay.classList.toggle('active', state.loading);
|
||
overlay.setAttribute(
|
||
'aria-hidden',
|
||
state.loading ? 'false' : 'true'
|
||
);
|
||
}
|
||
function showToast(message) {
|
||
var toast = byId('toast');
|
||
toast.textContent = message;
|
||
toast.classList.add('show');
|
||
window.clearTimeout(state.toastTimer);
|
||
state.toastTimer = window.setTimeout(function () {
|
||
toast.classList.remove('show');
|
||
}, 1800);
|
||
}
|
||
function readField(source, snakeKey, camelKey) {
|
||
if (!source) return null;
|
||
return source[snakeKey] != null
|
||
? source[snakeKey]
|
||
: source[camelKey];
|
||
}
|
||
function staticImageURL(resource) {
|
||
var candidates = [
|
||
readField(resource, 'preview_url', 'previewUrl'),
|
||
readField(resource, 'asset_url', 'assetUrl'),
|
||
readField(resource, 'animation_url', 'animationUrl'),
|
||
];
|
||
for (var i = 0; i < candidates.length; i += 1) {
|
||
var url = String(candidates[i] || '').trim();
|
||
// 等级表使用 img 展示静态封面;SVGA/PAG/MP4 主资产不能直接塞入 img,必须等待后端下发 preview_url。
|
||
if (
|
||
/^(https?:\/\/|\/|\.\.\/|\.\/)/i.test(url) &&
|
||
/\.(avif|gif|jpe?g|png|svg|webp)(\?|#|$)/i.test(url)
|
||
)
|
||
return url;
|
||
}
|
||
return '';
|
||
}
|
||
function escapeHTML(value) {
|
||
return String(value == null ? '' : value).replace(
|
||
/[&<>"']/g,
|
||
function (char) {
|
||
return {
|
||
'&': '&',
|
||
'<': '<',
|
||
'>': '>',
|
||
'"': '"',
|
||
"'": ''',
|
||
}[char];
|
||
}
|
||
);
|
||
}
|
||
function tierResource(tier, snakeKey, camelKey) {
|
||
return readField(tier, snakeKey, camelKey) || {};
|
||
}
|
||
function rowsHTML(detail) {
|
||
var tiers = ((detail && detail.tiers) || [])
|
||
.slice()
|
||
.sort(function (a, b) {
|
||
return (
|
||
Number(a.min_level || 0) -
|
||
Number(b.min_level || 0)
|
||
);
|
||
});
|
||
if (!tiers.length)
|
||
return '<div class="tier-empty">No level data</div>';
|
||
return tiers
|
||
.map(function (tier) {
|
||
var min = Number(tier.min_level || 0);
|
||
var max = Number(tier.max_level || 0);
|
||
var range =
|
||
min === max ? String(min) : min + '-' + max;
|
||
var badgeURL = staticImageURL(
|
||
tierResource(
|
||
tier,
|
||
'display_badge_resource',
|
||
'displayBadgeResource'
|
||
)
|
||
);
|
||
var frameURL = staticImageURL(
|
||
tierResource(
|
||
tier,
|
||
'display_avatar_frame_resource',
|
||
'displayAvatarFrameResource'
|
||
)
|
||
);
|
||
var badge = badgeURL
|
||
? '<img class="badge-icon" src="' +
|
||
escapeHTML(badgeURL) +
|
||
'" alt="">'
|
||
: '<span class="tier-asset-empty badge">—</span>';
|
||
var frame = frameURL
|
||
? '<img class="frame-icon" src="' +
|
||
escapeHTML(frameURL) +
|
||
'" alt="">'
|
||
: '<span class="tier-asset-empty frame">—</span>';
|
||
return (
|
||
'<div class="tier-row"><div class="range">' +
|
||
escapeHTML(range) +
|
||
'</div>' +
|
||
badge +
|
||
frame +
|
||
'</div>'
|
||
);
|
||
})
|
||
.join('');
|
||
}
|
||
function renderTierRows(detail) {
|
||
var root = byId('tierRows');
|
||
root.innerHTML = rowsHTML(detail);
|
||
root.querySelectorAll('img').forEach(function (image) {
|
||
// CDN 单个素材异常时只隐藏该格,不让一个坏 URL 破坏整张等级层表。
|
||
image.addEventListener(
|
||
'error',
|
||
function () {
|
||
image.style.visibility = 'hidden';
|
||
},
|
||
{ once: true }
|
||
);
|
||
});
|
||
}
|
||
function setOptionalImageSource(element, src) {
|
||
if (!element) return;
|
||
if (!src) {
|
||
element.removeAttribute('src');
|
||
element.style.display = 'none';
|
||
return;
|
||
}
|
||
element.style.display = 'block';
|
||
element.src = src;
|
||
}
|
||
function upgradesHTML(cfg) {
|
||
var cls =
|
||
cfg.upgrades.length === 3
|
||
? 'upgrade-three'
|
||
: 'upgrade-one';
|
||
return cfg.upgrades
|
||
.map(function (item) {
|
||
return (
|
||
'<div class="upgrade-item ' +
|
||
cls +
|
||
'"><img class="upgrade-icon" src="' +
|
||
item.icon +
|
||
'" alt=""><div class="upgrade-name">' +
|
||
item.name +
|
||
'</div><div class="upgrade-exp">' +
|
||
item.exp +
|
||
'</div></div>'
|
||
);
|
||
})
|
||
.join('');
|
||
}
|
||
function buttonHTML(cfg) {
|
||
var corner =
|
||
cfg.button === 'gold'
|
||
? A + 'corner-gold.png'
|
||
: A + 'corner-purple.png';
|
||
var texture =
|
||
cfg.button === 'gold'
|
||
? A + 'button-gold.webp'
|
||
: A + 'button-purple.webp';
|
||
return (
|
||
'<img class="consume-bg" src="' +
|
||
texture +
|
||
'" alt=""><img class="corner c1" src="' +
|
||
corner +
|
||
'" alt=""><img class="corner c2" src="' +
|
||
corner +
|
||
'" alt=""><img class="corner c3" src="' +
|
||
corner +
|
||
'" alt=""><img class="corner c4" src="' +
|
||
corner +
|
||
'" alt=""><span>To consume</span>'
|
||
);
|
||
}
|
||
function applyData() {
|
||
var d = state.details[state.track];
|
||
var o = d && d.overview ? d.overview : {};
|
||
var level = Number(o.level || 0);
|
||
var next = Number(o.next_level || level + 1);
|
||
var percent = Math.round(ratio(d) * 100);
|
||
byId('levelName').textContent = 'Level ' + level;
|
||
byId('lvCurrent').textContent = 'Lv.' + level;
|
||
byId('lvNext').textContent = 'Lv.' + next;
|
||
byId('progressPercent').textContent = percent + '%';
|
||
byId('progressFill').style.width = percent + '%';
|
||
var curRequired = Number(
|
||
o.current_level_required_value || 0
|
||
);
|
||
var nextRequired = Number(o.next_level_required_value || 0);
|
||
var total = Number(o.total_value || 0);
|
||
if (d && d.overview && nextRequired <= curRequired) {
|
||
byId('needText').textContent = 'Max level reached';
|
||
return;
|
||
}
|
||
var need = Math.max(0, nextRequired - total);
|
||
byId('needText').textContent =
|
||
'You need ' + fmt(need) + ' more experience points';
|
||
}
|
||
function render() {
|
||
state.track = track();
|
||
var cfg = tracks[state.track];
|
||
byId('pageBg').src = cfg.bg;
|
||
byId('cardBg').src = cfg.cardBg;
|
||
byId('decorLeft').src = cfg.decor;
|
||
byId('decorRight').src = cfg.decor;
|
||
setAvatarSource(state.userAvatar, cfg.avatar);
|
||
var detail = state.details[state.track];
|
||
var overview = detail && detail.overview;
|
||
setOptionalImageSource(
|
||
byId('avatarFrame'),
|
||
staticImageURL(
|
||
readField(
|
||
overview,
|
||
'display_avatar_frame_resource',
|
||
'displayAvatarFrameResource'
|
||
)
|
||
)
|
||
);
|
||
renderTierRows(detail);
|
||
byId('upgradeList').innerHTML = upgradesHTML(cfg);
|
||
byId('consumeButton').className =
|
||
'consume ' + (cfg.button === 'gold' ? 'gold' : '');
|
||
byId('consumeButton').innerHTML = buttonHTML(cfg);
|
||
document.documentElement.style.setProperty(
|
||
'--card-texture',
|
||
cfg.texture
|
||
);
|
||
document.documentElement.style.setProperty(
|
||
'--card-texture-opacity',
|
||
cfg.textureOpacity
|
||
);
|
||
document.documentElement.style.setProperty(
|
||
'--card-glow',
|
||
cfg.glow
|
||
);
|
||
document.documentElement.style.setProperty(
|
||
'--title-shadow',
|
||
cfg.titleShadow
|
||
);
|
||
document.documentElement.style.setProperty(
|
||
'--title-fill',
|
||
cfg.titleFill
|
||
);
|
||
document.documentElement.style.setProperty(
|
||
'--title-gradient',
|
||
cfg.titleGradient
|
||
);
|
||
document.documentElement.style.setProperty(
|
||
'--progress-text',
|
||
cfg.progress
|
||
);
|
||
document.documentElement.style.setProperty(
|
||
'--progress-track',
|
||
cfg.progressTrack
|
||
);
|
||
document.documentElement.style.setProperty(
|
||
'--progress-fill',
|
||
cfg.progressFill
|
||
);
|
||
document.documentElement.style.setProperty(
|
||
'--need-text',
|
||
cfg.need
|
||
);
|
||
document.documentElement.style.setProperty(
|
||
'--upgrade-title',
|
||
cfg.upgrade
|
||
);
|
||
document.querySelectorAll('.tab').forEach(function (node) {
|
||
node.classList.toggle(
|
||
'active',
|
||
node.dataset.track === state.track
|
||
);
|
||
});
|
||
applyData();
|
||
syncLoading();
|
||
resizeApp();
|
||
}
|
||
function loadOverview() {
|
||
if (
|
||
!window.HyAppAPI ||
|
||
!window.HyAppAPI.level ||
|
||
!window.HyAppAPI.getAccessToken()
|
||
)
|
||
return Promise.resolve(null);
|
||
return window.HyAppAPI.level
|
||
.overview()
|
||
.then(function (data) {
|
||
((data && data.tracks) || []).forEach(
|
||
function (item) {
|
||
if (
|
||
!item ||
|
||
!item.track ||
|
||
!tracks[item.track]
|
||
)
|
||
return;
|
||
if (!state.details[item.track])
|
||
state.details[item.track] = {};
|
||
state.details[item.track].overview = item;
|
||
}
|
||
);
|
||
render();
|
||
return data;
|
||
})
|
||
.catch(function () {
|
||
return null;
|
||
});
|
||
}
|
||
function loadTrack(name) {
|
||
state.track = tracks[name] ? name : 'charm';
|
||
if (window.history && window.history.replaceState) {
|
||
var url = new URL(window.location.href);
|
||
url.searchParams.set('track', state.track);
|
||
window.history.replaceState(null, '', url.toString());
|
||
}
|
||
render();
|
||
if (
|
||
!window.HyAppAPI ||
|
||
!window.HyAppAPI.level ||
|
||
!window.HyAppAPI.getAccessToken()
|
||
)
|
||
return Promise.resolve(null);
|
||
state.loading = true;
|
||
render();
|
||
return window.HyAppAPI.level
|
||
.track(state.track)
|
||
.then(function (data) {
|
||
state.details[state.track] = data;
|
||
})
|
||
.catch(function (error) {
|
||
showToast(
|
||
(error && error.message) || 'Network error'
|
||
);
|
||
})
|
||
.finally(function () {
|
||
state.loading = false;
|
||
render();
|
||
});
|
||
}
|
||
function loadUserProfile() {
|
||
if (!window.HyAppParams || !window.HyAppParams.loadUser)
|
||
return Promise.resolve(null);
|
||
return window.HyAppParams.loadUser()
|
||
.then(function (current) {
|
||
applyUserProfile(current);
|
||
return current;
|
||
})
|
||
.catch(function () {
|
||
return null;
|
||
});
|
||
}
|
||
byId('tabs').addEventListener('click', function (event) {
|
||
var target = event.target.closest('[data-track]');
|
||
if (!target) return;
|
||
loadTrack(target.dataset.track);
|
||
});
|
||
byId('backButton').addEventListener('click', function () {
|
||
window.HyAppBridge.back();
|
||
});
|
||
byId('consumeHit').addEventListener('click', function () {
|
||
// Level 页只发稳定的 recharge 动作,Flutter 统一负责打开原生钱包充值页。
|
||
window.HyAppBridge.post('recharge', {
|
||
source: 'level',
|
||
track: state.track,
|
||
});
|
||
});
|
||
window.addEventListener('resize', resizeApp);
|
||
window.addEventListener('orientationchange', function () {
|
||
window.requestAnimationFrame(resizeApp);
|
||
});
|
||
render();
|
||
window.HyAppBridge.ready({ page: 'level' });
|
||
loadTrack(state.track).finally(function () {
|
||
loadOverview();
|
||
loadUserProfile();
|
||
});
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|