2129 lines
81 KiB
HTML
2129 lines
81 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>VIP</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;
|
|
color: #fff;
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
}
|
|
body {
|
|
display: block;
|
|
}
|
|
button {
|
|
border: 0;
|
|
padding: 0;
|
|
background: transparent;
|
|
color: inherit;
|
|
font: inherit;
|
|
cursor: pointer;
|
|
outline: none;
|
|
}
|
|
.app-viewport {
|
|
position: relative;
|
|
width: 375px;
|
|
height: 766px;
|
|
margin: 0 auto;
|
|
transform: scale(var(--app-scale));
|
|
transform-origin: top center;
|
|
}
|
|
.page {
|
|
position: relative;
|
|
width: 375px;
|
|
height: 766px;
|
|
overflow: hidden;
|
|
touch-action: pan-y;
|
|
background: #130a2b;
|
|
}
|
|
.bg {
|
|
position: absolute;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
overflow: hidden;
|
|
}
|
|
.bg-starry {
|
|
position: absolute;
|
|
left: -126px;
|
|
top: -76px;
|
|
width: 631px;
|
|
height: 350px;
|
|
object-fit: cover;
|
|
opacity: 0.3;
|
|
transform: rotate(90deg);
|
|
}
|
|
.bg-meteor {
|
|
position: absolute;
|
|
left: -16px;
|
|
top: -113px;
|
|
width: 371px;
|
|
height: 371px;
|
|
object-fit: cover;
|
|
transform: rotate(10.98deg);
|
|
}
|
|
.bg-palace {
|
|
position: absolute;
|
|
left: -15px;
|
|
top: -67px;
|
|
width: 406px;
|
|
height: 389px;
|
|
object-fit: cover;
|
|
}
|
|
.bg-ornaments {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
width: 375px;
|
|
height: 244px;
|
|
object-fit: cover;
|
|
}
|
|
.bg::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
top: 244px;
|
|
height: 566px;
|
|
background: #11071f;
|
|
}
|
|
.nav {
|
|
position: fixed;
|
|
left: 50%;
|
|
top: 0;
|
|
z-index: 70;
|
|
width: 375px;
|
|
height: 44px;
|
|
transform: translateX(-50%) scale(var(--app-scale));
|
|
transform-origin: top center;
|
|
}
|
|
.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;
|
|
border-radius: 1px;
|
|
transform: rotate(45deg);
|
|
}
|
|
.title-vip {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 10px;
|
|
width: 50px;
|
|
height: 22px;
|
|
color: #5f270f;
|
|
font-family: Georgia, 'Times New Roman', serif;
|
|
font-size: 23px;
|
|
line-height: 22px;
|
|
font-style: italic;
|
|
font-weight: 900;
|
|
text-align: center;
|
|
letter-spacing: 0;
|
|
transform: translateX(-50%) skewX(-7deg);
|
|
-webkit-text-stroke: 1px #55250f;
|
|
text-shadow:
|
|
0 1px 0 #cb7628,
|
|
0 2px 1px rgba(43, 16, 5, 0.75),
|
|
0 0 4px rgba(255, 199, 83, 0.48);
|
|
}
|
|
.title-vip::before {
|
|
content: attr(aria-label);
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(180deg, #fffbe0 0%, #ffe27a 30%, #d87525 72%, #6e2b12 100%);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
-webkit-text-stroke: 0.35px #fff0a8;
|
|
}
|
|
.hero-badge {
|
|
position: absolute;
|
|
z-index: 2;
|
|
left: 104px;
|
|
top: 55px;
|
|
width: 167px;
|
|
height: 167px;
|
|
}
|
|
.hero-badge-image {
|
|
display: block;
|
|
width: 167px;
|
|
height: 167px;
|
|
object-fit: contain;
|
|
}
|
|
.hero-badge-image.is-dynamic-hidden {
|
|
visibility: hidden;
|
|
}
|
|
.hero-badge-animation {
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 4;
|
|
display: none;
|
|
pointer-events: none;
|
|
}
|
|
.hero-badge-animation.is-active {
|
|
display: block;
|
|
}
|
|
.hero-badge-animation video,
|
|
.hero-badge-animation canvas {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
.vip-switch {
|
|
position: absolute;
|
|
z-index: 12;
|
|
top: 116px;
|
|
width: 36px;
|
|
height: 64px;
|
|
}
|
|
.vip-switch::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 13px;
|
|
top: 23px;
|
|
width: 13px;
|
|
height: 13px;
|
|
border-left: 3px solid #fff;
|
|
border-bottom: 3px solid #fff;
|
|
border-radius: 2px;
|
|
filter: drop-shadow(0 0 4px rgba(230, 151, 255, 0.72));
|
|
transform: rotate(45deg);
|
|
}
|
|
.vip-switch-next::before {
|
|
left: 8px;
|
|
transform: rotate(225deg);
|
|
}
|
|
.vip-switch-prev {
|
|
left: 16px;
|
|
}
|
|
.vip-switch-next {
|
|
right: 16px;
|
|
}
|
|
.vip-switch[disabled] {
|
|
opacity: 0.36;
|
|
cursor: default;
|
|
}
|
|
.vip-switch.is-hidden {
|
|
display: none;
|
|
}
|
|
.vip-level-stamp {
|
|
position: absolute;
|
|
left: 50%;
|
|
z-index: 3;
|
|
transform: translateX(-50%);
|
|
color: #fff5af;
|
|
font-family: Georgia, 'Times New Roman', serif;
|
|
font-weight: 900;
|
|
font-style: italic;
|
|
letter-spacing: 0;
|
|
text-align: center;
|
|
text-shadow:
|
|
0 1px 0 #7a2b14,
|
|
0 0 4px rgba(117, 31, 255, 0.9),
|
|
0 0 6px rgba(255, 226, 92, 0.58);
|
|
-webkit-text-stroke: 0.7px #7a2b14;
|
|
}
|
|
.vip-level-stamp.is-hidden {
|
|
display: none;
|
|
}
|
|
.hero-vip-stamp {
|
|
bottom: 40px;
|
|
min-width: 42px;
|
|
padding: 0 3px;
|
|
font-size: 17px;
|
|
line-height: 18px;
|
|
}
|
|
.section-title {
|
|
position: absolute;
|
|
z-index: 3;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
height: 18px;
|
|
}
|
|
.section-title img {
|
|
display: none;
|
|
}
|
|
.section-title::before,
|
|
.section-title::after {
|
|
content: '';
|
|
width: 74px;
|
|
height: 16px;
|
|
flex: 1 1 74px;
|
|
max-width: 96px;
|
|
background:
|
|
radial-gradient(circle at 100% 50%, #f1c7ff 0 1.6px, transparent 2px),
|
|
radial-gradient(ellipse at 86% 50%, transparent 0 4px, #d997ff 4.4px 5.3px, transparent 5.7px),
|
|
radial-gradient(ellipse at 73% 50%, transparent 0 5px, rgba(217, 151, 255, 0.95) 5.3px 6.2px, transparent 6.6px),
|
|
linear-gradient(90deg, transparent 0, rgba(217, 151, 255, 0.12) 18%, rgba(217, 151, 255, 0.95) 72%, transparent 100%) center / 100% 1px no-repeat;
|
|
opacity: 1;
|
|
}
|
|
.section-title::after {
|
|
transform: scaleX(-1);
|
|
}
|
|
.section-title span {
|
|
display: block;
|
|
color: #fff;
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
font-size: 17px;
|
|
line-height: 18px;
|
|
font-weight: 900;
|
|
white-space: nowrap;
|
|
text-align: center;
|
|
text-shadow:
|
|
0 1px 0 #a3229d,
|
|
0 2px 0 #b31942,
|
|
1px 0 0 #b94cff,
|
|
-1px 0 0 #b94cff,
|
|
0 -1px 0 #b94cff,
|
|
0 0 5px rgba(233, 116, 255, 0.72);
|
|
-webkit-text-stroke: 0.35px #f0b5ff;
|
|
-webkit-text-fill-color: #fff;
|
|
}
|
|
.decoration-title {
|
|
left: 18px;
|
|
top: 246px;
|
|
width: 339px;
|
|
}
|
|
.vip-title {
|
|
left: 68px;
|
|
top: 522px;
|
|
width: 239px;
|
|
}
|
|
.decor-card {
|
|
position: absolute;
|
|
z-index: 3;
|
|
color: #fff;
|
|
text-align: center;
|
|
--frame-line: #d9a6ff;
|
|
--frame-corner: #ffd9ff;
|
|
border: 1px solid var(--frame-line);
|
|
background:
|
|
radial-gradient(circle at 50% 26%, rgba(98, 52, 132, 0.22), transparent 46%),
|
|
rgba(17, 6, 30, 0.62);
|
|
box-shadow:
|
|
inset 0 0 0 1px rgba(255, 255, 255, 0.06),
|
|
inset 0 0 18px rgba(90, 35, 129, 0.2);
|
|
}
|
|
.decor-card.is-previewable {
|
|
cursor: pointer;
|
|
}
|
|
.decor-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: -2px;
|
|
pointer-events: none;
|
|
background:
|
|
linear-gradient(var(--frame-corner), var(--frame-corner)) left top / 11px 1px no-repeat,
|
|
linear-gradient(var(--frame-corner), var(--frame-corner)) left top / 1px 11px no-repeat,
|
|
linear-gradient(var(--frame-corner), var(--frame-corner)) right top / 11px 1px no-repeat,
|
|
linear-gradient(var(--frame-corner), var(--frame-corner)) right top / 1px 11px no-repeat,
|
|
linear-gradient(var(--frame-corner), var(--frame-corner)) left bottom / 11px 1px no-repeat,
|
|
linear-gradient(var(--frame-corner), var(--frame-corner)) left bottom / 1px 11px no-repeat,
|
|
linear-gradient(var(--frame-corner), var(--frame-corner)) right bottom / 11px 1px no-repeat,
|
|
linear-gradient(var(--frame-corner), var(--frame-corner)) right bottom / 1px 11px no-repeat;
|
|
}
|
|
.decor-card .frame {
|
|
display: none;
|
|
}
|
|
.decor-card .item {
|
|
position: absolute;
|
|
object-fit: contain;
|
|
pointer-events: none;
|
|
}
|
|
.hero-badge-image:not([src]),
|
|
.hero-badge-image[src=''],
|
|
.decor-card .item:not([src]),
|
|
.decor-card .item[src=''] {
|
|
display: none;
|
|
}
|
|
.card-vip-stamp {
|
|
top: 52px;
|
|
min-width: 26px;
|
|
font-size: 9px;
|
|
line-height: 10px;
|
|
}
|
|
.decor-card .label {
|
|
position: absolute;
|
|
left: 4px;
|
|
right: 4px;
|
|
bottom: 13px;
|
|
font-size: 13px;
|
|
line-height: 13px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.card-badge {
|
|
left: 16px;
|
|
top: 278px;
|
|
width: 110px;
|
|
height: 110px;
|
|
}
|
|
.card-badge .item {
|
|
left: 25px;
|
|
top: 16px;
|
|
width: 60px;
|
|
height: 60px;
|
|
}
|
|
.card-frame {
|
|
left: 133px;
|
|
top: 278px;
|
|
width: 110px;
|
|
height: 110px;
|
|
}
|
|
.card-frame .item {
|
|
left: 25px;
|
|
top: 16px;
|
|
width: 60px;
|
|
height: 60px;
|
|
}
|
|
.card-mount {
|
|
left: 250px;
|
|
top: 278px;
|
|
width: 110px;
|
|
height: 110px;
|
|
}
|
|
.card-mount .item {
|
|
left: 25px;
|
|
top: 16px;
|
|
width: 60px;
|
|
height: 60px;
|
|
}
|
|
.card-mic {
|
|
left: 16px;
|
|
top: 396px;
|
|
width: 110px;
|
|
height: 110px;
|
|
}
|
|
.card-mic .item {
|
|
left: 25px;
|
|
top: 16px;
|
|
width: 60px;
|
|
height: 60px;
|
|
}
|
|
.card-entry {
|
|
left: 133px;
|
|
top: 396px;
|
|
width: 227px;
|
|
height: 110px;
|
|
}
|
|
.card-entry .item {
|
|
left: 11px;
|
|
top: 31px;
|
|
width: 205px;
|
|
height: 33px;
|
|
object-fit: contain;
|
|
}
|
|
.privileges {
|
|
position: absolute;
|
|
z-index: 3;
|
|
left: 0;
|
|
top: 566px;
|
|
width: 375px;
|
|
height: 200px;
|
|
}
|
|
.privilege {
|
|
position: absolute;
|
|
width: 90px;
|
|
min-height: 98px;
|
|
text-align: center;
|
|
color: #fff;
|
|
}
|
|
.privilege .circle {
|
|
position: relative;
|
|
width: 56px;
|
|
height: 56px;
|
|
margin: 0 auto;
|
|
border-radius: 50%;
|
|
background: #3d2766;
|
|
}
|
|
.privilege .circle img {
|
|
display: none;
|
|
}
|
|
.privilege.profile .circle::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 15px;
|
|
top: 16px;
|
|
width: 25px;
|
|
height: 22px;
|
|
border: 3px solid #fff;
|
|
border-radius: 3px;
|
|
background:
|
|
radial-gradient(circle at 74% 28%, #fff 0 2px, transparent 2.5px),
|
|
linear-gradient(135deg, transparent 48%, #fff 49% 55%, transparent 56%);
|
|
}
|
|
.privilege.nickname .circle::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 14px;
|
|
top: 17px;
|
|
width: 28px;
|
|
height: 20px;
|
|
border-radius: 2px;
|
|
background:
|
|
radial-gradient(circle at 25% 50%, #3d2766 0 3px, transparent 3.4px),
|
|
linear-gradient(#3d2766, #3d2766) 15px 6px / 11px 2px no-repeat,
|
|
linear-gradient(#3d2766, #3d2766) 15px 12px / 11px 2px no-repeat,
|
|
#fff;
|
|
}
|
|
.privilege.room .circle::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 17px;
|
|
top: 24px;
|
|
width: 22px;
|
|
height: 18px;
|
|
border-radius: 2px;
|
|
background: #fff;
|
|
}
|
|
.privilege.room .circle::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 15px;
|
|
top: 17px;
|
|
width: 24px;
|
|
height: 24px;
|
|
border-left: 5px solid #fff;
|
|
border-top: 5px solid #fff;
|
|
transform: rotate(45deg);
|
|
}
|
|
.privilege .id-mark {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 15px;
|
|
width: 56px;
|
|
color: #fff;
|
|
font-size: 26px;
|
|
line-height: 26px;
|
|
font-weight: 800;
|
|
letter-spacing: 0;
|
|
}
|
|
.privilege .label {
|
|
margin-top: 4px;
|
|
font-size: 14px;
|
|
line-height: 16px;
|
|
text-transform: capitalize;
|
|
}
|
|
.privilege.profile {
|
|
left: 11px;
|
|
top: 0;
|
|
}
|
|
.privilege.nickname {
|
|
left: 143px;
|
|
top: 0;
|
|
}
|
|
.privilege.room {
|
|
left: 275px;
|
|
top: 0;
|
|
}
|
|
.privilege.id {
|
|
left: 11px;
|
|
top: 108px;
|
|
}
|
|
.purchase-bar {
|
|
position: fixed;
|
|
z-index: 50;
|
|
left: 50%;
|
|
bottom: 0;
|
|
width: 375px;
|
|
height: 107px;
|
|
overflow: hidden;
|
|
transform: translateX(-50%) scale(var(--app-scale));
|
|
transform-origin: bottom center;
|
|
background: linear-gradient(180deg, #3e249d 0%, #20113a 69%);
|
|
}
|
|
.purchase-bar::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
opacity: 0.23;
|
|
background: url('assets/bottom-texture.png') center/100% 100%
|
|
no-repeat;
|
|
mix-blend-mode: plus-lighter;
|
|
}
|
|
.price {
|
|
position: absolute;
|
|
left: 24px;
|
|
top: 31px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 3px;
|
|
max-width: 184px;
|
|
color: #ffe4bc;
|
|
font-size: 16px;
|
|
line-height: 18px;
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
}
|
|
.price img {
|
|
width: 22px;
|
|
height: 22px;
|
|
object-fit: contain;
|
|
}
|
|
.activate {
|
|
position: absolute;
|
|
left: 211px;
|
|
top: 17px;
|
|
width: 140px;
|
|
height: 50px;
|
|
border: 1px solid #fff09b;
|
|
color: #fff;
|
|
font-size: 18px;
|
|
line-height: 50px;
|
|
font-weight: 700;
|
|
text-align: center;
|
|
background: #2a1351;
|
|
overflow: hidden;
|
|
}
|
|
.activate::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: -8px -20px;
|
|
opacity: 0.8;
|
|
background: url('assets/button-texture.png') center/100% 100%
|
|
no-repeat;
|
|
}
|
|
.activate::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(
|
|
176deg,
|
|
#462a78 36%,
|
|
rgba(59, 36, 100, 0) 63%
|
|
);
|
|
mix-blend-mode: plus-lighter;
|
|
}
|
|
.activate span {
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
.activate .corner {
|
|
position: absolute;
|
|
z-index: 3;
|
|
width: 16px;
|
|
height: 16px;
|
|
object-fit: contain;
|
|
}
|
|
.activate .c1 {
|
|
left: 0;
|
|
top: 0;
|
|
}
|
|
.activate .c2 {
|
|
right: 0;
|
|
top: 0;
|
|
transform: rotate(90deg);
|
|
}
|
|
.activate .c3 {
|
|
right: 0;
|
|
bottom: 0;
|
|
transform: rotate(180deg);
|
|
}
|
|
.activate .c4 {
|
|
left: 0;
|
|
bottom: 0;
|
|
transform: rotate(270deg);
|
|
}
|
|
.activate[disabled] {
|
|
opacity: 0.72;
|
|
cursor: default;
|
|
}
|
|
.home-indicator {
|
|
position: absolute;
|
|
left: 120px;
|
|
bottom: 9px;
|
|
z-index: 6;
|
|
width: 134px;
|
|
height: 5px;
|
|
border-radius: 100px;
|
|
background: #fff;
|
|
}
|
|
.loading-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 30;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(19, 10, 43, 0.28);
|
|
backdrop-filter: blur(1px);
|
|
}
|
|
.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;
|
|
}
|
|
.toast {
|
|
position: fixed;
|
|
left: 50%;
|
|
bottom: calc(132px * var(--app-scale));
|
|
z-index: 140;
|
|
min-width: 168px;
|
|
max-width: 328px;
|
|
transform: translateX(-50%);
|
|
padding: 13px 22px;
|
|
border-radius: 24px;
|
|
background: rgba(0, 0, 0, 0.82);
|
|
color: #fff;
|
|
font-size: 15px;
|
|
line-height: 20px;
|
|
font-weight: 700;
|
|
text-align: center;
|
|
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.36);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.18s ease;
|
|
}
|
|
.toast.show {
|
|
opacity: 1;
|
|
}
|
|
.effect-preview-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 120;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: calc(env(safe-area-inset-top) + 28px) 18px
|
|
calc(env(safe-area-inset-bottom) + 28px);
|
|
background: rgba(0, 0, 0, 0.9);
|
|
}
|
|
.effect-preview-overlay.is-open {
|
|
display: flex;
|
|
}
|
|
.effect-preview-close {
|
|
position: absolute;
|
|
right: 16px;
|
|
top: calc(env(safe-area-inset-top) + 16px);
|
|
z-index: 3;
|
|
width: 40px;
|
|
height: 40px;
|
|
}
|
|
.effect-preview-close::before,
|
|
.effect-preview-close::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 10px;
|
|
top: 19px;
|
|
width: 20px;
|
|
height: 2px;
|
|
border-radius: 2px;
|
|
background: #fff;
|
|
box-shadow: 0 0 8px rgba(230, 151, 255, 0.64);
|
|
}
|
|
.effect-preview-close::before {
|
|
transform: rotate(45deg);
|
|
}
|
|
.effect-preview-close::after {
|
|
transform: rotate(-45deg);
|
|
}
|
|
.effect-preview-stage {
|
|
position: relative;
|
|
width: var(--preview-width, calc(100vw - 64px));
|
|
height: var(--preview-height, calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 156px));
|
|
max-width: calc(100vw - 64px);
|
|
max-height: calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 156px);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: visible;
|
|
}
|
|
.effect-preview-image,
|
|
.effect-preview-animation {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
}
|
|
.effect-preview-image {
|
|
object-fit: contain;
|
|
object-position: center center;
|
|
}
|
|
.effect-preview-image.is-dynamic-hidden {
|
|
visibility: hidden;
|
|
}
|
|
.effect-preview-animation {
|
|
display: none;
|
|
pointer-events: none;
|
|
}
|
|
.effect-preview-animation.is-active {
|
|
display: block;
|
|
}
|
|
.effect-preview-animation video,
|
|
.effect-preview-animation canvas {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
object-position: center center;
|
|
}
|
|
@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 class="page">
|
|
<div class="bg" aria-hidden="true">
|
|
<img class="bg-starry" src="assets/bg-starry.png" alt="" />
|
|
<img class="bg-meteor" src="assets/bg-meteor.png" alt="" />
|
|
<img class="bg-palace" src="assets/bg-palace.png" alt="" />
|
|
<img
|
|
class="bg-ornaments"
|
|
src="assets/top-ornaments.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
|
|
<div class="hero-badge" aria-hidden="true">
|
|
<img id="heroBadgeImage" class="hero-badge-image" alt="" />
|
|
<div
|
|
id="heroBadgeAnimation"
|
|
class="hero-badge-animation"
|
|
></div>
|
|
<div id="heroVipName" class="vip-level-stamp hero-vip-stamp">
|
|
VIP
|
|
</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
id="vipPrevButton"
|
|
class="vip-switch vip-switch-prev"
|
|
aria-label="Previous VIP"
|
|
></button>
|
|
<button
|
|
type="button"
|
|
id="vipNextButton"
|
|
class="vip-switch vip-switch-next"
|
|
aria-label="Next VIP"
|
|
></button>
|
|
|
|
<div class="section-title decoration-title">
|
|
<img src="assets/divider.png" alt="" />
|
|
<span>Decoration Privileges</span>
|
|
<img class="right" src="assets/divider.png" alt="" />
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
class="decor-card card-badge"
|
|
data-vip-slot="badge"
|
|
>
|
|
<img
|
|
class="frame"
|
|
src="assets/privilege-frame.png"
|
|
alt=""
|
|
/>
|
|
<img id="badgeItem" class="item" alt="" />
|
|
<span
|
|
id="badgeVipName"
|
|
class="vip-level-stamp card-vip-stamp"
|
|
>
|
|
VIP
|
|
</span>
|
|
<span id="badgeLabel" class="label">SVIP Badge</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="decor-card card-frame"
|
|
data-vip-slot="avatar_frame"
|
|
>
|
|
<img
|
|
class="frame"
|
|
src="assets/privilege-frame.png"
|
|
alt=""
|
|
/>
|
|
<img id="frameItem" class="item" alt="" />
|
|
<span
|
|
id="frameVipName"
|
|
class="vip-level-stamp card-vip-stamp"
|
|
>
|
|
VIP
|
|
</span>
|
|
<span id="frameLabel" class="label">Frame</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="decor-card card-mount"
|
|
data-vip-slot="vehicle"
|
|
>
|
|
<img
|
|
class="frame"
|
|
src="assets/privilege-frame.png"
|
|
alt=""
|
|
/>
|
|
<img id="mountItem" class="item" alt="" />
|
|
<span id="mountLabel" class="label">Mount</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="decor-card card-mic"
|
|
data-vip-slot="mic_seat_animation"
|
|
>
|
|
<img
|
|
class="frame"
|
|
src="assets/privilege-frame.png"
|
|
alt=""
|
|
/>
|
|
<img id="micItem" class="item" alt="" />
|
|
<span id="micLabel" class="label">Mic Animation</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="decor-card card-entry"
|
|
data-vip-slot="entry_effect"
|
|
>
|
|
<img class="frame" src="assets/entry-frame.png" alt="" />
|
|
<img id="entryItem" class="item" alt="" />
|
|
<span id="entryLabel" class="label">Entry Effect</span>
|
|
</button>
|
|
|
|
<div class="section-title vip-title">
|
|
<img src="assets/divider.png" alt="" />
|
|
<span>VIP Privileges</span>
|
|
<img class="right" src="assets/divider.png" alt="" />
|
|
</div>
|
|
|
|
<section class="privileges" aria-label="VIP privileges">
|
|
<div class="privilege profile">
|
|
<div class="circle">
|
|
<img
|
|
class="bg-circle"
|
|
src="assets/privilege-circle.png"
|
|
alt=""
|
|
/>
|
|
<img
|
|
class="icon"
|
|
src="assets/icon-profile.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div class="label">Gif Profile<br />Picture</div>
|
|
</div>
|
|
<div class="privilege nickname">
|
|
<div class="circle">
|
|
<img
|
|
class="bg-circle"
|
|
src="assets/privilege-circle.png"
|
|
alt=""
|
|
/>
|
|
<img
|
|
class="icon"
|
|
src="assets/icon-nickname.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div class="label">Colorful<br />Nickname</div>
|
|
</div>
|
|
<div class="privilege room">
|
|
<div class="circle">
|
|
<img
|
|
class="bg-circle"
|
|
src="assets/privilege-circle.png"
|
|
alt=""
|
|
/>
|
|
<img
|
|
class="icon"
|
|
src="assets/icon-room.png"
|
|
alt=""
|
|
/>
|
|
</div>
|
|
<div class="label">Gif Room<br />Picture</div>
|
|
</div>
|
|
<div class="privilege id">
|
|
<div class="circle">
|
|
<img
|
|
class="bg-circle"
|
|
src="assets/privilege-circle.png"
|
|
alt=""
|
|
/>
|
|
<div class="id-mark">ID</div>
|
|
</div>
|
|
<div class="label">Colorful ID</div>
|
|
</div>
|
|
</section>
|
|
|
|
<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>
|
|
</main>
|
|
</div>
|
|
|
|
<nav class="nav" aria-label="VIP navigation">
|
|
<button class="back" id="backButton" aria-label="Back"></button>
|
|
<div class="title-vip" aria-label="VIP">VIP</div>
|
|
</nav>
|
|
|
|
<section class="purchase-bar" aria-label="VIP purchase">
|
|
<div class="price">
|
|
<img src="assets/coin.png" alt="" />
|
|
<span id="priceText">30,500,000 / 30 Days</span>
|
|
</div>
|
|
<button class="activate" id="activateButton">
|
|
<img class="corner c1" src="assets/corner.png" alt="" />
|
|
<img class="corner c2" src="assets/corner.png" alt="" />
|
|
<img class="corner c3" src="assets/corner.png" alt="" />
|
|
<img class="corner c4" src="assets/corner.png" alt="" />
|
|
<span id="activateText">Activate</span>
|
|
</button>
|
|
<div class="home-indicator"></div>
|
|
</section>
|
|
<div id="toast" class="toast"></div>
|
|
<div
|
|
id="effectPreviewOverlay"
|
|
class="effect-preview-overlay"
|
|
role="button"
|
|
tabindex="0"
|
|
aria-label="Close preview"
|
|
aria-hidden="true"
|
|
>
|
|
<button
|
|
type="button"
|
|
id="effectPreviewClose"
|
|
class="effect-preview-close"
|
|
aria-label="Close preview"
|
|
></button>
|
|
<div id="effectPreviewStage" class="effect-preview-stage">
|
|
<img id="effectPreviewImage" class="effect-preview-image" alt="" />
|
|
<div
|
|
id="effectPreviewAnimation"
|
|
class="effect-preview-animation"
|
|
></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 src="../common/effect-player.js"></script>
|
|
<script>
|
|
(function () {
|
|
var DESIGN_WIDTH = 375;
|
|
var DESIGN_HEIGHT = 766;
|
|
var PURCHASE_HEIGHT = 107;
|
|
var state = {
|
|
loading: false,
|
|
purchasing: false,
|
|
packages: [],
|
|
currentVip: null,
|
|
selectedPackage: null,
|
|
selectedPackageIndex: -1,
|
|
userSelectedPackage: false,
|
|
badgeAnimationURL: '',
|
|
};
|
|
var badgeEffectPlayer = window.HyAppEffectPlayer.create(
|
|
'#heroBadgeAnimation',
|
|
{
|
|
width: 167,
|
|
height: 167,
|
|
waitForWindowLoad: true,
|
|
}
|
|
);
|
|
var previewEffectPlayer = window.HyAppEffectPlayer.create(
|
|
'#effectPreviewAnimation',
|
|
{
|
|
waitForWindowLoad: false,
|
|
}
|
|
);
|
|
var previewSequence = 0;
|
|
var swipeState = null;
|
|
var suppressPreviewClick = false;
|
|
|
|
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 resizeApp() {
|
|
var widthScale =
|
|
window.innerWidth <= 480
|
|
? window.innerWidth / DESIGN_WIDTH
|
|
: 1;
|
|
var scale = Math.max(0.1, widthScale);
|
|
document.documentElement.style.setProperty(
|
|
'--app-scale',
|
|
String(scale)
|
|
);
|
|
document.body.style.minHeight =
|
|
Math.ceil((DESIGN_HEIGHT + PURCHASE_HEIGHT) * scale) +
|
|
'px';
|
|
}
|
|
|
|
function setLoading(loading) {
|
|
state.loading = loading;
|
|
byId('loadingOverlay').classList.toggle('active', loading);
|
|
}
|
|
|
|
function toast(message) {
|
|
var node = byId('toast');
|
|
node.textContent = message || '';
|
|
node.classList.add('show');
|
|
window.clearTimeout(toast.timer);
|
|
toast.timer = window.setTimeout(function () {
|
|
node.classList.remove('show');
|
|
}, 1800);
|
|
}
|
|
|
|
function formatCoin(value) {
|
|
var num = Number(value || 0);
|
|
if (!Number.isFinite(num)) num = 0;
|
|
return String(Math.round(num)).replace(
|
|
/\B(?=(\d{3})+(?!\d))/g,
|
|
','
|
|
);
|
|
}
|
|
|
|
function formatDays(durationMS) {
|
|
var days = Math.max(
|
|
1,
|
|
Math.round(Number(durationMS || 0) / 86400000)
|
|
);
|
|
return days + ' Days';
|
|
}
|
|
|
|
var DECORATION_SLOTS = [
|
|
{
|
|
key: 'badge',
|
|
itemId: 'badgeItem',
|
|
labelId: 'badgeLabel',
|
|
stampId: 'badgeVipName',
|
|
label: 'SVIP Badge',
|
|
types: ['badge', 'vip_badge'],
|
|
hints: ['badge', 'svip'],
|
|
},
|
|
{
|
|
key: 'avatar_frame',
|
|
itemId: 'frameItem',
|
|
labelId: 'frameLabel',
|
|
stampId: 'frameVipName',
|
|
label: 'Frame',
|
|
types: ['avatar_frame', 'profile_frame', 'frame'],
|
|
hints: ['avatar', 'frame'],
|
|
},
|
|
{
|
|
key: 'vehicle',
|
|
itemId: 'mountItem',
|
|
labelId: 'mountLabel',
|
|
label: 'Mount',
|
|
types: ['vehicle', 'mount', 'car'],
|
|
hints: ['vehicle', 'mount', 'car'],
|
|
},
|
|
{
|
|
key: 'mic_seat_animation',
|
|
itemId: 'micItem',
|
|
labelId: 'micLabel',
|
|
label: 'Mic Animation',
|
|
types: [
|
|
'mic_seat_animation',
|
|
'mic_seat_icon',
|
|
'mic_animation',
|
|
'microphone_effect',
|
|
],
|
|
hints: ['mic', 'microphone'],
|
|
},
|
|
{
|
|
key: 'entry_effect',
|
|
itemId: 'entryItem',
|
|
labelId: 'entryLabel',
|
|
label: 'Entry Effect',
|
|
types: [
|
|
'floating_screen',
|
|
'entry_effect',
|
|
'entrance_effect',
|
|
'entry',
|
|
],
|
|
hints: ['floating', 'entry', 'entrance'],
|
|
},
|
|
];
|
|
|
|
function readField(source, names, fallback) {
|
|
if (!source) return fallback;
|
|
for (var index = 0; index < names.length; index += 1) {
|
|
var name = names[index];
|
|
if (
|
|
Object.prototype.hasOwnProperty.call(source, name) &&
|
|
source[name] !== undefined &&
|
|
source[name] !== null
|
|
) {
|
|
return source[name];
|
|
}
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
function normalizeReward(item) {
|
|
return {
|
|
resource_id: readField(
|
|
item,
|
|
['resource_id', 'resourceId'],
|
|
0
|
|
),
|
|
resource_code: readField(
|
|
item,
|
|
['resource_code', 'resourceCode'],
|
|
''
|
|
),
|
|
resource_type: readField(
|
|
item,
|
|
['resource_type', 'resourceType'],
|
|
''
|
|
),
|
|
name: readField(item, ['name'], ''),
|
|
quantity: Number(readField(item, ['quantity'], 0) || 0),
|
|
expires_at_ms: Number(
|
|
readField(
|
|
item,
|
|
['expires_at_ms', 'expiresAtMs'],
|
|
0
|
|
) || 0
|
|
),
|
|
cover_url: readField(
|
|
item,
|
|
['cover_url', 'coverUrl'],
|
|
''
|
|
),
|
|
asset_url: readField(item, ['asset_url', 'assetUrl'], ''),
|
|
preview_url: readField(
|
|
item,
|
|
['preview_url', 'previewUrl'],
|
|
''
|
|
),
|
|
animation_url: readField(
|
|
item,
|
|
['animation_url', 'animationUrl'],
|
|
''
|
|
),
|
|
};
|
|
}
|
|
|
|
function normalizeVip(vip) {
|
|
if (!vip) return null;
|
|
return {
|
|
level: Number(readField(vip, ['level'], 0) || 0),
|
|
name: readField(vip, ['name'], ''),
|
|
active: !!readField(vip, ['active'], false),
|
|
started_at_ms: Number(
|
|
readField(vip, ['started_at_ms', 'startedAtMs'], 0) ||
|
|
0
|
|
),
|
|
expires_at_ms: Number(
|
|
readField(vip, ['expires_at_ms', 'expiresAtMs'], 0) ||
|
|
0
|
|
),
|
|
};
|
|
}
|
|
|
|
function normalizePackage(item) {
|
|
var rewards = readField(item, ['reward_items', 'rewardItems'], []);
|
|
return {
|
|
level: Number(readField(item, ['level'], 0) || 0),
|
|
name: readField(item, ['name'], ''),
|
|
status: readField(item, ['status'], ''),
|
|
price_coin: Number(
|
|
readField(
|
|
item,
|
|
[
|
|
'price_coin',
|
|
'priceCoin',
|
|
'coin_price',
|
|
'coinPrice',
|
|
],
|
|
0
|
|
) || 0
|
|
),
|
|
duration_ms: Number(
|
|
readField(item, ['duration_ms', 'durationMs'], 0) || 0
|
|
),
|
|
reward_resource_group_id: Number(
|
|
readField(
|
|
item,
|
|
[
|
|
'reward_resource_group_id',
|
|
'rewardResourceGroupId',
|
|
],
|
|
0
|
|
) || 0
|
|
),
|
|
reward_items: Array.isArray(rewards)
|
|
? rewards.map(normalizeReward)
|
|
: [],
|
|
can_purchase: readField(item, ['can_purchase', 'canPurchase'], false),
|
|
sort_order: Number(readField(item, ['sort_order', 'sortOrder'], 0) || 0),
|
|
purchase_locked_reason: readField(
|
|
item,
|
|
['purchase_locked_reason', 'purchaseLockedReason'],
|
|
''
|
|
),
|
|
};
|
|
}
|
|
|
|
function normalizePackageResponse(data) {
|
|
var packages = readField(data, ['packages', 'levels'], []);
|
|
return {
|
|
current_vip: normalizeVip(
|
|
readField(data, ['current_vip', 'currentVip', 'vip'], null)
|
|
),
|
|
packages: Array.isArray(packages)
|
|
? packages.map(normalizePackage)
|
|
: [],
|
|
};
|
|
}
|
|
|
|
function lower(value) {
|
|
return String(value || '').toLowerCase();
|
|
}
|
|
|
|
function slotMatchesReward(slot, reward) {
|
|
var type = lower(reward.resource_type);
|
|
var code = lower(reward.resource_code);
|
|
if (
|
|
slot.types.indexOf(type) >= 0 ||
|
|
slot.hints.some(function (hint) {
|
|
return code.indexOf(hint) >= 0;
|
|
})
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function rewardForSlot(rewards, slot) {
|
|
return (rewards || []).find(function (reward) {
|
|
return slotMatchesReward(slot, reward);
|
|
});
|
|
}
|
|
|
|
function coverURL(reward) {
|
|
if (!reward) return '';
|
|
var cover = String(
|
|
reward.cover_url || reward.preview_url || ''
|
|
).trim();
|
|
if (cover) return cover;
|
|
var assetURL = String(reward.asset_url || '').trim();
|
|
return isDynamicAssetURL(assetURL) ? '' : assetURL;
|
|
}
|
|
|
|
function animationURL(reward) {
|
|
if (!reward) return '';
|
|
var explicitURL = String(reward.animation_url || '').trim();
|
|
if (explicitURL) return explicitURL;
|
|
var assetURL = String(reward.asset_url || '').trim();
|
|
return isDynamicAssetURL(assetURL) ? assetURL : '';
|
|
}
|
|
|
|
function isDynamicAssetURL(url) {
|
|
var clean = String(url || '').split('?')[0].split('#')[0];
|
|
return /\.(svga|pag|mp4|webm|mov)$/i.test(clean);
|
|
}
|
|
|
|
function playBadgeAnimation() {
|
|
var url = state.badgeAnimationURL;
|
|
var heroImage = byId('heroBadgeImage');
|
|
if (!url) {
|
|
heroImage.classList.remove('is-dynamic-hidden');
|
|
badgeEffectPlayer.stop();
|
|
return;
|
|
}
|
|
heroImage.classList.remove('is-dynamic-hidden');
|
|
badgeEffectPlayer
|
|
.play(url)
|
|
.then(function () {
|
|
if (state.badgeAnimationURL === url) {
|
|
heroImage.classList.add('is-dynamic-hidden');
|
|
}
|
|
})
|
|
.catch(function () {
|
|
if (state.badgeAnimationURL === url) {
|
|
heroImage.classList.remove('is-dynamic-hidden');
|
|
}
|
|
});
|
|
}
|
|
|
|
function setPreviewData(card, cover, animation, label) {
|
|
if (!card) return;
|
|
if (cover) {
|
|
card.dataset.coverUrl = cover;
|
|
} else {
|
|
delete card.dataset.coverUrl;
|
|
}
|
|
if (animation) {
|
|
card.dataset.animationUrl = animation;
|
|
} else {
|
|
delete card.dataset.animationUrl;
|
|
}
|
|
card.dataset.previewLabel = label || '';
|
|
card.classList.toggle('is-previewable', !!(cover || animation));
|
|
}
|
|
|
|
function resetPreview() {
|
|
var image = byId('effectPreviewImage');
|
|
var animation = byId('effectPreviewAnimation');
|
|
var stage = byId('effectPreviewStage');
|
|
previewSequence += 1;
|
|
previewEffectPlayer.stop();
|
|
stage.style.removeProperty('--preview-width');
|
|
stage.style.removeProperty('--preview-height');
|
|
image.classList.remove('is-dynamic-hidden');
|
|
image.removeAttribute('src');
|
|
image.alt = '';
|
|
animation.classList.remove('is-active');
|
|
}
|
|
|
|
function previewKindFromCard(card) {
|
|
if (!card) return 'default';
|
|
if (card.classList.contains('card-badge')) return 'badge';
|
|
if (card.classList.contains('card-frame')) return 'frame';
|
|
if (card.classList.contains('card-mount')) return 'mount';
|
|
if (card.classList.contains('card-mic')) return 'mic';
|
|
if (card.classList.contains('card-entry')) return 'entry';
|
|
return 'default';
|
|
}
|
|
|
|
function previewLimits(kind) {
|
|
var limits = {
|
|
badge: { width: 0.7, height: 0.58 },
|
|
frame: { width: 0.78, height: 0.62 },
|
|
mount: { width: 0.76, height: 0.72 },
|
|
mic: { width: 0.62, height: 0.56 },
|
|
entry: { width: 0.9, height: 0.34 },
|
|
default: { width: 0.82, height: 0.68 },
|
|
};
|
|
return limits[kind] || limits.default;
|
|
}
|
|
|
|
function fitPreviewStage(width, height, kind) {
|
|
var stage = byId('effectPreviewStage');
|
|
var sourceWidth = Number(width || 0);
|
|
var sourceHeight = Number(height || 0);
|
|
var limit = previewLimits(kind);
|
|
var maxWidth = Math.max(
|
|
1,
|
|
Math.round((window.innerWidth - 64) * limit.width)
|
|
);
|
|
var maxHeight = Math.max(
|
|
1,
|
|
Math.round((window.innerHeight - 156) * limit.height)
|
|
);
|
|
if (!sourceWidth || !sourceHeight) {
|
|
stage.style.setProperty('--preview-width', maxWidth + 'px');
|
|
stage.style.setProperty(
|
|
'--preview-height',
|
|
maxHeight + 'px'
|
|
);
|
|
return;
|
|
}
|
|
var scale = Math.min(
|
|
maxWidth / sourceWidth,
|
|
maxHeight / sourceHeight
|
|
);
|
|
stage.style.setProperty(
|
|
'--preview-width',
|
|
Math.round(sourceWidth * scale) + 'px'
|
|
);
|
|
stage.style.setProperty(
|
|
'--preview-height',
|
|
Math.round(sourceHeight * scale) + 'px'
|
|
);
|
|
}
|
|
|
|
function fitPreviewImage(image, sequence, kind) {
|
|
if (sequence !== previewSequence) return;
|
|
fitPreviewStage(
|
|
image.naturalWidth,
|
|
image.naturalHeight,
|
|
kind
|
|
);
|
|
}
|
|
|
|
function closeEffectPreview() {
|
|
var overlay = byId('effectPreviewOverlay');
|
|
overlay.classList.remove('is-open');
|
|
overlay.setAttribute('aria-hidden', 'true');
|
|
resetPreview();
|
|
}
|
|
|
|
function openEffectPreview(card) {
|
|
var cover = card.dataset.coverUrl || '';
|
|
var animation = card.dataset.animationUrl || '';
|
|
if (!cover && !animation) return;
|
|
var overlay = byId('effectPreviewOverlay');
|
|
var image = byId('effectPreviewImage');
|
|
var previewKind = previewKindFromCard(card);
|
|
resetPreview();
|
|
var sequence = previewSequence;
|
|
var animationStarted = false;
|
|
function startAnimation() {
|
|
if (
|
|
animationStarted ||
|
|
!animation ||
|
|
sequence !== previewSequence
|
|
) {
|
|
return;
|
|
}
|
|
animationStarted = true;
|
|
previewEffectPlayer
|
|
.play(animation)
|
|
.then(function () {
|
|
if (sequence === previewSequence) {
|
|
var playerNode = byId(
|
|
'effectPreviewAnimation'
|
|
);
|
|
var media = playerNode.querySelector(
|
|
'video, canvas'
|
|
);
|
|
if (media && !cover) {
|
|
fitPreviewStage(
|
|
media.videoWidth ||
|
|
media.width ||
|
|
0,
|
|
media.videoHeight ||
|
|
media.height ||
|
|
0,
|
|
previewKind
|
|
);
|
|
}
|
|
image.classList.add('is-dynamic-hidden');
|
|
}
|
|
})
|
|
.catch(function () {
|
|
if (sequence === previewSequence) {
|
|
image.classList.remove(
|
|
'is-dynamic-hidden'
|
|
);
|
|
}
|
|
});
|
|
}
|
|
if (cover) {
|
|
image.onload = function () {
|
|
fitPreviewImage(image, sequence, previewKind);
|
|
startAnimation();
|
|
};
|
|
image.onerror = function () {
|
|
fitPreviewStage(0, 0, previewKind);
|
|
startAnimation();
|
|
};
|
|
image.src = cover;
|
|
image.alt = card.dataset.previewLabel || '';
|
|
if (image.complete && image.naturalWidth) {
|
|
fitPreviewImage(image, sequence, previewKind);
|
|
}
|
|
} else {
|
|
fitPreviewStage(0, 0, previewKind);
|
|
}
|
|
overlay.classList.add('is-open');
|
|
overlay.setAttribute('aria-hidden', 'false');
|
|
overlay.focus();
|
|
if (!cover) startAnimation();
|
|
}
|
|
|
|
function displayVipName(vipPackage) {
|
|
if (!vipPackage) return 'VIP';
|
|
return vipPackage.name || 'VIP' + vipPackage.level;
|
|
}
|
|
|
|
function setImage(node, url, altText, animationURL) {
|
|
if (!node) return;
|
|
if (!url) {
|
|
node.removeAttribute('src');
|
|
node.alt = '';
|
|
node.removeAttribute('data-animation-url');
|
|
return;
|
|
}
|
|
node.src = url;
|
|
node.alt = altText || '';
|
|
if (animationURL) {
|
|
node.dataset.animationUrl = animationURL;
|
|
} else {
|
|
node.removeAttribute('data-animation-url');
|
|
}
|
|
}
|
|
|
|
function applyDecorationAssets(vipPackage) {
|
|
var rewards = (vipPackage && vipPackage.reward_items) || [];
|
|
var vipName = displayVipName(vipPackage);
|
|
var badgeReward = rewardForSlot(rewards, DECORATION_SLOTS[0]);
|
|
var heroURL = coverURL(badgeReward) || coverURL(rewards[0]);
|
|
state.badgeAnimationURL = animationURL(badgeReward);
|
|
setImage(
|
|
byId('heroBadgeImage'),
|
|
heroURL,
|
|
badgeReward ? badgeReward.name : vipName,
|
|
state.badgeAnimationURL
|
|
);
|
|
byId('heroVipName').textContent = vipName;
|
|
byId('heroVipName').classList.add('is-hidden');
|
|
playBadgeAnimation();
|
|
|
|
DECORATION_SLOTS.forEach(function (slot) {
|
|
var reward = rewardForSlot(rewards, slot);
|
|
var url = coverURL(reward);
|
|
var effectURL = animationURL(reward);
|
|
var label = reward && reward.name ? reward.name : slot.label;
|
|
setImage(
|
|
byId(slot.itemId),
|
|
url,
|
|
label,
|
|
effectURL
|
|
);
|
|
setPreviewData(
|
|
document.querySelector(
|
|
'[data-vip-slot="' + slot.key + '"]'
|
|
),
|
|
url,
|
|
effectURL,
|
|
label
|
|
);
|
|
byId(slot.labelId).textContent = label;
|
|
if (slot.stampId) {
|
|
byId(slot.stampId).textContent = vipName;
|
|
byId(slot.stampId).classList.add('is-hidden');
|
|
}
|
|
});
|
|
}
|
|
|
|
function samplePackages() {
|
|
return {
|
|
current_vip: {
|
|
level: 0,
|
|
name: '',
|
|
active: false,
|
|
started_at_ms: 0,
|
|
expires_at_ms: 0,
|
|
},
|
|
packages: [
|
|
{
|
|
level: 1,
|
|
name: 'VIP1',
|
|
price_coin: 1000,
|
|
duration_ms: 604800000,
|
|
can_purchase: true,
|
|
sort_order: 10,
|
|
reward_items: [],
|
|
},
|
|
{
|
|
level: 2,
|
|
name: 'VIP2',
|
|
price_coin: 3000,
|
|
duration_ms: 604800000,
|
|
can_purchase: true,
|
|
sort_order: 20,
|
|
reward_items: [],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
function comparePackages(a, b) {
|
|
var orderA = Number(a.sort_order || a.level || 0);
|
|
var orderB = Number(b.sort_order || b.level || 0);
|
|
if (orderA !== orderB) return orderA - orderB;
|
|
return Number(a.level || 0) - Number(b.level || 0);
|
|
}
|
|
|
|
function sortedPackages(packages) {
|
|
return (packages || []).slice().sort(comparePackages);
|
|
}
|
|
|
|
function packageIndex(vipPackage) {
|
|
if (!vipPackage) return -1;
|
|
var selectedLevel = Number(vipPackage.level || 0);
|
|
for (var index = 0; index < state.packages.length; index += 1) {
|
|
if (
|
|
Number(state.packages[index].level || 0) ===
|
|
selectedLevel
|
|
) {
|
|
return index;
|
|
}
|
|
}
|
|
return state.packages.indexOf(vipPackage);
|
|
}
|
|
|
|
function setSelectedPackage(vipPackage, userInitiated) {
|
|
if (userInitiated) {
|
|
state.userSelectedPackage = true;
|
|
}
|
|
var index = packageIndex(vipPackage);
|
|
if (index < 0 && state.packages.length) {
|
|
index = 0;
|
|
}
|
|
state.selectedPackageIndex = index;
|
|
state.selectedPackage =
|
|
index >= 0 ? state.packages[index] : null;
|
|
}
|
|
|
|
function selectedPackageOrDefault(currentVip) {
|
|
if (state.userSelectedPackage && state.selectedPackage) {
|
|
var currentSelection = state.packages.find(function (item) {
|
|
return (
|
|
Number(item.level || 0) ===
|
|
Number(state.selectedPackage.level || 0)
|
|
);
|
|
});
|
|
if (currentSelection) return currentSelection;
|
|
}
|
|
return choosePackage(state.packages, currentVip);
|
|
}
|
|
|
|
function selectPackageByIndex(index) {
|
|
if (
|
|
state.loading ||
|
|
state.purchasing ||
|
|
state.packages.length < 2
|
|
) {
|
|
updateSwitchButtons();
|
|
return false;
|
|
}
|
|
var nextIndex = Math.max(
|
|
0,
|
|
Math.min(index, state.packages.length - 1)
|
|
);
|
|
if (nextIndex === state.selectedPackageIndex) {
|
|
updateSwitchButtons();
|
|
return false;
|
|
}
|
|
setSelectedPackage(state.packages[nextIndex], true);
|
|
render();
|
|
return true;
|
|
}
|
|
|
|
function selectPackageByDelta(delta) {
|
|
var index = packageIndex(state.selectedPackage);
|
|
if (index < 0) index = 0;
|
|
return selectPackageByIndex(index + delta);
|
|
}
|
|
|
|
function updateSwitchButtons() {
|
|
var prevButton = byId('vipPrevButton');
|
|
var nextButton = byId('vipNextButton');
|
|
var hasMultiplePackages = state.packages.length > 1;
|
|
var index = packageIndex(state.selectedPackage);
|
|
if (state.selectedPackageIndex !== index) {
|
|
state.selectedPackageIndex = index;
|
|
}
|
|
prevButton.classList.toggle(
|
|
'is-hidden',
|
|
!hasMultiplePackages
|
|
);
|
|
nextButton.classList.toggle(
|
|
'is-hidden',
|
|
!hasMultiplePackages
|
|
);
|
|
prevButton.disabled =
|
|
!hasMultiplePackages ||
|
|
state.loading ||
|
|
state.purchasing ||
|
|
index <= 0;
|
|
nextButton.disabled =
|
|
!hasMultiplePackages ||
|
|
state.loading ||
|
|
state.purchasing ||
|
|
index >= state.packages.length - 1;
|
|
}
|
|
|
|
function choosePackage(packages, currentVip) {
|
|
var requested = Number(queryValue('level') || 0);
|
|
var list = sortedPackages(packages);
|
|
if (requested) {
|
|
return (
|
|
list.find(function (item) {
|
|
return Number(item.level) === requested;
|
|
}) ||
|
|
list[list.length - 1] ||
|
|
null
|
|
);
|
|
}
|
|
if (currentVip && currentVip.level) {
|
|
return (
|
|
list.find(function (item) {
|
|
return (
|
|
Number(item.level) ===
|
|
Number(currentVip.level)
|
|
);
|
|
}) ||
|
|
list[list.length - 1] ||
|
|
null
|
|
);
|
|
}
|
|
return (
|
|
list.find(function (item) {
|
|
return item.can_purchase;
|
|
}) ||
|
|
list[list.length - 1] ||
|
|
null
|
|
);
|
|
}
|
|
|
|
function render() {
|
|
var selected = state.selectedPackage;
|
|
var currentVip = state.currentVip || {};
|
|
var priceText = selected
|
|
? formatCoin(selected.price_coin) +
|
|
' / ' +
|
|
formatDays(selected.duration_ms)
|
|
: '30,500,000 / 30 Days';
|
|
var selectedLevel = Number((selected && selected.level) || 0);
|
|
var currentLevel = Number(currentVip.level || 0);
|
|
var sameLevelActive =
|
|
currentVip.active &&
|
|
selected &&
|
|
currentLevel === selectedLevel;
|
|
var lowerThanCurrent =
|
|
currentVip.active &&
|
|
selected &&
|
|
currentLevel > selectedLevel;
|
|
var locked =
|
|
selected &&
|
|
((selected.can_purchase === false &&
|
|
!sameLevelActive) ||
|
|
lowerThanCurrent);
|
|
byId('priceText').textContent = priceText;
|
|
byId('activateText').textContent = sameLevelActive
|
|
? 'Renew'
|
|
: 'Activate';
|
|
byId('activateButton').disabled =
|
|
state.loading ||
|
|
state.purchasing ||
|
|
locked;
|
|
applyDecorationAssets(selected);
|
|
updateSwitchButtons();
|
|
}
|
|
|
|
function loadVIP() {
|
|
if (mockEnabled()) return Promise.resolve(samplePackages());
|
|
return window.HyAppAPI.vip.packages();
|
|
}
|
|
|
|
function loadMine() {
|
|
if (mockEnabled())
|
|
return Promise.resolve(samplePackages().current_vip);
|
|
return window.HyAppAPI.vip.me();
|
|
}
|
|
|
|
function hydrate() {
|
|
setLoading(true);
|
|
return loadVIP()
|
|
.then(function (data) {
|
|
var normalized = normalizePackageResponse(data || {});
|
|
state.packages = sortedPackages(normalized.packages);
|
|
state.currentVip = normalized.current_vip;
|
|
setSelectedPackage(
|
|
selectedPackageOrDefault(state.currentVip),
|
|
false
|
|
);
|
|
render();
|
|
return loadMine()
|
|
.then(function (vip) {
|
|
state.currentVip =
|
|
normalizeVip(vip) || state.currentVip;
|
|
setSelectedPackage(
|
|
selectedPackageOrDefault(
|
|
state.currentVip
|
|
),
|
|
false
|
|
);
|
|
render();
|
|
})
|
|
.catch(function () {
|
|
render();
|
|
});
|
|
})
|
|
.catch(function (error) {
|
|
toast((error && error.message) || 'Load failed');
|
|
})
|
|
.finally(function () {
|
|
setLoading(false);
|
|
render();
|
|
});
|
|
}
|
|
|
|
function commandId(level) {
|
|
var appCode =
|
|
window.HyAppAPI && window.HyAppAPI.getAppCode
|
|
? window.HyAppAPI.getAppCode()
|
|
: 'lalu';
|
|
return (
|
|
'vip_' +
|
|
appCode +
|
|
'_' +
|
|
level +
|
|
'_' +
|
|
Date.now() +
|
|
'_' +
|
|
Math.random().toString(36).slice(2, 8)
|
|
);
|
|
}
|
|
|
|
function purchase() {
|
|
var selected = state.selectedPackage;
|
|
if (!selected || state.purchasing) return;
|
|
state.purchasing = true;
|
|
setLoading(true);
|
|
render();
|
|
window.HyAppAPI.vip
|
|
.purchase({
|
|
command_id: commandId(selected.level),
|
|
level: Number(selected.level),
|
|
})
|
|
.then(function (data) {
|
|
state.currentVip =
|
|
normalizeVip(
|
|
readField(data, ['vip', 'current_vip', 'currentVip'], null)
|
|
) || state.currentVip;
|
|
toast('Purchase successful');
|
|
return hydrate();
|
|
})
|
|
.catch(function (error) {
|
|
toast(
|
|
(error && error.message) || 'Purchase failed'
|
|
);
|
|
})
|
|
.finally(function () {
|
|
state.purchasing = false;
|
|
setLoading(false);
|
|
render();
|
|
});
|
|
}
|
|
|
|
function handleSwipeStart(event) {
|
|
if (
|
|
state.loading ||
|
|
state.purchasing ||
|
|
event.touches.length !== 1
|
|
) {
|
|
swipeState = null;
|
|
return;
|
|
}
|
|
var target = event.target;
|
|
if (
|
|
target.closest &&
|
|
target.closest(
|
|
'.vip-switch, .effect-preview-overlay'
|
|
)
|
|
) {
|
|
swipeState = null;
|
|
return;
|
|
}
|
|
swipeState = {
|
|
x: event.touches[0].clientX,
|
|
y: event.touches[0].clientY,
|
|
};
|
|
}
|
|
|
|
function handleSwipeEnd(event) {
|
|
if (!swipeState || !event.changedTouches.length) return;
|
|
var touch = event.changedTouches[0];
|
|
var deltaX = touch.clientX - swipeState.x;
|
|
var deltaY = touch.clientY - swipeState.y;
|
|
swipeState = null;
|
|
if (
|
|
Math.abs(deltaX) < 44 ||
|
|
Math.abs(deltaX) < Math.abs(deltaY) * 1.2
|
|
) {
|
|
return;
|
|
}
|
|
suppressPreviewClick = true;
|
|
window.setTimeout(function () {
|
|
suppressPreviewClick = false;
|
|
}, 120);
|
|
selectPackageByDelta(deltaX < 0 ? 1 : -1);
|
|
}
|
|
|
|
window.addEventListener('resize', resizeApp);
|
|
window.addEventListener('orientationchange', resizeApp);
|
|
byId('backButton').addEventListener('click', function () {
|
|
window.HyAppBridge.back();
|
|
});
|
|
byId('vipPrevButton').addEventListener('click', function () {
|
|
selectPackageByDelta(-1);
|
|
});
|
|
byId('vipNextButton').addEventListener('click', function () {
|
|
selectPackageByDelta(1);
|
|
});
|
|
byId('activateButton').addEventListener('click', purchase);
|
|
document
|
|
.querySelector('.page')
|
|
.addEventListener('touchstart', handleSwipeStart, {
|
|
passive: true,
|
|
});
|
|
document
|
|
.querySelector('.page')
|
|
.addEventListener('touchend', handleSwipeEnd, {
|
|
passive: true,
|
|
});
|
|
document
|
|
.querySelector('.page')
|
|
.addEventListener('touchcancel', function () {
|
|
swipeState = null;
|
|
});
|
|
byId('effectPreviewOverlay').addEventListener(
|
|
'click',
|
|
function (event) {
|
|
if (event.target === event.currentTarget) {
|
|
closeEffectPreview();
|
|
}
|
|
}
|
|
);
|
|
byId('effectPreviewClose').addEventListener(
|
|
'click',
|
|
function (event) {
|
|
event.stopPropagation();
|
|
closeEffectPreview();
|
|
}
|
|
);
|
|
byId('effectPreviewOverlay').addEventListener(
|
|
'keydown',
|
|
function (event) {
|
|
if (
|
|
event.key === 'Enter' ||
|
|
event.key === ' ' ||
|
|
event.key === 'Escape'
|
|
) {
|
|
closeEffectPreview();
|
|
}
|
|
}
|
|
);
|
|
Array.prototype.forEach.call(
|
|
document.querySelectorAll('.decor-card'),
|
|
function (card) {
|
|
card.addEventListener('click', function () {
|
|
if (suppressPreviewClick) {
|
|
suppressPreviewClick = false;
|
|
return;
|
|
}
|
|
openEffectPreview(card);
|
|
});
|
|
}
|
|
);
|
|
|
|
resizeApp();
|
|
render();
|
|
window.HyAppParams.parse();
|
|
window.HyAppBridge.ready({ page: 'vip' });
|
|
hydrate();
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|