2026-06-12 19:35:46 +08:00

1127 lines
29 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<title>App Invite</title>
<style>
:root {
--page-width: 375px;
--page-bg: #0b3008;
--page-bg-deep: #061e05;
--panel-bg: #0d3b0a;
--panel-bg-soft: rgba(15, 62, 11, 0.94);
--panel-line: rgba(240, 192, 83, 0.95);
--panel-line-soft: rgba(255, 220, 146, 0.36);
--text-primary: #f6e0b2;
--text-secondary: #d79667;
--text-muted: rgba(246, 224, 178, 0.7);
--text-bright: #f9f478;
--button-glow: linear-gradient(135deg, #248934 0%, #1f9f49 42%, #2dc45d 100%);
--button-passive: linear-gradient(180deg, rgba(12, 62, 14, 0.9), rgba(12, 54, 14, 0.92));
--shadow-soft: 0 14px 34px rgba(0, 0, 0, 0.22);
--shadow-gold: 0 0 0 1px rgba(246, 224, 178, 0.18), 0 10px 20px rgba(0, 0, 0, 0.24);
--surface-radius: 18px;
--ornate-top: url("./assets/panel-top.png");
--ornate-middle: url("./assets/panel-middle.png");
--ornate-bottom: url("./assets/panel-bottom.png");
--monthly-title-bg: url("./assets/mt1.png");
--task-tab-active-bg: url("./assets/btnselect.png");
--task-tab-inactive-bg: url("./assets/btnunselect.png");
--countdown-card-bg: url("./assets/djs.png");
--button-active-bg: url("./assets/btnselect.png");
--button-inactive-bg: url("./assets/btnunselect.png");
--gift-cta-side: 12px;
--gift-cta-height: min(68px, 18.235vw);
--gift-cta-bottom: calc(env(safe-area-inset-bottom) + 10px);
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
min-height: 100%;
}
body {
background:
radial-gradient(circle at top, rgba(59, 130, 40, 0.18), transparent 22%),
linear-gradient(180deg, #112708 0%, #081a05 100%);
font-family:
-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial,
sans-serif;
color: var(--text-primary);
}
button,
input {
font: inherit;
}
button {
border: none;
cursor: pointer;
appearance: none;
-webkit-appearance: none;
background: transparent;
}
.page {
width: min(100%, var(--page-width));
min-height: 100vh;
margin: 0 auto;
background: linear-gradient(180deg, #0d3108 0%, #0a2907 100%);
overflow: hidden;
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.03);
}
.view {
display: none;
}
.view.active {
display: block;
}
.section {
padding: 0 12px;
}
.hero-invite {
position: relative;
aspect-ratio: 1080 / 1027;
overflow: hidden;
}
.hero-invite img {
position: absolute;
left: -1.8519%;
top: -44.0117%;
width: 102.1296%;
height: 192.3077%;
object-fit: fill;
}
.hero-invite::after {
content: "";
position: absolute;
inset: auto 0 0;
height: 100px;
background: linear-gradient(180deg, rgba(11, 48, 8, 0), rgba(11, 48, 8, 0.96));
}
.hero-copy {
position: absolute;
inset: auto 10px 86px;
z-index: 1;
text-align: center;
}
.hero-copy.art-title {
left: 0;
right: 0;
bottom: 82px;
height: 107px;
}
.hero-copy.art-title img {
position: absolute;
left: 50%;
display: block;
height: auto;
transform: translateX(-50%);
}
.hero-title-top {
top: 0;
width: min(100%, 338px);
}
.hero-title-combined {
top: 0;
width: min(100%, 338px);
}
.ornate-panel {
position: relative;
z-index: 0;
isolation: isolate;
background-color: #0b3a08;
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.22);
}
.ornate-panel::before {
content: "";
position: absolute;
inset: 0;
z-index: 0;
background-image: var(--ornate-top), var(--ornate-bottom), var(--ornate-middle);
background-position: top center, bottom center, top center;
background-size: 100% auto, 100% auto, 100% auto;
background-repeat: no-repeat, no-repeat, repeat-y;
pointer-events: none;
}
.ornate-panel > * {
position: relative;
z-index: 1;
}
.timer-panel {
margin-top: -12px;
padding: 22px 16px 18px;
}
.timer-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
}
.timer-box {
display: flex;
flex-direction: column;
align-items: center;
padding: 0;
text-align: center;
}
.timer-value {
width: 58px;
aspect-ratio: 113 / 131;
display: flex;
align-items: center;
justify-content: center;
padding-top: 1px;
background: center / 100% 100% no-repeat var(--countdown-card-bg);
font-size: 22px;
line-height: 1;
font-weight: 700;
color: #fff7dd;
}
.timer-label {
margin-top: 9px;
font-size: 10px;
line-height: 1;
color: var(--text-muted);
}
.reward-panel {
margin-top: 14px;
padding: 24px 16px 20px;
text-align: center;
}
.panel-title {
margin: 0;
font-size: 18px;
line-height: 1.2;
font-weight: 700;
color: #fff7dd;
}
.panel-subtitle {
margin: 6px 0 16px;
font-size: 10px;
line-height: 1.35;
color: var(--text-secondary);
}
.reward-grid {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: 8px;
}
.reward-item {
position: relative;
}
.reward-amount {
z-index: 999;
position: absolute;
top: 27px;
right: 6px;
font-size: 20px;
line-height: 1;
font-weight: 700;
color: #fff3d3;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.32);
}
.reward-orb {
position: relative;
width: min(100%, 122px);
aspect-ratio: 1;
margin: 0 auto;
border-radius: 999px;
background:
radial-gradient(circle, rgba(255, 240, 180, 0.5) 0%, rgba(255, 240, 180, 0.06) 56%, transparent 72%),
rgba(255, 255, 255, 0.02);
box-shadow:
inset 0 0 0 1px rgba(255, 245, 209, 0.28),
0 0 28px rgba(255, 230, 157, 0.08);
overflow: hidden;
}
.reward-orb img {
position: absolute;
left: 50%;
top: 55%;
width: 66%;
height: 66%;
object-fit: contain;
transform: translate(-50%, -50%);
}
.reward-label {
margin-top: 10px;
font-size: 11px;
line-height: 1.3;
color: var(--text-primary);
}
.reward-plus {
font-size: 42px;
line-height: 1;
color: rgba(249, 244, 120, 0.9);
}
.intro-card {
margin-top: 14px;
padding: 24px 16px 20px;
text-align: center;
}
.intro-title {
margin: 0;
font-size: 18px;
line-height: 1.2;
font-weight: 700;
}
.intro-text {
margin: 7px 0 14px;
font-size: 10px;
line-height: 1.4;
color: var(--text-muted);
}
.copy-stack {
display: grid;
gap: 10px;
margin-top: 14px;
}
.copy-row {
display: grid;
grid-template-columns: minmax(0, 1fr) 104px;
gap: 8px;
align-items: center;
}
.copy-value {
min-width: 0;
display: flex;
align-items: center;
gap: 6px;
height: 36px;
padding: 0 10px;
border-radius: 8px;
border: 1px solid rgba(255, 236, 176, 0.78);
background: rgba(6, 37, 6, 0.46);
color: #fef7dd;
font-size: 10px;
line-height: 1;
overflow: hidden;
}
.copy-prefix {
flex: 0 0 auto;
color: #f6e0b2;
}
.copy-text {
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.art-button {
display: block;
width: 100%;
padding: 0;
border: 0;
background: transparent;
}
.art-button img {
display: block;
width: 100%;
height: auto;
pointer-events: none;
}
.wide-art-button {
width: min(100%, 269px);
margin: 12px auto 0;
}
.copy-action-button {
width: 100%;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: center / 100% 100% no-repeat var(--button-active-bg);
color: #fff8d5;
font-size: 16px;
font-weight: 800;
line-height: 1;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.45);
}
.meta-inline {
margin-top: 10px;
font-size: 11px;
line-height: 1.4;
color: var(--text-muted);
}
.gold-button {
min-width: 118px;
height: 42px;
padding: 0 20px;
border-radius: 12px;
border: 1.5px solid rgba(253, 225, 146, 0.85);
background: var(--button-glow);
color: #fff7dd;
font-size: 14px;
font-weight: 700;
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.12),
0 8px 18px rgba(0, 0, 0, 0.22);
}
.gold-button.passive {
background: var(--button-passive);
color: var(--text-primary);
}
.stats-panel {
margin-top: 14px;
padding: 22px 14px 20px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px 12px;
}
.stat-card {
text-align: center;
}
.stat-value {
font-size: 22px;
line-height: 1;
font-weight: 700;
color: #fff6d4;
}
.stat-label {
margin-top: 8px;
font-size: 10px;
line-height: 1.3;
color: var(--text-muted);
}
.section-header-art {
width: min(100%, 150px);
margin: 16px auto 12px;
}
.section-header-art img {
display: block;
width: 100%;
height: auto;
}
.monthly-task-panel {
margin-top: 32px;
padding: 58px 18px 20px;
}
.inline-rank-panel {
margin-top: 20px;
padding: 44px 16px 18px;
}
.monthly-title {
position: absolute;
top: -24px;
left: 50%;
z-index: 2;
width: 140px;
aspect-ratio: 419 / 120;
display: flex;
align-items: center;
justify-content: center;
padding: 0 10px;
transform: translateX(-50%);
background: center / 100% 100% no-repeat var(--monthly-title-bg);
color: #fff7d7;
font-size: 16px;
font-weight: 800;
line-height: 1;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.45);
}
.inline-rank-title {
top: -16px;
width: 88px;
font-size: 12px;
}
.task-tabs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.task-tab {
position: relative;
min-height: 0;
aspect-ratio: 419 / 120;
padding: 0 6px;
border: 0;
background: center / 100% 100% no-repeat var(--task-tab-inactive-bg);
color: #fff4b9;
font-size: 12px;
font-weight: 700;
line-height: 1.05;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.55);
}
.task-tab.active {
background-image: var(--task-tab-active-bg);
color: #fff8d5;
}
.task-list-panel {
margin-top: 12px;
padding: 22px 12px 18px;
}
.task-list {
display: grid;
gap: 9px;
margin-top: 14px;
}
.task-card {
display: grid;
grid-template-columns: 1fr auto;
gap: 8px;
align-items: center;
min-height: 58px;
padding: 10px 12px 9px;
background: center / 100% 100% no-repeat url("./assets/task-row-bg.svg");
}
.task-title {
margin: 0;
font-size: 13px;
line-height: 1.2;
font-weight: 700;
color: #fff9e3;
}
.task-desc {
margin: 4px 0 0;
font-size: 10px;
line-height: 1.25;
color: var(--text-muted);
}
.claim-button {
position: relative;
min-width: 73px;
height: 22px;
padding: 0 9px;
border: 0;
background: center / 100% 100% no-repeat var(--button-inactive-bg);
color: #f0dfb3;
font-size: 10px;
font-weight: 700;
flex-shrink: 0;
white-space: nowrap;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
.claim-button.is-claimable {
background-image: var(--button-active-bg);
color: #fff8d5;
}
.claim-button.disabled,
.claim-button.is-claimed,
.claim-button.is-pending,
.claim-button.is-loading {
background-image: var(--button-inactive-bg);
color: rgba(246, 224, 178, 0.75);
}
.task-note {
margin: 14px 4px 0;
font-size: 10px;
line-height: 1.4;
text-align: center;
color: var(--text-muted);
}
.rank-view {
padding: 18px 12px 28px;
}
.rank-header {
position: relative;
}
.rank-header .section-header-art {
margin: 0 auto 12px;
}
.rank-back-button {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
gap: 6px;
min-height: 30px;
padding: 0 12px 0 10px;
border: 1px solid rgba(255, 236, 176, 0.78);
border-radius: 999px;
background: rgba(6, 37, 6, 0.58);
color: #fef2cc;
font-size: 12px;
line-height: 1;
font-weight: 700;
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.06),
0 8px 18px rgba(0, 0, 0, 0.18);
}
.rank-back-icon {
width: 8px;
height: 8px;
border-left: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: rotate(45deg);
}
.rank-list {
display: grid;
gap: 8px;
}
.inline-rank-list {
gap: 4px;
}
.rank-row {
display: grid;
grid-template-columns: 30px 42px minmax(0, 1fr) auto;
align-items: center;
gap: 10px;
min-height: 50px;
padding: 10px 14px;
background-position: center;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.rank-badge {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
font-size: 14px;
font-weight: 700;
color: #fef7e2;
}
.rank-badge img {
display: block;
width: 24px;
height: 24px;
object-fit: contain;
}
.avatar {
width: 42px;
height: 42px;
border-radius: 999px;
border: 1.5px solid #ffebbb;
overflow: hidden;
background: rgba(255, 255, 255, 0.08);
}
.avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.rank-name {
min-width: 0;
font-size: 14px;
line-height: 1;
color: #fff7e2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.rank-score {
font-size: 14px;
line-height: 1;
color: #f8ead2;
}
.inline-rank-panel .rank-row {
grid-template-columns: 22px 30px minmax(0, 1fr) 48px 48px;
min-height: 37px;
gap: 6px;
padding: 4px 10px;
}
.inline-rank-panel .rank-badge {
width: 20px;
height: 20px;
font-size: 10px;
}
.inline-rank-panel .rank-badge img {
width: 20px;
height: 20px;
}
.inline-rank-panel .avatar {
width: 30px;
height: 30px;
}
.inline-rank-panel .rank-name,
.inline-rank-panel .rank-score,
.inline-rank-panel .rank-reward {
font-size: 10px;
}
.inline-rank-panel .rank-score,
.inline-rank-panel .rank-reward {
text-align: right;
white-space: nowrap;
}
.gift-view {
position: relative;
min-height: 100vh;
overflow: hidden;
}
.gift-backdrop {
position: absolute;
inset: 0;
overflow: hidden;
}
.gift-backdrop img {
position: absolute;
left: -130.3704%;
top: 0;
width: 302.3148%;
height: 245.5508%;
object-fit: fill;
}
.gift-overlay {
position: relative;
z-index: 1;
padding:
calc(env(safe-area-inset-top) + 18px)
14px
calc(var(--gift-cta-height) + var(--gift-cta-bottom) + 22px);
background: linear-gradient(180deg, rgba(11, 48, 8, 0.08), rgba(11, 48, 8, 0.2));
}
.gift-avatar {
width: 90px;
height: 90px;
margin: 0 auto;
border-radius: 999px;
border: 2px solid #ffebbb;
overflow: hidden;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.24);
}
.gift-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.gift-title {
margin: 16px auto 0;
max-width: 300px;
font-size: 28px;
line-height: 1.02;
font-weight: 700;
text-align: center;
}
.gift-subtitle {
margin: 12px 0 18px;
font-size: 17px;
line-height: 1.2;
text-align: center;
}
.gift-panel {
padding: 24px 16px 18px;
text-align: center;
}
.gift-panel .reward-orb {
width: 134px;
}
.gift-note {
margin-top: 12px;
font-size: 11px;
line-height: 1.4;
color: #efe2bb;
}
.phone-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
margin-top: 18px;
}
.phone-card {
border-radius: 18px;
overflow: hidden;
box-shadow: var(--shadow-soft);
}
.phone-card img {
display: block;
width: 100%;
height: auto;
}
.cta-button {
position: fixed;
left: 50%;
bottom: var(--gift-cta-bottom);
z-index: 30;
width: min(
calc(100vw - var(--gift-cta-side) - var(--gift-cta-side)),
calc(var(--page-width) - var(--gift-cta-side) - var(--gift-cta-side))
);
height: auto;
margin: 0;
padding: 0;
border: 0;
background: transparent;
transform: translateX(-50%);
}
.toast {
position: fixed;
left: 50%;
bottom: 24px;
z-index: 40;
transform: translateX(-50%) translateY(14px);
min-width: 120px;
max-width: calc(100vw - 32px);
padding: 10px 14px;
border-radius: 999px;
background: rgba(2, 16, 6, 0.88);
color: #fff8de;
font-size: 12px;
line-height: 1.2;
text-align: center;
opacity: 0;
transition:
opacity 0.2s ease,
transform 0.2s ease;
pointer-events: none;
}
.toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
</style>
</head>
<body>
<main class="page">
<section class="view active" data-view="invite">
<header class="hero-invite">
<img
id="inviteHeroImage"
data-src="./assets/invite-hero-bg.png"
alt="Invite activity hero"
/>
<div class="hero-copy art-title">
<img
data-src="./assets/hero-title-combined.png"
alt="Invite friends Make money"
class="lazy-image hero-title-combined"
/>
</div>
</header>
<div class="section">
<div class="ornate-panel timer-panel">
<div class="timer-grid">
<div class="timer-box">
<div class="timer-value" data-countdown="days">01</div>
<div class="timer-label">Days</div>
</div>
<div class="timer-box">
<div class="timer-value" data-countdown="hours">01</div>
<div class="timer-label">Hours</div>
</div>
<div class="timer-box">
<div class="timer-value" data-countdown="minutes">01</div>
<div class="timer-label">Minutes</div>
</div>
<div class="timer-box">
<div class="timer-value" data-countdown="seconds">01</div>
<div class="timer-label">Seconds</div>
</div>
</div>
</div>
<div class="ornate-panel reward-panel">
<p class="panel-title">Your Reward</p>
<p class="panel-subtitle">(Earn for every friend you invite)</p>
<div class="reward-grid">
<article class="reward-item">
<div class="reward-amount">+2000</div>
<div class="reward-orb">
<img
data-src="./assets/reward-bag.png"
alt="Invitation reward"
class="lazy-image"
/>
</div>
<div class="reward-label">Invitation received</div>
</article>
<div class="reward-plus">+</div>
<article class="reward-item">
<div class="reward-amount">+1000</div>
<div class="reward-orb">
<img
data-src="./assets/reward-bag.png"
alt="Recharge reward"
class="lazy-image"
/>
</div>
<div class="reward-label">Top up via friends</div>
</article>
</div>
</div>
<div class="ornate-panel intro-card">
<p class="intro-title">Start inviting people now</p>
<p class="intro-text">(Copy your invitation link and share it with your friends)</p>
<div class="copy-stack">
<div class="copy-row">
<div class="copy-value">
<span class="copy-prefix">Invitation link:</span>
<span class="copy-text" id="inviteLinkText"></span>
</div>
<button
type="button"
class="art-button copy-action-button"
data-copy-target="invite-link"
aria-label="Copy invitation link"
>
Copy
</button>
</div>
<div class="copy-row">
<div class="copy-value">
<span class="copy-prefix">Invitation code:</span>
<span class="copy-text" id="inviteCodeText"></span>
</div>
<button
type="button"
class="art-button copy-action-button"
data-copy-target="invite-code"
aria-label="Copy invitation code"
>
Copy
</button>
</div>
</div>
</div>
<div class="ornate-panel stats-panel">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="statInvitees">168</div>
<div class="stat-label">Total number of invitees</div>
</div>
<div class="stat-card">
<div class="stat-value" id="statEligible">01</div>
<div class="stat-label">Number of eligible voters</div>
</div>
<div class="stat-card">
<div class="stat-value" id="statRecharge">01</div>
<div class="stat-label">Total amount recharged by invitees</div>
</div>
<div class="stat-card">
<div class="stat-value" id="statRewards">12354</div>
<div class="stat-label">My Rewards</div>
</div>
</div>
</div>
<div class="ornate-panel monthly-task-panel">
<div class="monthly-title">Monthly Tasks</div>
<div class="task-tabs">
<button type="button" class="task-tab passive" data-task-set="transport">
Total Transportation<br />Volume
</button>
<button type="button" class="task-tab active" data-task-set="quota">
Invitation Quota<br />Task
</button>
</div>
<div class="task-list" id="taskList"></div>
<p class="task-note" id="taskNote"></p>
</div>
<div class="ornate-panel inline-rank-panel">
<div class="monthly-title inline-rank-title">Rank</div>
<div
class="rank-list inline-rank-list"
id="inlineRankList"
data-rank-list
data-rank-variant="inline"
></div>
</div>
</div>
</section>
<section class="view" data-view="rank">
<div class="rank-view">
<div class="rank-header">
<button type="button" class="rank-back-button" data-nav-view="invite" aria-label="Back to invite">
<span class="rank-back-icon" aria-hidden="true"></span>
<span>Back</span>
</button>
<div class="section-header-art">
<img src="./assets/rank-title-button.png" alt="Rank" />
</div>
</div>
<div class="rank-list" id="rankList" data-rank-list></div>
</div>
</section>
<section class="view gift-view" data-view="gift">
<div class="gift-backdrop">
<img
id="giftBgImage"
data-src="./assets/gift-bg.png"
alt="Invite gift background"
/>
</div>
<div class="gift-overlay">
<div class="gift-avatar">
<img
id="giftAvatarImage"
data-src="./assets/avatar-sample.png"
alt="Inviter avatar"
/>
</div>
<h1 class="gift-title" id="giftTitle"></h1>
<p class="gift-subtitle">Join him to receive an exclusive welcome gift</p>
<div class="ornate-panel gift-panel">
<p class="panel-title">Your Personal Gift</p>
<p class="panel-subtitle">(This is a special gift from your friend)</p>
<article class="reward-item">
<div class="reward-amount">+1000</div>
<div class="reward-orb">
<img
data-src="./assets/reward-bag.png"
alt="Personal gift"
class="lazy-image"
/>
</div>
</article>
<p class="gift-note">
(Available only through this invitation link! Don't miss out!)
</p>
</div>
<div class="phone-grid">
<div class="phone-card">
<img
id="phoneHomeImage"
data-src="./assets/phone-home.png"
alt="App home preview"
/>
</div>
<div class="phone-card">
<img
id="phoneGiftImage"
data-src="./assets/phone-gift.png"
alt="Gift preview"
/>
</div>
</div>
<button
type="button"
class="cta-button art-button"
id="downloadButton"
aria-label="Download now and claim your gift"
>
<img data-src="./assets/gift-cta.png" alt="" class="lazy-image" />
</button>
</div>
</section>
</main>
<div class="toast" id="pageToast" aria-live="polite"></div>
<script type="module" src="./app.js"></script>
</body>
</html>