相关h5
BIN
activity/cp/assets/avatar-sample.png
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
activity/cp/assets/countdown-heart.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
activity/cp/assets/crown-bronze.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
activity/cp/assets/crown-gold.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
activity/cp/assets/crown-silver.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
activity/cp/assets/deco-line.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
activity/cp/assets/gift-plane.png
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
activity/cp/assets/hero-bg.png
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
activity/cp/assets/panel-bottom.png
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
activity/cp/assets/panel-mid.png
Normal file
|
After Width: | Height: | Size: 313 KiB |
BIN
activity/cp/assets/panel-top.png
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
activity/cp/assets/rank-crown-gold.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
activity/cp/assets/rank-crown-silver.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
activity/cp/assets/rank-wing-heart.png
Normal file
|
After Width: | Height: | Size: 979 KiB |
BIN
activity/cp/assets/reward-frame.png
Normal file
|
After Width: | Height: | Size: 167 KiB |
BIN
activity/cp/assets/score-heart.png
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
activity/cp/assets/tab-active.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
activity/cp/assets/tab-inactive.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
109
activity/cp/index.html
Normal file
@ -0,0 +1,109 @@
|
||||
<!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>Last Weekly CP</title>
|
||||
<link rel="stylesheet" href="../../common/theme.css?v=20260608-cp" />
|
||||
<link rel="stylesheet" href="./style.css?v=20260608-cp" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-viewport" id="appViewport">
|
||||
<main class="page" id="page" data-tab="plaza">
|
||||
<div class="stage-wrap" id="stageWrap">
|
||||
<section
|
||||
class="stage"
|
||||
aria-label="Last Weekly CP activity"
|
||||
data-i18n-aria="cp.pageLabel"
|
||||
>
|
||||
<div class="language-switcher">
|
||||
<button
|
||||
class="language-button"
|
||||
type="button"
|
||||
aria-label="Change language"
|
||||
aria-expanded="false"
|
||||
data-language-toggle
|
||||
data-current-lang
|
||||
data-i18n-aria="cp.changeLanguage"
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
<div class="language-menu" data-language-menu hidden>
|
||||
<button type="button" data-lang-option="en">EN</button>
|
||||
<button type="button" data-lang-option="ar">AR</button>
|
||||
<button type="button" data-lang-option="tr">TR</button>
|
||||
<button type="button" data-lang-option="es">ES</button>
|
||||
<button type="button" data-lang-option="zh">ZH</button>
|
||||
<button type="button" data-lang-option="id">ID</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img class="hero-bg" src="./assets/hero-bg.png" alt="" aria-hidden="true" />
|
||||
|
||||
<section class="countdown" aria-label="Activity countdown">
|
||||
<div class="time-unit">
|
||||
<img src="./assets/countdown-heart.png" alt="" aria-hidden="true" />
|
||||
<strong data-count="days">02</strong>
|
||||
<span data-i18n="cp.days">D</span>
|
||||
</div>
|
||||
<div class="time-unit">
|
||||
<img src="./assets/countdown-heart.png" alt="" aria-hidden="true" />
|
||||
<strong data-count="hours">02</strong>
|
||||
<span data-i18n="cp.hours">H</span>
|
||||
</div>
|
||||
<div class="time-unit">
|
||||
<img src="./assets/countdown-heart.png" alt="" aria-hidden="true" />
|
||||
<strong data-count="minutes">02</strong>
|
||||
<span data-i18n="cp.minutes">M</span>
|
||||
</div>
|
||||
<div class="time-unit">
|
||||
<img src="./assets/countdown-heart.png" alt="" aria-hidden="true" />
|
||||
<strong data-count="seconds">02</strong>
|
||||
<span data-i18n="cp.seconds">S</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<nav class="tabs" aria-label="CP activity tabs" data-i18n-aria="cp.tabsLabel">
|
||||
<button class="tab is-active" type="button" data-tab="plaza">
|
||||
<img src="./assets/tab-active.png" alt="" aria-hidden="true" />
|
||||
<span data-i18n="cp.tabPlaza">CP Plaza</span>
|
||||
</button>
|
||||
<button class="tab" type="button" data-tab="rank">
|
||||
<img src="./assets/tab-inactive.png" alt="" aria-hidden="true" />
|
||||
<span data-i18n="cp.tabRank">CP Rank</span>
|
||||
</button>
|
||||
<button class="tab" type="button" data-tab="rewards">
|
||||
<img src="./assets/tab-inactive.png" alt="" aria-hidden="true" />
|
||||
<span data-i18n="cp.tabRewards">Rewards</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="tab-content" id="tabContent"></div>
|
||||
<footer class="copyright" data-i18n="cp.copyright">
|
||||
The copyright of this activity belongs to Shila
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
try {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
if (!params.get('lang') && !window.localStorage.getItem('cp_activity_lang_ready')) {
|
||||
window.localStorage.setItem('hyapp_h5_lang', 'en');
|
||||
window.localStorage.setItem('cp_activity_lang_ready', '1');
|
||||
}
|
||||
} catch (error) {
|
||||
document.documentElement.lang = 'en';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script src="../../common/i18n.js?v=20260608-cp"></script>
|
||||
<script src="./script.js?v=20260608-cp"></script>
|
||||
</body>
|
||||
</html>
|
||||
370
activity/cp/script.js
Normal file
@ -0,0 +1,370 @@
|
||||
(function () {
|
||||
var DESIGN_WIDTH = 1080;
|
||||
var VIEWPORT_WIDTH = 375;
|
||||
var STORAGE_KEY = 'hy_cp_activity_end_at';
|
||||
var stageHeights = {
|
||||
plaza: 4283,
|
||||
rank: 3670,
|
||||
rewards: 4797,
|
||||
};
|
||||
var activeTab = 'plaza';
|
||||
var countdownEndAt = 0;
|
||||
var countdownTimer = 0;
|
||||
|
||||
var viewport = document.getElementById('appViewport');
|
||||
var page = document.getElementById('page');
|
||||
var stage = document.querySelector('.stage');
|
||||
var stageWrap = document.getElementById('stageWrap');
|
||||
var tabContent = document.getElementById('tabContent');
|
||||
var copyright = document.querySelector('.copyright');
|
||||
var tabs = Array.prototype.slice.call(document.querySelectorAll('.tab'));
|
||||
|
||||
var mockData = {
|
||||
plaza: {
|
||||
exchangeRows: buildRows('gift', 4),
|
||||
previousRows: buildRows('score', 4),
|
||||
},
|
||||
rank: [
|
||||
{ place: 1, score: '1,234,567', leftName: 'Namename', rightName: 'Namename' },
|
||||
{ place: 2, score: '987,654', leftName: 'Namename', rightName: 'Namename' },
|
||||
{ place: 3, score: '876,543', leftName: 'Namename', rightName: 'Namename' },
|
||||
],
|
||||
rewards: [
|
||||
{
|
||||
crown: 'crown-gold.png',
|
||||
items: [
|
||||
{ key: 'cp.rewardMedal7', noteKey: 'cp.rewardMedalNote' },
|
||||
{ key: 'cp.rewardHonorBanner7' },
|
||||
{ key: 'cp.rewardHeadwear7' },
|
||||
{ key: 'cp.rewardMount7' },
|
||||
{ key: 'cp.rewardCoins20000' },
|
||||
{ key: 'cp.rewardDuke7' },
|
||||
],
|
||||
},
|
||||
{
|
||||
crown: 'crown-silver.png',
|
||||
items: [
|
||||
{ key: 'cp.rewardMedal7', noteKey: 'cp.rewardMedalNote' },
|
||||
{ key: 'cp.rewardHonorBanner7' },
|
||||
{ key: 'cp.rewardMount7' },
|
||||
{ key: 'cp.rewardCoins20000' },
|
||||
{ key: 'cp.rewardMarquis7' },
|
||||
],
|
||||
},
|
||||
{
|
||||
crown: 'crown-bronze.png',
|
||||
items: [
|
||||
{ key: 'cp.rewardMedal7', noteKey: 'cp.rewardMedalNote' },
|
||||
{ key: 'cp.rewardHonorBanner7' },
|
||||
{ key: 'cp.rewardCoins10000' },
|
||||
{ key: 'cp.rewardCount7' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function buildRows(type, count) {
|
||||
var rows = [];
|
||||
for (var index = 0; index < count; index += 1) {
|
||||
rows.push({
|
||||
type: type,
|
||||
leftName: 'Namename',
|
||||
rightName: 'Namename',
|
||||
score: '1234567',
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function t(key, fallback) {
|
||||
if (window.HyAppI18n && window.HyAppI18n.t) {
|
||||
return window.HyAppI18n.t(key, fallback);
|
||||
}
|
||||
return fallback || key;
|
||||
}
|
||||
|
||||
function escapeHTML(value) {
|
||||
return String(value == null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function twoDigits(value) {
|
||||
return String(Math.max(0, value)).padStart(2, '0');
|
||||
}
|
||||
|
||||
function resolveFallbackEndAt() {
|
||||
var params = new URLSearchParams(window.location.search || '');
|
||||
var explicitEnd = Date.parse(params.get('end_at') || params.get('endAt') || '');
|
||||
if (Number.isFinite(explicitEnd) && explicitEnd > Date.now()) return explicitEnd;
|
||||
|
||||
try {
|
||||
var cached = Number(window.localStorage.getItem(STORAGE_KEY));
|
||||
if (Number.isFinite(cached) && cached > Date.now()) return cached;
|
||||
var fallback = Date.now() + 2 * 86400 * 1000 + 2 * 3600 * 1000 + 2 * 60 * 1000 + 2 * 1000;
|
||||
window.localStorage.setItem(STORAGE_KEY, String(fallback));
|
||||
return fallback;
|
||||
} catch (error) {
|
||||
return Date.now() + 2 * 86400 * 1000 + 2 * 3600 * 1000 + 2 * 60 * 1000 + 2 * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
function getTimeParts() {
|
||||
var diff = Math.max(0, countdownEndAt - Date.now());
|
||||
var totalSeconds = Math.floor(diff / 1000);
|
||||
return {
|
||||
days: Math.floor(totalSeconds / 86400),
|
||||
hours: Math.floor((totalSeconds % 86400) / 3600),
|
||||
minutes: Math.floor((totalSeconds % 3600) / 60),
|
||||
seconds: totalSeconds % 60,
|
||||
};
|
||||
}
|
||||
|
||||
function updateCountdown() {
|
||||
var parts = getTimeParts();
|
||||
Object.keys(parts).forEach(function (key) {
|
||||
document.querySelectorAll('[data-count="' + key + '"]').forEach(function (node) {
|
||||
node.textContent = twoDigits(parts[key]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderPanel(className, top, height, bodyHTML) {
|
||||
return [
|
||||
'<section class="ornate-panel ',
|
||||
className,
|
||||
'" style="top:',
|
||||
top,
|
||||
'px;height:',
|
||||
height,
|
||||
'px">',
|
||||
'<img class="panel-top" src="./assets/panel-top.png" alt="" aria-hidden="true" />',
|
||||
'<div class="panel-mid" aria-hidden="true"></div>',
|
||||
'<img class="panel-bottom" src="./assets/panel-bottom.png" alt="" aria-hidden="true" />',
|
||||
'<div class="panel-body">',
|
||||
bodyHTML,
|
||||
'</div>',
|
||||
'</section>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderCPRow(row) {
|
||||
var center = row.type === 'score'
|
||||
? [
|
||||
'<img class="score-heart" src="./assets/score-heart.png" alt="" aria-hidden="true" />',
|
||||
'<strong class="score-value">',
|
||||
escapeHTML(row.score),
|
||||
'</strong>',
|
||||
].join('')
|
||||
: [
|
||||
'<div class="gift-center">',
|
||||
'<img src="./assets/gift-plane.png" alt="" aria-hidden="true" />',
|
||||
'</div>',
|
||||
].join('');
|
||||
|
||||
return [
|
||||
'<article class="cp-row">',
|
||||
'<div class="avatar avatar-left"><img src="./assets/avatar-sample.png" alt="" /></div>',
|
||||
'<div class="avatar avatar-right"><img src="./assets/avatar-sample.png" alt="" /></div>',
|
||||
'<span class="name name-left">',
|
||||
escapeHTML(row.leftName),
|
||||
'</span>',
|
||||
'<span class="name name-right">',
|
||||
escapeHTML(row.rightName),
|
||||
'</span>',
|
||||
'<img class="deco-line deco-left" src="./assets/deco-line.png" alt="" aria-hidden="true" />',
|
||||
'<img class="deco-line deco-right" src="./assets/deco-line.png" alt="" aria-hidden="true" />',
|
||||
center,
|
||||
'</article>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderPlaza() {
|
||||
var firstBody = [
|
||||
'<h2 class="panel-title">',
|
||||
t('cp.plazaGiftTitle', 'Gifts exchanged between CPs ≥ 5000 gold will be displayed here'),
|
||||
'</h2>',
|
||||
'<p class="panel-subtitle">',
|
||||
t('cp.plazaGiftSubtitle', '(Display the last 10 records, sorted in ascending order.)'),
|
||||
'</p>',
|
||||
'<div class="cp-list">',
|
||||
mockData.plaza.exchangeRows.map(renderCPRow).join(''),
|
||||
'</div>',
|
||||
].join('');
|
||||
var secondBody = [
|
||||
'<h2 class="panel-title">',
|
||||
t('cp.previousNo1Title', 'The previous number one position'),
|
||||
'</h2>',
|
||||
'<p class="panel-subtitle">',
|
||||
t('cp.plazaGiftSubtitle', '(Display the last 10 records, sorted in ascending order.)'),
|
||||
'</p>',
|
||||
'<div class="cp-list">',
|
||||
mockData.plaza.previousRows.map(renderCPRow).join(''),
|
||||
'</div>',
|
||||
].join('');
|
||||
|
||||
return [
|
||||
renderPanel('plaza-panel-a', 1816, 1152, firstBody),
|
||||
renderPanel('plaza-panel-b', 3024, 1126, secondBody),
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderInlineCountdown() {
|
||||
var units = [
|
||||
['days', 'cp.days', 'D'],
|
||||
['hours', 'cp.hours', 'H'],
|
||||
['minutes', 'cp.minutes', 'M'],
|
||||
['seconds', 'cp.seconds', 'S'],
|
||||
];
|
||||
return [
|
||||
'<div class="countdown-inline">',
|
||||
units
|
||||
.map(function (unit) {
|
||||
return [
|
||||
'<div class="time-unit">',
|
||||
'<img src="./assets/countdown-heart.png" alt="" aria-hidden="true" />',
|
||||
'<strong data-count="',
|
||||
unit[0],
|
||||
'">02</strong>',
|
||||
'<span>',
|
||||
t(unit[1], unit[2]),
|
||||
'</span>',
|
||||
'</div>',
|
||||
].join('');
|
||||
})
|
||||
.join(''),
|
||||
'</div>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderPodiumCard(item) {
|
||||
var crown = item.place === 1 ? 'rank-crown-gold.png' : item.place === 2 ? 'rank-crown-silver.png' : 'crown-bronze.png';
|
||||
return [
|
||||
'<article class="podium-card rank-',
|
||||
item.place,
|
||||
'">',
|
||||
'<img class="podium-bg" src="./assets/rank-wing-heart.png" alt="" aria-hidden="true" />',
|
||||
'<img class="podium-crown" src="./assets/',
|
||||
crown,
|
||||
'" alt="" aria-hidden="true" />',
|
||||
'<div class="avatar podium-avatar left"><img src="./assets/avatar-sample.png" alt="" /></div>',
|
||||
'<div class="avatar podium-avatar right"><img src="./assets/avatar-sample.png" alt="" /></div>',
|
||||
'<div class="podium-names"><span>',
|
||||
escapeHTML(item.leftName),
|
||||
'</span><span>',
|
||||
escapeHTML(item.rightName),
|
||||
'</span></div>',
|
||||
'</article>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderRankRow(item) {
|
||||
return renderCPRow({
|
||||
type: 'score',
|
||||
leftName: item.leftName,
|
||||
rightName: item.rightName,
|
||||
score: item.score,
|
||||
});
|
||||
}
|
||||
|
||||
function renderRank() {
|
||||
var podium = [
|
||||
'<section class="rank-podium">',
|
||||
mockData.rank.map(renderPodiumCard).join(''),
|
||||
'</section>',
|
||||
].join('');
|
||||
var panelBody = [renderInlineCountdown(), '<div class="cp-list">', mockData.rank.concat(mockData.rank.slice(0, 1)).map(renderRankRow).join(''), '</div>'].join('');
|
||||
return podium + renderPanel('rank-panel', 2430, 1107, panelBody);
|
||||
}
|
||||
|
||||
function renderRewardItem(item) {
|
||||
return [
|
||||
'<article class="reward-card">',
|
||||
'<div class="reward-icon-box"><img src="./assets/reward-frame.png" alt="" aria-hidden="true" /></div>',
|
||||
'<div class="reward-name">',
|
||||
t(item.key, item.key),
|
||||
'</div>',
|
||||
item.noteKey ? '<div class="reward-note">' + t(item.noteKey, '') + '</div>' : '',
|
||||
'</article>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderRewardPanel(section, index) {
|
||||
var className = ['reward-panel', 'reward-' + ['a', 'b', 'c'][index]].join(' ');
|
||||
var top = [1816, 2784, 3752][index];
|
||||
var body = [
|
||||
'<img class="reward-crown" src="./assets/',
|
||||
section.crown,
|
||||
'" alt="" aria-hidden="true" />',
|
||||
'<div class="reward-grid">',
|
||||
section.items.map(renderRewardItem).join(''),
|
||||
'</div>',
|
||||
].join('');
|
||||
return renderPanel(className, top, 912, body);
|
||||
}
|
||||
|
||||
function renderRewards() {
|
||||
return mockData.rewards.map(renderRewardPanel).join('');
|
||||
}
|
||||
|
||||
function render() {
|
||||
document.title = t('cp.pageTitle', 'Last Weekly CP');
|
||||
tabContent.innerHTML = activeTab === 'rank' ? renderRank() : activeTab === 'rewards' ? renderRewards() : renderPlaza();
|
||||
applyTabState();
|
||||
updateStageHeight();
|
||||
updateCountdown();
|
||||
}
|
||||
|
||||
function applyTabState() {
|
||||
tabs.forEach(function (tab) {
|
||||
var isActive = tab.getAttribute('data-tab') === activeTab;
|
||||
tab.classList.toggle('is-active', isActive);
|
||||
tab.querySelector('img').src = isActive ? './assets/tab-active.png' : './assets/tab-inactive.png';
|
||||
});
|
||||
page.setAttribute('data-tab', activeTab);
|
||||
}
|
||||
|
||||
function updateStageHeight() {
|
||||
var designHeight = stageHeights[activeTab] || stageHeights.plaza;
|
||||
stage.style.minHeight = designHeight + 'px';
|
||||
stage.style.height = designHeight + 'px';
|
||||
stageWrap.style.height = Math.ceil(designHeight * (VIEWPORT_WIDTH / DESIGN_WIDTH)) + 'px';
|
||||
copyright.style.top = designHeight - 77 + 'px';
|
||||
var scale = Math.min(window.innerWidth / VIEWPORT_WIDTH, 1);
|
||||
document.documentElement.style.setProperty('--app-scale', String(scale));
|
||||
document.body.style.minHeight = Math.ceil(stageWrap.offsetHeight * scale) + 'px';
|
||||
viewport.style.minHeight = stageWrap.style.height;
|
||||
page.style.minHeight = stageWrap.style.height;
|
||||
}
|
||||
|
||||
function bindTabs() {
|
||||
tabs.forEach(function (tab) {
|
||||
tab.addEventListener('click', function () {
|
||||
activeTab = tab.getAttribute('data-tab') || 'plaza';
|
||||
render();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
countdownEndAt = resolveFallbackEndAt();
|
||||
bindTabs();
|
||||
render();
|
||||
countdownTimer = window.setInterval(updateCountdown, 1000);
|
||||
window.addEventListener('resize', updateStageHeight);
|
||||
window.addEventListener('hyapp:i18n-ready', render);
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', function () {
|
||||
if (countdownTimer) window.clearInterval(countdownTimer);
|
||||
});
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
707
activity/cp/style.css
Normal file
@ -0,0 +1,707 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
:root {
|
||||
--app-scale: 1;
|
||||
--design-scale: 0.3472222222;
|
||||
--cp-red: #c52d45;
|
||||
--cp-dark-red: #8c2130;
|
||||
--cp-panel-text: #a8302b;
|
||||
--cp-panel-sub: #d26984;
|
||||
--cp-cream: #fff2f3;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
overflow-x: hidden;
|
||||
background: #1d080d;
|
||||
color: #fff;
|
||||
font-family:
|
||||
"Source Han Sans SC",
|
||||
"Noto Sans CJK SC",
|
||||
Arial,
|
||||
"Helvetica Neue",
|
||||
Helvetica,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.app-viewport {
|
||||
position: relative;
|
||||
width: 375px;
|
||||
min-height: 1300px;
|
||||
margin: 0 auto;
|
||||
transform: scale(var(--app-scale));
|
||||
transform-origin: top center;
|
||||
}
|
||||
|
||||
.page {
|
||||
position: relative;
|
||||
width: 375px;
|
||||
min-height: 1300px;
|
||||
overflow: hidden;
|
||||
background: #1d080d;
|
||||
}
|
||||
|
||||
.stage-wrap {
|
||||
position: relative;
|
||||
width: 375px;
|
||||
height: 1487px;
|
||||
overflow: hidden;
|
||||
background: var(--cp-red);
|
||||
}
|
||||
|
||||
.stage {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 1080px;
|
||||
min-height: 4283px;
|
||||
overflow: hidden;
|
||||
transform: scale(var(--design-scale));
|
||||
transform-origin: top left;
|
||||
background: var(--cp-red);
|
||||
}
|
||||
|
||||
.stage > * {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.hero-bg {
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
width: 1080px;
|
||||
height: 1919px;
|
||||
object-fit: cover;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.language-switcher {
|
||||
z-index: 20;
|
||||
top: 38px;
|
||||
right: 36px;
|
||||
width: 188px;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.language-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 150px;
|
||||
height: 78px;
|
||||
margin-left: auto;
|
||||
border: 2px solid rgba(255, 236, 238, 0.72);
|
||||
border-radius: 999px;
|
||||
background: rgba(152, 30, 51, 0.52);
|
||||
box-shadow:
|
||||
inset 0 0 16px rgba(255, 210, 220, 0.3),
|
||||
0 10px 20px rgba(78, 4, 16, 0.28);
|
||||
color: #fff;
|
||||
font-size: 32px;
|
||||
font-weight: 900;
|
||||
line-height: 32px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.language-menu {
|
||||
position: absolute;
|
||||
top: 92px;
|
||||
right: 0;
|
||||
z-index: 22;
|
||||
width: 188px;
|
||||
overflow: hidden;
|
||||
border: 2px solid rgba(255, 236, 238, 0.7);
|
||||
border-radius: 18px;
|
||||
background: rgba(121, 14, 33, 0.94);
|
||||
box-shadow: 0 18px 32px rgba(87, 1, 17, 0.36);
|
||||
}
|
||||
|
||||
.language-menu button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 58px;
|
||||
color: #fff0f3;
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.language-menu button.is-active {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: #ffe184;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
left: 48px;
|
||||
top: 1521px;
|
||||
z-index: 2;
|
||||
width: 974px;
|
||||
height: 106px;
|
||||
}
|
||||
|
||||
.time-unit {
|
||||
position: relative;
|
||||
width: 166px;
|
||||
height: 106px;
|
||||
}
|
||||
|
||||
.countdown > .time-unit {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.countdown > .time-unit:nth-child(1) {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.countdown > .time-unit:nth-child(2) {
|
||||
left: 250px;
|
||||
}
|
||||
|
||||
.countdown > .time-unit:nth-child(3) {
|
||||
left: 501px;
|
||||
}
|
||||
|
||||
.countdown > .time-unit:nth-child(4) {
|
||||
left: 756px;
|
||||
}
|
||||
|
||||
.time-unit img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 166px;
|
||||
height: 106px;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.time-unit strong {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 23px;
|
||||
width: 166px;
|
||||
color: #fff;
|
||||
font-size: 40px;
|
||||
font-weight: 900;
|
||||
line-height: 42px;
|
||||
text-align: center;
|
||||
text-shadow:
|
||||
0 2px 0 rgba(119, 16, 30, 0.6),
|
||||
0 0 12px rgba(255, 255, 255, 0.46);
|
||||
}
|
||||
|
||||
.time-unit span {
|
||||
position: absolute;
|
||||
left: 193px;
|
||||
top: 25px;
|
||||
width: 42px;
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
font-weight: 900;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 0 rgba(116, 9, 27, 0.62);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
left: 40px;
|
||||
top: 1683px;
|
||||
z-index: 4;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 303px);
|
||||
column-gap: 46px;
|
||||
width: 1000px;
|
||||
height: 93px;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.tab {
|
||||
position: relative;
|
||||
width: 303px;
|
||||
height: 93px;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
font-size: 40px;
|
||||
font-weight: 900;
|
||||
line-height: 44px;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 0 rgba(105, 30, 36, 0.55);
|
||||
}
|
||||
|
||||
.tab img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 303px;
|
||||
height: 93px;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.tab span {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 25px;
|
||||
width: 303px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.tab.is-active {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
width: 1080px;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
left: 114px;
|
||||
z-index: 3;
|
||||
width: 850px;
|
||||
height: 30px;
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ornate-panel {
|
||||
position: absolute;
|
||||
left: 60px;
|
||||
width: 960px;
|
||||
overflow: hidden;
|
||||
border: 8px solid #f62658;
|
||||
border-radius: 48px;
|
||||
background:
|
||||
radial-gradient(circle at 50% 0%, rgba(255, 255, 255, 0.98) 0, rgba(255, 255, 255, 0.86) 23%, rgba(255, 232, 236, 0.92) 100%),
|
||||
linear-gradient(180deg, #fff8f8 0%, #ffe6eb 100%);
|
||||
box-shadow:
|
||||
inset 0 0 0 4px rgba(255, 204, 119, 0.95),
|
||||
inset 0 0 0 12px rgba(255, 255, 255, 0.82),
|
||||
inset 0 0 24px rgba(255, 75, 113, 0.22),
|
||||
0 0 0 2px rgba(255, 237, 180, 0.92),
|
||||
0 0 16px rgba(255, 34, 87, 0.45);
|
||||
color: var(--cp-panel-text);
|
||||
}
|
||||
|
||||
.ornate-panel::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ornate-panel::after {
|
||||
left: 70px;
|
||||
right: 70px;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
height: 14px;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 241, 177, 0.95), transparent);
|
||||
filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8));
|
||||
}
|
||||
|
||||
.panel-top,
|
||||
.panel-bottom,
|
||||
.panel-mid {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.panel-top {
|
||||
top: 0;
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.panel-bottom {
|
||||
bottom: 0;
|
||||
height: 86px;
|
||||
}
|
||||
|
||||
.panel-mid {
|
||||
top: 89px;
|
||||
bottom: 85px;
|
||||
background-image: url("./assets/panel-mid.png");
|
||||
background-size: 960px 184px;
|
||||
background-repeat: repeat-y;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.plaza-panel-a {
|
||||
top: 1816px;
|
||||
height: 1152px;
|
||||
}
|
||||
|
||||
.plaza-panel-b {
|
||||
top: 3024px;
|
||||
height: 1126px;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
position: absolute;
|
||||
left: 55px;
|
||||
top: 70px;
|
||||
width: 850px;
|
||||
margin: 0;
|
||||
color: var(--cp-panel-text);
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
line-height: 46px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.panel-subtitle {
|
||||
position: absolute;
|
||||
left: 55px;
|
||||
top: 161px;
|
||||
width: 850px;
|
||||
margin: 0;
|
||||
color: var(--cp-panel-sub);
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cp-list {
|
||||
position: absolute;
|
||||
left: 60px;
|
||||
top: 224px;
|
||||
display: grid;
|
||||
row-gap: 24px;
|
||||
width: 840px;
|
||||
}
|
||||
|
||||
.cp-row {
|
||||
position: relative;
|
||||
width: 840px;
|
||||
height: 199px;
|
||||
border-radius: 24px;
|
||||
background: rgba(197, 45, 69, 0.12);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
background: #ffd6df;
|
||||
}
|
||||
|
||||
.avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.cp-row .avatar {
|
||||
top: 28px;
|
||||
width: 113px;
|
||||
height: 113px;
|
||||
}
|
||||
|
||||
.cp-row .avatar-left {
|
||||
left: 42px;
|
||||
}
|
||||
|
||||
.cp-row .avatar-right {
|
||||
right: 42px;
|
||||
}
|
||||
|
||||
.cp-row .name {
|
||||
position: absolute;
|
||||
top: 146px;
|
||||
width: 144px;
|
||||
overflow: hidden;
|
||||
color: #9f1c32;
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.cp-row .name-left {
|
||||
left: 42px;
|
||||
}
|
||||
|
||||
.cp-row .name-right {
|
||||
right: 42px;
|
||||
}
|
||||
|
||||
.gift-center {
|
||||
position: absolute;
|
||||
left: 344px;
|
||||
top: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 144px;
|
||||
height: 144px;
|
||||
}
|
||||
|
||||
.gift-center img {
|
||||
max-width: 152px;
|
||||
max-height: 152px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.score-heart {
|
||||
position: absolute;
|
||||
left: 308px;
|
||||
top: 28px;
|
||||
width: 222px;
|
||||
height: 143px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.score-value {
|
||||
position: absolute;
|
||||
left: 359px;
|
||||
top: 71px;
|
||||
width: 120px;
|
||||
height: 36px;
|
||||
color: #fff7f7;
|
||||
font-size: 30px;
|
||||
font-weight: 900;
|
||||
line-height: 36px;
|
||||
text-align: center;
|
||||
text-shadow:
|
||||
0 2px 0 #a51f32,
|
||||
0 0 8px rgba(255, 255, 255, 0.32);
|
||||
}
|
||||
|
||||
.deco-line {
|
||||
position: absolute;
|
||||
top: 70px;
|
||||
width: 159px;
|
||||
height: 30px;
|
||||
object-fit: fill;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.deco-left {
|
||||
left: 252px;
|
||||
}
|
||||
|
||||
.deco-right {
|
||||
right: 252px;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.rank-podium {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 1816px;
|
||||
width: 1080px;
|
||||
height: 580px;
|
||||
}
|
||||
|
||||
.podium-card {
|
||||
position: absolute;
|
||||
width: 716px;
|
||||
height: 288px;
|
||||
}
|
||||
|
||||
.podium-card.rank-1 {
|
||||
left: 182px;
|
||||
top: 14px;
|
||||
}
|
||||
|
||||
.podium-card.rank-2 {
|
||||
left: -34px;
|
||||
top: 332px;
|
||||
transform: scale(0.77);
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
.podium-card.rank-3 {
|
||||
left: 565px;
|
||||
top: 332px;
|
||||
transform: scale(0.77);
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
.podium-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 716px;
|
||||
height: 288px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.podium-crown {
|
||||
position: absolute;
|
||||
left: 310px;
|
||||
top: -8px;
|
||||
width: 120px;
|
||||
height: 106px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.podium-card.rank-2 .podium-crown,
|
||||
.podium-card.rank-3 .podium-crown {
|
||||
top: -2px;
|
||||
width: 104px;
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.podium-avatar {
|
||||
position: absolute;
|
||||
top: 92px;
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
border: 5px solid #ffd5a3;
|
||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.42);
|
||||
}
|
||||
|
||||
.podium-avatar.left {
|
||||
left: 236px;
|
||||
}
|
||||
|
||||
.podium-avatar.right {
|
||||
left: 386px;
|
||||
}
|
||||
|
||||
.podium-names {
|
||||
position: absolute;
|
||||
left: 146px;
|
||||
top: 214px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 168px);
|
||||
column-gap: 64px;
|
||||
width: 400px;
|
||||
color: #fff3f5;
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 0 rgba(126, 15, 31, 0.62);
|
||||
}
|
||||
|
||||
.rank-panel {
|
||||
top: 2430px;
|
||||
height: 1107px;
|
||||
}
|
||||
|
||||
.rank-panel .countdown-inline {
|
||||
position: absolute;
|
||||
left: 110px;
|
||||
top: 64px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 166px);
|
||||
column-gap: 44px;
|
||||
width: 830px;
|
||||
}
|
||||
|
||||
.rank-panel .cp-list {
|
||||
top: 252px;
|
||||
}
|
||||
|
||||
.reward-panel {
|
||||
height: 912px;
|
||||
}
|
||||
|
||||
.reward-panel.reward-a {
|
||||
top: 1816px;
|
||||
}
|
||||
|
||||
.reward-panel.reward-b {
|
||||
top: 2784px;
|
||||
}
|
||||
|
||||
.reward-panel.reward-c {
|
||||
top: 3752px;
|
||||
}
|
||||
|
||||
.reward-crown {
|
||||
position: absolute;
|
||||
left: 390px;
|
||||
top: 58px;
|
||||
width: 180px;
|
||||
height: 158px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.reward-grid {
|
||||
position: absolute;
|
||||
left: 106px;
|
||||
top: 220px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 217px);
|
||||
gap: 24px 84px;
|
||||
width: 750px;
|
||||
}
|
||||
|
||||
.reward-card {
|
||||
position: relative;
|
||||
width: 217px;
|
||||
min-height: 264px;
|
||||
color: #9f1c32;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reward-icon-box {
|
||||
position: relative;
|
||||
width: 217px;
|
||||
height: 217px;
|
||||
border-radius: 24px;
|
||||
background: rgba(197, 45, 69, 0.12);
|
||||
}
|
||||
|
||||
.reward-card img {
|
||||
position: absolute;
|
||||
left: 9px;
|
||||
top: 9px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.reward-name {
|
||||
margin-top: 10px;
|
||||
color: #9f1c32;
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.reward-note {
|
||||
color: var(--cp-panel-sub);
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .tab span,
|
||||
html[dir="rtl"] .panel-title,
|
||||
html[dir="rtl"] .panel-subtitle,
|
||||
html[dir="rtl"] .copyright,
|
||||
html[dir="rtl"] .reward-name,
|
||||
html[dir="rtl"] .reward-note {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .language-switcher {
|
||||
direction: ltr;
|
||||
}
|
||||
@ -106,50 +106,50 @@
|
||||
<div class="table-content" id="tierTable"></div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="user-panel"
|
||||
aria-label="My room reward progress"
|
||||
data-i18n-aria="roomReward.progressLabel"
|
||||
>
|
||||
<img class="user-avatar" id="userAvatar" src="./assets/294_8091.png" alt="" />
|
||||
<div class="user-name" id="userName">Namename...</div>
|
||||
<div class="user-id" id="userId">ID: 123456789</div>
|
||||
<div class="metric-label contribution-label" data-i18n="roomReward.contribution">
|
||||
This week's contribution:
|
||||
</div>
|
||||
<div class="metric-label rewards-label" data-i18n="roomReward.rewards">
|
||||
This week's rewards:
|
||||
</div>
|
||||
<button
|
||||
class="reward-button is-active"
|
||||
id="contributionButton"
|
||||
type="button"
|
||||
aria-label="Contribution reward"
|
||||
data-i18n-aria="roomReward.contributionReward"
|
||||
data-active-src="./assets/296_8424.png"
|
||||
data-inactive-src="./assets/296_8429.png"
|
||||
>
|
||||
<img src="./assets/296_8424.png" alt="" aria-hidden="true" />
|
||||
<span id="contributionValue">100</span>
|
||||
</button>
|
||||
<button
|
||||
class="reward-button is-disabled"
|
||||
id="rewardsButton"
|
||||
type="button"
|
||||
aria-label="Weekly rewards"
|
||||
data-i18n-aria="roomReward.weeklyRewards"
|
||||
disabled
|
||||
data-active-src="./assets/296_8424.png"
|
||||
data-inactive-src="./assets/296_8429.png"
|
||||
>
|
||||
<img src="./assets/296_8429.png" alt="" aria-hidden="true" />
|
||||
<span id="weeklyRewardValue">100</span>
|
||||
</button>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<section
|
||||
class="user-panel"
|
||||
aria-label="My room reward progress"
|
||||
data-i18n-aria="roomReward.progressLabel"
|
||||
>
|
||||
<img class="user-avatar" id="userAvatar" src="./assets/294_8091.png" alt="" />
|
||||
<div class="user-name" id="userName">Namename...</div>
|
||||
<div class="user-id" id="userId">ID: 123456789</div>
|
||||
<div class="metric-label contribution-label" data-i18n="roomReward.contribution">
|
||||
This week's contribution:
|
||||
</div>
|
||||
<div class="metric-label rewards-label" data-i18n="roomReward.rewards">
|
||||
This week's rewards:
|
||||
</div>
|
||||
<button
|
||||
class="reward-button is-active"
|
||||
id="contributionButton"
|
||||
type="button"
|
||||
aria-label="Contribution reward"
|
||||
data-i18n-aria="roomReward.contributionReward"
|
||||
data-active-src="./assets/296_8424.png"
|
||||
data-inactive-src="./assets/296_8429.png"
|
||||
>
|
||||
<img src="./assets/296_8424.png" alt="" aria-hidden="true" />
|
||||
<span id="contributionValue">100</span>
|
||||
</button>
|
||||
<button
|
||||
class="reward-button is-disabled"
|
||||
id="rewardsButton"
|
||||
type="button"
|
||||
aria-label="Weekly rewards"
|
||||
data-i18n-aria="roomReward.weeklyRewards"
|
||||
disabled
|
||||
data-active-src="./assets/296_8424.png"
|
||||
data-inactive-src="./assets/296_8429.png"
|
||||
>
|
||||
<img src="./assets/296_8429.png" alt="" aria-hidden="true" />
|
||||
<span id="weeklyRewardValue">100</span>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
|
||||
@ -120,9 +120,10 @@
|
||||
}
|
||||
|
||||
function updateBodyHeight() {
|
||||
var scale = Math.min(window.innerWidth / 375, 1);
|
||||
var scale = window.innerWidth / 375;
|
||||
document.documentElement.style.setProperty('--app-scale', String(scale));
|
||||
document.body.style.minHeight = viewport.offsetHeight * scale + 'px';
|
||||
document.body.style.minHeight =
|
||||
Math.ceil(viewport.offsetHeight * scale) + 'px';
|
||||
}
|
||||
|
||||
function readQuery(name) {
|
||||
|
||||
@ -14,6 +14,7 @@ body {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background: #291000;
|
||||
color: #fff;
|
||||
font-family:
|
||||
@ -21,6 +22,7 @@ body {
|
||||
"Helvetica Neue",
|
||||
Helvetica,
|
||||
sans-serif;
|
||||
padding-bottom: calc((169 / 1080) * 100vw + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
button {
|
||||
@ -49,15 +51,15 @@ button {
|
||||
position: relative;
|
||||
width: 375px;
|
||||
min-height: 772px;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
background: #291000;
|
||||
}
|
||||
|
||||
.stage-wrap {
|
||||
position: relative;
|
||||
width: 375px;
|
||||
height: 772px;
|
||||
overflow: hidden;
|
||||
height: 830px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.stage {
|
||||
@ -65,7 +67,7 @@ button {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 1080px;
|
||||
height: 2221px;
|
||||
height: 2388px;
|
||||
overflow: hidden;
|
||||
transform: scale(var(--design-scale));
|
||||
transform-origin: top left;
|
||||
@ -297,34 +299,37 @@ button {
|
||||
}
|
||||
|
||||
.user-panel {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
z-index: 80;
|
||||
left: 0;
|
||||
top: 2052px;
|
||||
width: 1080px;
|
||||
height: 169px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100vw;
|
||||
height: calc((169 / 1080) * 100vw + env(safe-area-inset-bottom, 0px));
|
||||
background: #190901;
|
||||
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
position: absolute;
|
||||
left: 46px;
|
||||
top: 25px;
|
||||
width: 119px;
|
||||
height: 119px;
|
||||
left: calc((46 / 1080) * 100vw);
|
||||
top: calc((25 / 1080) * 100vw);
|
||||
width: calc((119 / 1080) * 100vw);
|
||||
height: calc((119 / 1080) * 100vw);
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
position: absolute;
|
||||
left: 189px;
|
||||
top: 36px;
|
||||
width: 280px;
|
||||
left: calc((189 / 1080) * 100vw);
|
||||
top: calc((36 / 1080) * 100vw);
|
||||
width: calc((280 / 1080) * 100vw);
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
font-size: 40px;
|
||||
font-size: calc((40 / 1080) * 100vw);
|
||||
font-weight: 500;
|
||||
line-height: 42px;
|
||||
line-height: calc((42 / 1080) * 100vw);
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
@ -332,14 +337,14 @@ button {
|
||||
|
||||
.user-id {
|
||||
position: absolute;
|
||||
left: 189px;
|
||||
top: 102px;
|
||||
width: 292px;
|
||||
left: calc((189 / 1080) * 100vw);
|
||||
top: calc((102 / 1080) * 100vw);
|
||||
width: calc((292 / 1080) * 100vw);
|
||||
overflow: hidden;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 30px;
|
||||
font-size: calc((30 / 1080) * 100vw);
|
||||
font-weight: 400;
|
||||
line-height: 36px;
|
||||
line-height: calc((36 / 1080) * 100vw);
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
@ -349,30 +354,30 @@ button {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
font-size: 28px;
|
||||
font-size: calc((28 / 1080) * 100vw);
|
||||
font-weight: 400;
|
||||
line-height: 32px;
|
||||
line-height: calc((32 / 1080) * 100vw);
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.contribution-label {
|
||||
left: 494px;
|
||||
top: 43px;
|
||||
width: 328px;
|
||||
left: calc((494 / 1080) * 100vw);
|
||||
top: calc((43 / 1080) * 100vw);
|
||||
width: calc((328 / 1080) * 100vw);
|
||||
}
|
||||
|
||||
.rewards-label {
|
||||
left: 552px;
|
||||
top: 111px;
|
||||
width: 270px;
|
||||
left: calc((552 / 1080) * 100vw);
|
||||
top: calc((111 / 1080) * 100vw);
|
||||
width: calc((270 / 1080) * 100vw);
|
||||
}
|
||||
|
||||
.reward-button {
|
||||
position: absolute;
|
||||
width: 213px;
|
||||
height: 69px;
|
||||
width: calc((213 / 1080) * 100vw);
|
||||
height: calc((69 / 1080) * 100vw);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -394,21 +399,21 @@ button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
font-size: calc((24 / 1080) * 100vw);
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
line-height: calc((24 / 1080) * 100vw);
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#contributionButton {
|
||||
left: 838px;
|
||||
top: 19px;
|
||||
left: calc((838 / 1080) * 100vw);
|
||||
top: calc((19 / 1080) * 100vw);
|
||||
}
|
||||
|
||||
#rewardsButton {
|
||||
left: 837px;
|
||||
top: 87px;
|
||||
left: calc((837 / 1080) * 100vw);
|
||||
top: calc((87 / 1080) * 100vw);
|
||||
}
|
||||
|
||||
.reward-button.is-disabled {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
(function () {
|
||||
var page = document.getElementById('page');
|
||||
var viewport = document.getElementById('appViewport');
|
||||
var tabs = Array.prototype.slice.call(document.querySelectorAll('[data-tab-target]'));
|
||||
var panels = Array.prototype.slice.call(document.querySelectorAll('[data-panel]'));
|
||||
var stageWrap = document.getElementById('stageWrap');
|
||||
@ -117,7 +116,7 @@
|
||||
input.find(function (item) {
|
||||
return Number(item.rank_no || item.rankNo) === rankNo;
|
||||
}) || {};
|
||||
var items = reward.items || reward.resources || [];
|
||||
var items = normalizeRewardItems(reward);
|
||||
if (!items.length) {
|
||||
items = [
|
||||
{
|
||||
@ -132,6 +131,41 @@
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeRewardItems(reward) {
|
||||
var group = reward.resource_group || reward.resourceGroup || reward.reward_resource_group || reward.rewardResourceGroup || {};
|
||||
var items = reward.items || reward.resources || group.items || [];
|
||||
return (items || []).map(function (item) {
|
||||
var resource = item.resource || {};
|
||||
var quantity = Number(item.quantity || 0);
|
||||
var walletAmount = Number(item.wallet_asset_amount || item.walletAssetAmount || 0);
|
||||
var walletType = item.wallet_asset_type || item.walletAssetType || '';
|
||||
var name =
|
||||
item.label ||
|
||||
resource.name ||
|
||||
item.name ||
|
||||
(walletType ? walletType.replace(/_/g, ' ') : '') ||
|
||||
group.name ||
|
||||
reward.name ||
|
||||
'Reward';
|
||||
var suffix = quantity > 1 ? ' x' + quantity : walletAmount > 0 ? ' x' + formatNumber(walletAmount) : '';
|
||||
return {
|
||||
label: name + suffix,
|
||||
resource_group_id: reward.resource_group_id || reward.resourceGroupId || group.group_id || group.groupId,
|
||||
image:
|
||||
item.image ||
|
||||
item.image_url ||
|
||||
item.imageUrl ||
|
||||
resource.asset_url ||
|
||||
resource.assetUrl ||
|
||||
resource.preview_url ||
|
||||
resource.previewUrl ||
|
||||
resource.animation_url ||
|
||||
resource.animationUrl,
|
||||
single: false,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function renderGiftStrip(gifts) {
|
||||
var cards = Array.prototype.slice.call(document.querySelectorAll('.gift-strip .gift-card'));
|
||||
cards.forEach(function (card, index) {
|
||||
@ -202,7 +236,6 @@
|
||||
panels.forEach(function (panel) {
|
||||
panel.hidden = panel.getAttribute('data-panel') !== next;
|
||||
});
|
||||
stageWrap.style.height = next === 'rewards' ? '1090px' : '802px';
|
||||
updateBodyHeight();
|
||||
}
|
||||
|
||||
@ -212,9 +245,11 @@
|
||||
}
|
||||
|
||||
function updateBodyHeight() {
|
||||
var scale = Math.min(window.innerWidth / 375, 1);
|
||||
document.documentElement.style.setProperty('--app-scale', String(scale));
|
||||
document.body.style.minHeight = viewport.offsetHeight * scale + 'px';
|
||||
var scale = window.innerWidth / 1080;
|
||||
var designHeight = page.getAttribute('data-tab') === 'rewards' ? 3138 : 2310;
|
||||
document.documentElement.style.setProperty('--design-scale', String(scale));
|
||||
stageWrap.style.height = Math.ceil(designHeight * scale) + 'px';
|
||||
document.body.style.minHeight = stageWrap.offsetHeight + 'px';
|
||||
}
|
||||
|
||||
function loadMock() {
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
}
|
||||
|
||||
:root {
|
||||
--app-scale: 1;
|
||||
--design-scale: 0.3472222222;
|
||||
}
|
||||
|
||||
@ -37,16 +36,15 @@ button {
|
||||
|
||||
.app-viewport {
|
||||
position: relative;
|
||||
width: 375px;
|
||||
width: 100vw;
|
||||
min-height: 800px;
|
||||
margin: 0 auto;
|
||||
transform: scale(var(--app-scale));
|
||||
transform-origin: top center;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page {
|
||||
position: relative;
|
||||
width: 375px;
|
||||
width: 100vw;
|
||||
min-height: 800px;
|
||||
overflow: hidden;
|
||||
background: #050a36;
|
||||
@ -54,15 +52,11 @@ button {
|
||||
|
||||
.stage-wrap {
|
||||
position: relative;
|
||||
width: 375px;
|
||||
width: 100vw;
|
||||
height: 802px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page[data-tab="rewards"] .stage-wrap {
|
||||
height: 1090px;
|
||||
}
|
||||
|
||||
.stage {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@ -445,10 +439,3 @@ button {
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
@media (max-width: 374px) {
|
||||
.app-viewport {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -436,6 +436,76 @@ var default_api = 'https://api.global-interaction.com/';
|
||||
},
|
||||
};
|
||||
|
||||
var managerCenterAPI = {
|
||||
overview: function () {
|
||||
return request('/api/v1/manager-center/overview', {
|
||||
method: 'GET',
|
||||
});
|
||||
},
|
||||
searchUsers: function (keyword, pageSize) {
|
||||
return request('/api/v1/manager-center/users/search', {
|
||||
method: 'GET',
|
||||
query: {
|
||||
keyword: keyword,
|
||||
page_size: pageSize || 20,
|
||||
},
|
||||
});
|
||||
},
|
||||
listResources: function (resourceType, pageSize) {
|
||||
return request('/api/v1/manager-center/resource-grants/resources', {
|
||||
method: 'GET',
|
||||
query: {
|
||||
resource_type: resourceType,
|
||||
page_size: pageSize || 20,
|
||||
},
|
||||
});
|
||||
},
|
||||
grantResource: function (payload) {
|
||||
return request('/api/v1/manager-center/resource-grants', {
|
||||
method: 'POST',
|
||||
body: payload || {},
|
||||
});
|
||||
},
|
||||
grantVip: function (payload) {
|
||||
return request('/api/v1/manager-center/vip-grants', {
|
||||
method: 'POST',
|
||||
body: payload || {},
|
||||
});
|
||||
},
|
||||
listBlocks: function (status, pageSize) {
|
||||
return request('/api/v1/manager-center/blocks', {
|
||||
method: 'GET',
|
||||
query: {
|
||||
status: status || 'active',
|
||||
page_size: pageSize || 50,
|
||||
},
|
||||
});
|
||||
},
|
||||
blockUser: function (payload) {
|
||||
return request('/api/v1/manager-center/blocks', {
|
||||
method: 'POST',
|
||||
body: payload || {},
|
||||
});
|
||||
},
|
||||
unblockUser: function (blockId, payload) {
|
||||
return request(
|
||||
'/api/v1/manager-center/blocks/' +
|
||||
encodeURIComponent(blockId || '') +
|
||||
'/unblock',
|
||||
{
|
||||
method: 'POST',
|
||||
body: payload || {},
|
||||
}
|
||||
);
|
||||
},
|
||||
updateLevel: function (payload) {
|
||||
return request('/api/v1/manager-center/levels', {
|
||||
method: 'POST',
|
||||
body: payload || {},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
var superadminCenterAPI = bdLeaderCenterAPI;
|
||||
|
||||
var levelAPI = {
|
||||
@ -510,6 +580,7 @@ var default_api = 'https://api.global-interaction.com/';
|
||||
agencyCenter: agencyCenterAPI,
|
||||
bdCenter: bdCenterAPI,
|
||||
bdLeaderCenter: bdLeaderCenterAPI,
|
||||
managerCenter: managerCenterAPI,
|
||||
superadminCenter: superadminCenterAPI,
|
||||
level: levelAPI,
|
||||
weeklyStar: weeklyStarAPI,
|
||||
|
||||
@ -363,5 +363,30 @@
|
||||
"lalu_landing.room_no": "رقم الغرفة:",
|
||||
"lalu_landing.tagline": "تعرف على أصدقاء جدد",
|
||||
"lalu_landing.download": "تنزيل",
|
||||
"lalu_landing.open_download_failed": "رابط التنزيل غير مهيأ."
|
||||
"lalu_landing.open_download_failed": "رابط التنزيل غير مهيأ.",
|
||||
"cp.pageTitle": "أفضل CP في الأسبوع الماضي",
|
||||
"cp.pageLabel": "نشاط أفضل CP في الأسبوع الماضي",
|
||||
"cp.changeLanguage": "تغيير اللغة",
|
||||
"cp.tabsLabel": "تبويبات نشاط CP",
|
||||
"cp.days": "ي",
|
||||
"cp.hours": "س",
|
||||
"cp.minutes": "د",
|
||||
"cp.seconds": "ث",
|
||||
"cp.tabPlaza": "ساحة CP",
|
||||
"cp.tabRank": "ترتيب CP",
|
||||
"cp.tabRewards": "المكافآت",
|
||||
"cp.plazaGiftTitle": "الهدايا المتبادلة بين CP بقيمة >= 5000 ذهب ستظهر هنا",
|
||||
"cp.plazaGiftSubtitle": "(عرض آخر 10 سجلات مرتبة تصاعديا.)",
|
||||
"cp.previousNo1Title": "مركز الرقم واحد السابق",
|
||||
"cp.rewardMedal7": "وسام*7 أيام",
|
||||
"cp.rewardMedalNote": "(وسام دائم بعد الحصول عليه 3 مرات)",
|
||||
"cp.rewardHonorBanner7": "لافتة شرف*7 أيام",
|
||||
"cp.rewardHeadwear7": "زينة رأس*7 أيام",
|
||||
"cp.rewardMount7": "مركبة*7 أيام",
|
||||
"cp.rewardCoins20000": "عملات*20000",
|
||||
"cp.rewardDuke7": "دوق*7 أيام",
|
||||
"cp.rewardMarquis7": "ماركيز*7 أيام",
|
||||
"cp.rewardCoins10000": "عملات*10000",
|
||||
"cp.rewardCount7": "عدّاد*7 أيام",
|
||||
"cp.copyright": "حقوق هذا النشاط تعود إلى Shila"
|
||||
}
|
||||
|
||||
@ -369,5 +369,30 @@
|
||||
"lalu_landing.room_no": "Room No:",
|
||||
"lalu_landing.tagline": "Meet new friends",
|
||||
"lalu_landing.download": "Download",
|
||||
"lalu_landing.open_download_failed": "Download URL is not configured."
|
||||
"lalu_landing.open_download_failed": "Download URL is not configured.",
|
||||
"cp.pageTitle": "Last Weekly CP",
|
||||
"cp.pageLabel": "Last Weekly CP activity",
|
||||
"cp.changeLanguage": "Change language",
|
||||
"cp.tabsLabel": "CP activity tabs",
|
||||
"cp.days": "D",
|
||||
"cp.hours": "H",
|
||||
"cp.minutes": "M",
|
||||
"cp.seconds": "S",
|
||||
"cp.tabPlaza": "CP Plaza",
|
||||
"cp.tabRank": "CP Rank",
|
||||
"cp.tabRewards": "Rewards",
|
||||
"cp.plazaGiftTitle": "Gifts exchanged between CPs >= 5000 gold will be displayed here",
|
||||
"cp.plazaGiftSubtitle": "(Display the last 10 records, sorted in ascending order.)",
|
||||
"cp.previousNo1Title": "The previous number one position",
|
||||
"cp.rewardMedal7": "Medal*7 days",
|
||||
"cp.rewardMedalNote": "(Permanent medal after getting 3 times)",
|
||||
"cp.rewardHonorBanner7": "Honor banner*7 days",
|
||||
"cp.rewardHeadwear7": "Headwear*7days",
|
||||
"cp.rewardMount7": "Mount*7days",
|
||||
"cp.rewardCoins20000": "Coins*20000",
|
||||
"cp.rewardDuke7": "Duke*7days",
|
||||
"cp.rewardMarquis7": "Marquis*7days",
|
||||
"cp.rewardCoins10000": "Coins*10000",
|
||||
"cp.rewardCount7": "Count*7days",
|
||||
"cp.copyright": "The copyright of this activity belongs to Shila"
|
||||
}
|
||||
|
||||
@ -363,5 +363,30 @@
|
||||
"lalu_landing.room_no": "Sala No:",
|
||||
"lalu_landing.tagline": "Conoce nuevos amigos",
|
||||
"lalu_landing.download": "Descargar",
|
||||
"lalu_landing.open_download_failed": "La URL de descarga no está configurada."
|
||||
"lalu_landing.open_download_failed": "La URL de descarga no está configurada.",
|
||||
"cp.pageTitle": "CP de la semana pasada",
|
||||
"cp.pageLabel": "Actividad CP de la semana pasada",
|
||||
"cp.changeLanguage": "Cambiar idioma",
|
||||
"cp.tabsLabel": "Pestañas de actividad CP",
|
||||
"cp.days": "D",
|
||||
"cp.hours": "H",
|
||||
"cp.minutes": "M",
|
||||
"cp.seconds": "S",
|
||||
"cp.tabPlaza": "Plaza CP",
|
||||
"cp.tabRank": "Ranking CP",
|
||||
"cp.tabRewards": "Recompensas",
|
||||
"cp.plazaGiftTitle": "Los regalos entre CPs >= 5000 de oro se mostrarán aquí",
|
||||
"cp.plazaGiftSubtitle": "(Muestra los últimos 10 registros en orden ascendente.)",
|
||||
"cp.previousNo1Title": "La posición número uno anterior",
|
||||
"cp.rewardMedal7": "Medalla*7 días",
|
||||
"cp.rewardMedalNote": "(Medalla permanente tras obtenerla 3 veces)",
|
||||
"cp.rewardHonorBanner7": "Banner de honor*7 días",
|
||||
"cp.rewardHeadwear7": "Tocado*7 días",
|
||||
"cp.rewardMount7": "Montura*7 días",
|
||||
"cp.rewardCoins20000": "Monedas*20000",
|
||||
"cp.rewardDuke7": "Duque*7 días",
|
||||
"cp.rewardMarquis7": "Marqués*7 días",
|
||||
"cp.rewardCoins10000": "Monedas*10000",
|
||||
"cp.rewardCount7": "Contador*7 días",
|
||||
"cp.copyright": "Los derechos de esta actividad pertenecen a Shila"
|
||||
}
|
||||
|
||||
@ -363,5 +363,30 @@
|
||||
"lalu_landing.room_no": "No. room:",
|
||||
"lalu_landing.tagline": "Kenalan dengan teman baru",
|
||||
"lalu_landing.download": "Unduh",
|
||||
"lalu_landing.open_download_failed": "URL unduhan belum dikonfigurasi."
|
||||
"lalu_landing.open_download_failed": "URL unduhan belum dikonfigurasi.",
|
||||
"cp.pageTitle": "CP Minggu Lalu",
|
||||
"cp.pageLabel": "Aktivitas CP minggu lalu",
|
||||
"cp.changeLanguage": "Ganti bahasa",
|
||||
"cp.tabsLabel": "Tab aktivitas CP",
|
||||
"cp.days": "H",
|
||||
"cp.hours": "J",
|
||||
"cp.minutes": "M",
|
||||
"cp.seconds": "D",
|
||||
"cp.tabPlaza": "CP Plaza",
|
||||
"cp.tabRank": "Peringkat CP",
|
||||
"cp.tabRewards": "Hadiah",
|
||||
"cp.plazaGiftTitle": "Hadiah antar CP >= 5000 gold akan ditampilkan di sini",
|
||||
"cp.plazaGiftSubtitle": "(Menampilkan 10 catatan terbaru, diurutkan naik.)",
|
||||
"cp.previousNo1Title": "Posisi nomor satu sebelumnya",
|
||||
"cp.rewardMedal7": "Medali*7 hari",
|
||||
"cp.rewardMedalNote": "(Medali permanen setelah mendapatkannya 3 kali)",
|
||||
"cp.rewardHonorBanner7": "Banner kehormatan*7 hari",
|
||||
"cp.rewardHeadwear7": "Hiasan kepala*7 hari",
|
||||
"cp.rewardMount7": "Mount*7 hari",
|
||||
"cp.rewardCoins20000": "Koin*20000",
|
||||
"cp.rewardDuke7": "Duke*7 hari",
|
||||
"cp.rewardMarquis7": "Marquis*7 hari",
|
||||
"cp.rewardCoins10000": "Koin*10000",
|
||||
"cp.rewardCount7": "Hitungan*7 hari",
|
||||
"cp.copyright": "Hak cipta aktivitas ini milik Shila"
|
||||
}
|
||||
|
||||
@ -363,5 +363,30 @@
|
||||
"lalu_landing.room_no": "Oda No:",
|
||||
"lalu_landing.tagline": "Yeni arkadaşlarla tanış",
|
||||
"lalu_landing.download": "İndir",
|
||||
"lalu_landing.open_download_failed": "İndirme URL'si yapılandırılmadı."
|
||||
"lalu_landing.open_download_failed": "İndirme URL'si yapılandırılmadı.",
|
||||
"cp.pageTitle": "Geçen Haftanın CP Etkinliği",
|
||||
"cp.pageLabel": "Geçen Haftanın CP etkinliği",
|
||||
"cp.changeLanguage": "Dili değiştir",
|
||||
"cp.tabsLabel": "CP etkinliği sekmeleri",
|
||||
"cp.days": "G",
|
||||
"cp.hours": "S",
|
||||
"cp.minutes": "D",
|
||||
"cp.seconds": "Sn",
|
||||
"cp.tabPlaza": "CP Plaza",
|
||||
"cp.tabRank": "CP Sıralaması",
|
||||
"cp.tabRewards": "Ödüller",
|
||||
"cp.plazaGiftTitle": "CPler arasında >= 5000 altın değerinde gönderilen hediyeler burada görünür",
|
||||
"cp.plazaGiftSubtitle": "(Son 10 kayıt artan sırayla gösterilir.)",
|
||||
"cp.previousNo1Title": "Önceki birincilik konumu",
|
||||
"cp.rewardMedal7": "Madalya*7 gün",
|
||||
"cp.rewardMedalNote": "(3 kez alındıktan sonra kalıcı madalya)",
|
||||
"cp.rewardHonorBanner7": "Onur afişi*7 gün",
|
||||
"cp.rewardHeadwear7": "Başlık*7 gün",
|
||||
"cp.rewardMount7": "Binek*7 gün",
|
||||
"cp.rewardCoins20000": "Coin*20000",
|
||||
"cp.rewardDuke7": "Dük*7 gün",
|
||||
"cp.rewardMarquis7": "Marki*7 gün",
|
||||
"cp.rewardCoins10000": "Coin*10000",
|
||||
"cp.rewardCount7": "Sayaç*7 gün",
|
||||
"cp.copyright": "Bu etkinliğin telif hakkı Shila'ya aittir"
|
||||
}
|
||||
|
||||
@ -369,5 +369,30 @@
|
||||
"lalu_landing.room_no": "房间号:",
|
||||
"lalu_landing.tagline": "结识新朋友",
|
||||
"lalu_landing.download": "下载",
|
||||
"lalu_landing.open_download_failed": "下载地址未配置。"
|
||||
"lalu_landing.open_download_failed": "下载地址未配置。",
|
||||
"cp.pageTitle": "上周 CP",
|
||||
"cp.pageLabel": "上周 CP 活动",
|
||||
"cp.changeLanguage": "切换语言",
|
||||
"cp.tabsLabel": "CP 活动标签",
|
||||
"cp.days": "天",
|
||||
"cp.hours": "时",
|
||||
"cp.minutes": "分",
|
||||
"cp.seconds": "秒",
|
||||
"cp.tabPlaza": "CP 广场",
|
||||
"cp.tabRank": "CP 排行",
|
||||
"cp.tabRewards": "奖励",
|
||||
"cp.plazaGiftTitle": "CP 之间互赠 >= 5000 金币的礼物会展示在这里",
|
||||
"cp.plazaGiftSubtitle": "(展示最近 10 条记录,按时间升序排列。)",
|
||||
"cp.previousNo1Title": "上期第一名位置",
|
||||
"cp.rewardMedal7": "勋章*7天",
|
||||
"cp.rewardMedalNote": "(获得 3 次后永久)",
|
||||
"cp.rewardHonorBanner7": "荣誉横幅*7天",
|
||||
"cp.rewardHeadwear7": "头饰*7天",
|
||||
"cp.rewardMount7": "坐骑*7天",
|
||||
"cp.rewardCoins20000": "金币*20000",
|
||||
"cp.rewardDuke7": "公爵*7天",
|
||||
"cp.rewardMarquis7": "侯爵*7天",
|
||||
"cp.rewardCoins10000": "金币*10000",
|
||||
"cp.rewardCount7": "计数*7天",
|
||||
"cp.copyright": "本活动版权归 Shila 所有"
|
||||
}
|
||||
|
||||
322
game-bridge/voice-room-game-bridge.v1.js
Normal file
@ -0,0 +1,322 @@
|
||||
(function () {
|
||||
var remoteBridgeVersion = '20260608.1';
|
||||
|
||||
function safeCall(fn, args) {
|
||||
try {
|
||||
if (typeof fn === 'function') {
|
||||
return fn.apply(window, args || []);
|
||||
}
|
||||
} catch (error) {
|
||||
post('debugLog', {
|
||||
message: String((error && error.message) || error),
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readObject(value) {
|
||||
return value && typeof value === 'object' && !Array.isArray(value)
|
||||
? value
|
||||
: {};
|
||||
}
|
||||
|
||||
function readString(value) {
|
||||
if (value === null || value === undefined) {
|
||||
return '';
|
||||
}
|
||||
return String(value).trim();
|
||||
}
|
||||
|
||||
function post(action, body) {
|
||||
var message = JSON.stringify({ action: action, payload: body || {} });
|
||||
if (postToChannel('GameBridgeChannel', message)) {
|
||||
return;
|
||||
}
|
||||
if (postToChannel('BaishunBridgeChannel', message)) {
|
||||
return;
|
||||
}
|
||||
if (postToChannel('LeaderBridgeChannel', message)) {
|
||||
return;
|
||||
}
|
||||
postToChannel('NativeBridge', message);
|
||||
}
|
||||
|
||||
function postToChannel(name, message) {
|
||||
if (window[name] && window[name].postMessage) {
|
||||
window[name].postMessage(message);
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
window.webkit &&
|
||||
window.webkit.messageHandlers &&
|
||||
window.webkit.messageHandlers[name] &&
|
||||
window.webkit.messageHandlers[name].postMessage
|
||||
) {
|
||||
window.webkit.messageHandlers[name].postMessage(message);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function launchPayload() {
|
||||
return readObject(window.hyappGameLaunchPayload);
|
||||
}
|
||||
|
||||
function launchConfig() {
|
||||
var payload = launchPayload();
|
||||
return readObject(window.hyappGameLaunchConfig || payload.launchConfig);
|
||||
}
|
||||
|
||||
function launchEntry() {
|
||||
var payload = launchPayload();
|
||||
return readObject(window.hyappGameEntry || payload.entry);
|
||||
}
|
||||
|
||||
function detectAdapter(config, payload) {
|
||||
var values = [
|
||||
config.adapterType,
|
||||
config.adapter_type,
|
||||
config.platformCode,
|
||||
config.platform_code,
|
||||
config.vendorType,
|
||||
config.vendor_type,
|
||||
payload.vendorType,
|
||||
payload.vendor_type,
|
||||
payload.platformCode,
|
||||
payload.platform_code,
|
||||
payload.gameId,
|
||||
payload.game_id,
|
||||
]
|
||||
.map(readString)
|
||||
.join('|')
|
||||
.toLowerCase();
|
||||
if (values.indexOf('vivagames') >= 0) {
|
||||
return 'vivagames_v1';
|
||||
}
|
||||
if (values.indexOf('baishun') >= 0) {
|
||||
return 'baishun_v1';
|
||||
}
|
||||
if (values.indexOf('leadercc') >= 0) {
|
||||
return 'leadercc_v1';
|
||||
}
|
||||
if (values.indexOf('zeeone') >= 0) {
|
||||
return 'zeeone_v1';
|
||||
}
|
||||
return 'generic';
|
||||
}
|
||||
|
||||
function stringifyParams(body) {
|
||||
try {
|
||||
return JSON.stringify(body || {});
|
||||
} catch (_) {
|
||||
return '{}';
|
||||
}
|
||||
}
|
||||
|
||||
function notifyVivaGames(method, body) {
|
||||
var params = body || {};
|
||||
safeCall(window.OnCallGame, [method, params]);
|
||||
try {
|
||||
window.postMessage(
|
||||
{ method: method, params: stringifyParams(params) },
|
||||
'*'
|
||||
);
|
||||
} catch (_) {}
|
||||
try {
|
||||
if (
|
||||
window.parent &&
|
||||
window.parent !== window &&
|
||||
window.parent.postMessage
|
||||
) {
|
||||
window.parent.postMessage(
|
||||
{ method: method, params: stringifyParams(params) },
|
||||
'*'
|
||||
);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function installAlias(name, action) {
|
||||
var fn = function (body) {
|
||||
return callNativeBridge(action || name, body);
|
||||
};
|
||||
fn.postMessage = function (body) {
|
||||
callNativeBridge(action || name, body);
|
||||
};
|
||||
window[name] = fn;
|
||||
window.webkit = window.webkit || {};
|
||||
window.webkit.messageHandlers = window.webkit.messageHandlers || {};
|
||||
window.webkit.messageHandlers[name] = { postMessage: fn.postMessage };
|
||||
}
|
||||
|
||||
function callNativeBridge(action, body) {
|
||||
var config = launchConfig();
|
||||
var payload = launchPayload();
|
||||
var adapter = detectAdapter(config, payload);
|
||||
var normalized = readString(action);
|
||||
if (normalized === 'getConfig') {
|
||||
post('getConfig', body || {});
|
||||
if (adapter === 'vivagames_v1') {
|
||||
notifyVivaGames('getConfig', config);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
if (normalized === 'getLaunchPayload') {
|
||||
post('getLaunchPayload', body || {});
|
||||
return payload;
|
||||
}
|
||||
post(normalized || 'NativeBridge', body || {});
|
||||
if (adapter === 'vivagames_v1') {
|
||||
notifyVivaGames(normalized || 'NativeBridge', body || {});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function installNativeBridge() {
|
||||
var config = launchConfig();
|
||||
var payload = launchPayload();
|
||||
var entry = launchEntry();
|
||||
var adapter = detectAdapter(config, payload);
|
||||
var nativeBridge = window.NativeBridge || {};
|
||||
var oldPostMessage =
|
||||
typeof nativeBridge.postMessage === 'function'
|
||||
? nativeBridge.postMessage.bind(nativeBridge)
|
||||
: null;
|
||||
|
||||
window.__hyappRemoteGameBridge = {
|
||||
adapter: adapter,
|
||||
version: remoteBridgeVersion,
|
||||
installedAt: Date.now(),
|
||||
};
|
||||
window.hyappGameLaunchPayload = payload;
|
||||
window.hyappGameLaunchConfig = config;
|
||||
window.hyappGameEntry = entry;
|
||||
window.__baishunLastConfig = config;
|
||||
window.baishunBridgeConfig = config;
|
||||
window.leaderLaunchPayload = payload;
|
||||
window.leaderLaunchConfig = config;
|
||||
window.leaderLaunchEntry = entry;
|
||||
|
||||
nativeBridge.config = config;
|
||||
nativeBridge.launchConfig = config;
|
||||
nativeBridge.entry = entry;
|
||||
nativeBridge.launchPayload = payload;
|
||||
nativeBridge.getConfigSync = function () {
|
||||
return launchConfig();
|
||||
};
|
||||
nativeBridge.getLaunchPayloadSync = function () {
|
||||
return launchPayload();
|
||||
};
|
||||
nativeBridge.postMessage = function (body) {
|
||||
if (oldPostMessage) {
|
||||
oldPostMessage(
|
||||
typeof body === 'string' ? body : stringifyParams(body)
|
||||
);
|
||||
return;
|
||||
}
|
||||
callNativeBridge('NativeBridge', body);
|
||||
};
|
||||
nativeBridge.getConfig = function (bodyOrCallback) {
|
||||
var currentConfig = launchConfig();
|
||||
if (typeof bodyOrCallback === 'function') {
|
||||
safeCall(bodyOrCallback, [currentConfig, launchPayload()]);
|
||||
}
|
||||
callNativeBridge('getConfig', bodyOrCallback || {});
|
||||
return currentConfig;
|
||||
};
|
||||
nativeBridge.getLaunchPayload = function (bodyOrCallback) {
|
||||
var currentPayload = launchPayload();
|
||||
if (typeof bodyOrCallback === 'function') {
|
||||
safeCall(bodyOrCallback, [currentPayload]);
|
||||
}
|
||||
callNativeBridge('getLaunchPayload', bodyOrCallback || {});
|
||||
return currentPayload;
|
||||
};
|
||||
nativeBridge.destroy = function (body) {
|
||||
callNativeBridge('destroy', body || {});
|
||||
};
|
||||
nativeBridge.closeGame = function (body) {
|
||||
callNativeBridge('closeGame', body || {});
|
||||
};
|
||||
nativeBridge.gameStart = function (body) {
|
||||
callNativeBridge('gameStart', body || {});
|
||||
};
|
||||
nativeBridge.gameLoading = function (body) {
|
||||
callNativeBridge('gameLoading', body || {});
|
||||
};
|
||||
nativeBridge.gameLoaded = function (body) {
|
||||
callNativeBridge('gameLoaded', body || {});
|
||||
};
|
||||
nativeBridge.loadComplete = function (body) {
|
||||
callNativeBridge('loadComplete', body || {});
|
||||
};
|
||||
nativeBridge.gameRecharge = function (body) {
|
||||
callNativeBridge('gameRecharge', body || {});
|
||||
};
|
||||
nativeBridge.pay = function (body) {
|
||||
callNativeBridge('pay', body || {});
|
||||
};
|
||||
nativeBridge.copyText = function (body) {
|
||||
callNativeBridge('copyText', body || {});
|
||||
};
|
||||
nativeBridge.refreshBalance = function (body) {
|
||||
callNativeBridge('refreshBalance', body || {});
|
||||
};
|
||||
nativeBridge.refreshBlance = function (body) {
|
||||
callNativeBridge('refreshBalance', body || {});
|
||||
};
|
||||
nativeBridge.debugLog = function (body) {
|
||||
callNativeBridge('debugLog', body || {});
|
||||
};
|
||||
window.NativeBridge = nativeBridge;
|
||||
|
||||
[
|
||||
['getConfig', 'getConfig'],
|
||||
['destroy', 'destroy'],
|
||||
['closeGame', 'closeGame'],
|
||||
['gameStart', 'gameStart'],
|
||||
['gameLoading', 'gameLoading'],
|
||||
['gameLoaded', 'gameLoaded'],
|
||||
['loadComplete', 'loadComplete'],
|
||||
['gameRecharge', 'gameRecharge'],
|
||||
['pay', 'pay'],
|
||||
['copyText', 'copyText'],
|
||||
['refreshBalance', 'refreshBalance'],
|
||||
['refreshBlance', 'refreshBalance'],
|
||||
['debugLog', 'debugLog'],
|
||||
].forEach(function (item) {
|
||||
installAlias(item[0], item[1]);
|
||||
});
|
||||
|
||||
if (adapter === 'vivagames_v1') {
|
||||
safeCall(window.OnCallGame, ['getConfig', config]);
|
||||
}
|
||||
signalReady(config, payload);
|
||||
}
|
||||
|
||||
function signalReady(config, payload) {
|
||||
[
|
||||
['hyappGameBridgeReady', payload],
|
||||
['hyappRemoteGameBridgeReady', window.__hyappRemoteGameBridge],
|
||||
['baishunBridgeReady', config],
|
||||
['leaderLaunchConfig', payload],
|
||||
['launchConfig', config],
|
||||
].forEach(function (item) {
|
||||
try {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent(item[0], { detail: item[1] })
|
||||
);
|
||||
if (document && document.dispatchEvent) {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(item[0], { detail: item[1] })
|
||||
);
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
|
||||
window.HYAppGameBridge = window.HYAppGameBridge || {};
|
||||
window.HYAppGameBridge.install = installNativeBridge;
|
||||
window.HYAppGameBridge.version = remoteBridgeVersion;
|
||||
installNativeBridge();
|
||||
})();
|
||||
@ -76,7 +76,8 @@ input:focus-visible {
|
||||
.bd-center,
|
||||
.agency-center,
|
||||
.superadmin-center,
|
||||
.coinseller-center {
|
||||
.coinseller-center,
|
||||
.manager-center {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 430px;
|
||||
|
||||
384
gonghui/manager-center/index.html
Normal file
@ -0,0 +1,384 @@
|
||||
<!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>Control Panel</title>
|
||||
<link rel="stylesheet" href="../../common/theme.css?v=20260608" />
|
||||
<link rel="stylesheet" href="../common/center.css?v=20260608" />
|
||||
<link rel="stylesheet" href="./style.css?v=20260608-manager-unblock" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="manager-center" aria-label="Control Panel">
|
||||
<div class="hero-bg" aria-hidden="true"></div>
|
||||
|
||||
<nav class="title-bar">
|
||||
<button
|
||||
class="back-button"
|
||||
id="backButton"
|
||||
type="button"
|
||||
aria-label="Back"
|
||||
data-i18n-aria="manager_center.back"
|
||||
>
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true">
|
||||
<path d="M20 8 12 16l8 8" />
|
||||
</svg>
|
||||
</button>
|
||||
<h1 data-i18n="manager_center.title">Control Panel</h1>
|
||||
<div class="language-switcher">
|
||||
<button
|
||||
class="language-button"
|
||||
type="button"
|
||||
aria-label="Change language"
|
||||
aria-expanded="false"
|
||||
data-language-toggle
|
||||
data-current-lang
|
||||
data-i18n-aria="manager_center.change_language"
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
<div class="language-menu" data-language-menu hidden>
|
||||
<button type="button" data-lang-option="en">EN</button>
|
||||
<button type="button" data-lang-option="ar">AR</button>
|
||||
<button type="button" data-lang-option="tr">TR</button>
|
||||
<button type="button" data-lang-option="es">ES</button>
|
||||
<button type="button" data-lang-option="zh">ZH</button>
|
||||
<button type="button" data-lang-option="id">ID</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="content">
|
||||
<section class="card manager-panel">
|
||||
<div class="manager-grid" aria-label="Manager tools">
|
||||
<button
|
||||
class="manager-tile"
|
||||
type="button"
|
||||
data-action="send-frame"
|
||||
>
|
||||
<span class="tile-icon frame-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 48 48">
|
||||
<path d="M10 10h28v28H10Z" />
|
||||
<path d="M16 16h16v16H16Z" />
|
||||
<path d="M10 18h6M32 18h6M10 30h6M32 30h6" />
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="tile-title"
|
||||
data-i18n="manager_center.send_frame"
|
||||
>Send Frame</span
|
||||
>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="manager-tile"
|
||||
type="button"
|
||||
data-action="send-car"
|
||||
>
|
||||
<span class="tile-icon car-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 48 48">
|
||||
<path d="M9 28h30l-4-10H13Z" />
|
||||
<path d="M13 28v7M35 28v7" />
|
||||
<path d="M15 35a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z" />
|
||||
<path d="M33 35a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z" />
|
||||
<path d="M18 18h12" />
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="tile-title"
|
||||
data-i18n="manager_center.send_car"
|
||||
>Send Car</span
|
||||
>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="manager-tile"
|
||||
type="button"
|
||||
data-action="send-vip"
|
||||
>
|
||||
<span class="tile-icon vip-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 48 48">
|
||||
<path d="m9 18 8 8 7-14 7 14 8-8-4 20H13Z" />
|
||||
<path d="M13 38h22" />
|
||||
<path d="M20 29h8" />
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="tile-title"
|
||||
data-i18n="manager_center.send_vip"
|
||||
>Send VIP</span
|
||||
>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="manager-tile"
|
||||
type="button"
|
||||
data-action="update-user-level"
|
||||
>
|
||||
<span
|
||||
class="tile-icon level-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg viewBox="0 0 48 48">
|
||||
<path
|
||||
d="M24 7a10 10 0 1 0 0 20 10 10 0 0 0 0-20Z"
|
||||
/>
|
||||
<path
|
||||
d="M8 42c2.4-8 8.2-12 16-12s13.6 4 16 12"
|
||||
/>
|
||||
<path d="M35 10v7M31.5 13.5h7" />
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="tile-title"
|
||||
data-i18n="manager_center.update_user_level"
|
||||
>Update User Level</span
|
||||
>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="manager-tile manager-tile-wide"
|
||||
type="button"
|
||||
data-action="block-user"
|
||||
>
|
||||
<span class="tile-icon block-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 48 48">
|
||||
<path
|
||||
d="M18 22a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 42c1.8-9.2 6.2-13.8 13-13.8 4.3 0 7.6 1.8 10 5.3"
|
||||
/>
|
||||
<path
|
||||
d="M35 23a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z"
|
||||
/>
|
||||
<path d="m29 29 12 12" />
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="tile-title"
|
||||
data-i18n="manager_center.block_user"
|
||||
>Block User</span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div
|
||||
class="block-modal level-modal"
|
||||
id="levelModal"
|
||||
aria-hidden="true"
|
||||
hidden
|
||||
>
|
||||
<button
|
||||
class="block-backdrop"
|
||||
id="levelBackdrop"
|
||||
type="button"
|
||||
aria-label="Close update level dialog"
|
||||
></button>
|
||||
<section
|
||||
class="block-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="levelDialogTitle"
|
||||
>
|
||||
<div class="block-dialog-header">
|
||||
<h2 id="levelDialogTitle">Update User Level</h2>
|
||||
<button
|
||||
class="block-close"
|
||||
id="levelCloseButton"
|
||||
type="button"
|
||||
aria-label="Close"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M6 6l12 12M18 6 6 18" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="block-dialog-body">
|
||||
<div class="block-field">
|
||||
<label for="levelSearchInput">User ID</label>
|
||||
<div class="block-search-row">
|
||||
<input
|
||||
id="levelSearchInput"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
autocomplete="off"
|
||||
placeholder="Enter user ID"
|
||||
/>
|
||||
<button id="levelSearchButton" type="button">
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="block-search-result"
|
||||
id="levelSearchResult"
|
||||
aria-live="polite"
|
||||
></div>
|
||||
|
||||
<div class="level-panel" id="levelPanel" hidden>
|
||||
<div class="block-section-title">
|
||||
Select user level
|
||||
</div>
|
||||
<div
|
||||
class="level-grid"
|
||||
id="levelGrid"
|
||||
aria-label="Select user level"
|
||||
></div>
|
||||
<button
|
||||
class="confirm-level-button"
|
||||
id="confirmLevelButton"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
Confirm Update
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="block-modal"
|
||||
id="blockModal"
|
||||
aria-hidden="true"
|
||||
hidden
|
||||
>
|
||||
<button
|
||||
class="block-backdrop"
|
||||
id="blockBackdrop"
|
||||
type="button"
|
||||
aria-label="Close block user dialog"
|
||||
></button>
|
||||
<section
|
||||
class="block-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="blockDialogTitle"
|
||||
>
|
||||
<div class="block-dialog-header">
|
||||
<h2 id="blockDialogTitle">Block User</h2>
|
||||
<button
|
||||
class="block-close"
|
||||
id="blockCloseButton"
|
||||
type="button"
|
||||
aria-label="Close"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M6 6l12 12M18 6 6 18" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="block-dialog-body">
|
||||
<div class="block-field">
|
||||
<label for="blockSearchInput">User ID</label>
|
||||
<div class="block-search-row">
|
||||
<input
|
||||
id="blockSearchInput"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
autocomplete="off"
|
||||
placeholder="Enter user ID"
|
||||
/>
|
||||
<button id="blockSearchButton" type="button">
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="block-search-result"
|
||||
id="blockSearchResult"
|
||||
aria-live="polite"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="block-duration-panel"
|
||||
id="blockDurationPanel"
|
||||
hidden
|
||||
>
|
||||
<div class="block-section-title">
|
||||
Select block duration
|
||||
</div>
|
||||
<div class="duration-grid" id="durationGrid">
|
||||
<button
|
||||
type="button"
|
||||
data-duration-ms="3600000"
|
||||
data-duration-label="1 hour"
|
||||
>
|
||||
1 hour
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-duration-ms="86400000"
|
||||
data-duration-label="1 day"
|
||||
>
|
||||
1 day
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-duration-ms="604800000"
|
||||
data-duration-label="7 days"
|
||||
>
|
||||
7 days
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-duration-ms="2592000000"
|
||||
data-duration-label="30 days"
|
||||
>
|
||||
30 days
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-duration-ms="0"
|
||||
data-duration-label="Permanent"
|
||||
>
|
||||
Permanent
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="confirm-block-button"
|
||||
id="confirmBlockButton"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
Confirm Block
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="block-list-header">
|
||||
<div class="block-section-title">Blocked Users</div>
|
||||
<div class="block-list-count" id="blockListCount">
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="blocked-list"
|
||||
id="blockedList"
|
||||
aria-live="polite"
|
||||
></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="home-indicator" aria-hidden="true"></div>
|
||||
</div>
|
||||
|
||||
<script src="../../common/toast.js"></script>
|
||||
<script src="../../common/api.js?v=20260608-manager-real"></script>
|
||||
<script src="../../common/params.js?v=20260608-manager-real"></script>
|
||||
<script src="../../common/jsbridge.js"></script>
|
||||
<script src="../../common/i18n.js?v=20260608"></script>
|
||||
<script src="./script.js?v=20260608-manager-duration"></script>
|
||||
</body>
|
||||
</html>
|
||||
937
gonghui/manager-center/script.js
Normal file
@ -0,0 +1,937 @@
|
||||
(function () {
|
||||
var ACTION_LABELS = {
|
||||
'block-user': 'Block User',
|
||||
'send-frame': 'Send Frame',
|
||||
'send-car': 'Send Car',
|
||||
'send-vip': 'Send VIP',
|
||||
'update-user-level': 'Update User Level',
|
||||
};
|
||||
var DAY_MS = 24 * 60 * 60 * 1000;
|
||||
var PERMANENT_BLOCK_MS = 10 * 365 * DAY_MS;
|
||||
var RESOURCE_TYPES = {
|
||||
'send-frame': 'avatar_frame',
|
||||
'send-car': 'vehicle',
|
||||
};
|
||||
var RESOURCE_GRANT_DURATIONS = [
|
||||
{ label: '7 days', ms: 7 * DAY_MS },
|
||||
{ label: '30 days', ms: 30 * DAY_MS },
|
||||
{ label: '90 days', ms: 90 * DAY_MS },
|
||||
];
|
||||
var DEFAULT_RESOURCE_GRANT_DURATION_MS = RESOURCE_GRANT_DURATIONS[0].ms;
|
||||
var TRACK_LABELS = {
|
||||
wealth: 'Wealth',
|
||||
charm: 'Charm',
|
||||
game: 'Game',
|
||||
};
|
||||
|
||||
var api = window.HyAppAPI && window.HyAppAPI.managerCenter;
|
||||
var pageState = {
|
||||
loading: true,
|
||||
denied: false,
|
||||
overview: null,
|
||||
resourcesByType: {},
|
||||
};
|
||||
var blockState = {
|
||||
searchedUser: null,
|
||||
selectedDurationMs: 0,
|
||||
selectedDurationLabel: '',
|
||||
blockedUsers: [],
|
||||
submitting: false,
|
||||
};
|
||||
var levelState = {
|
||||
searchedUser: null,
|
||||
selectedTrack: 'wealth',
|
||||
selectedLevel: 0,
|
||||
submitting: false,
|
||||
};
|
||||
var grantState = {
|
||||
action: '',
|
||||
resourceType: '',
|
||||
title: '',
|
||||
searchedUser: null,
|
||||
selectedResourceId: '',
|
||||
selectedResourceDurationMs: DEFAULT_RESOURCE_GRANT_DURATION_MS,
|
||||
selectedVipLevel: 0,
|
||||
submitting: false,
|
||||
};
|
||||
|
||||
function $(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function root() {
|
||||
return document.querySelector('.manager-center');
|
||||
}
|
||||
|
||||
function t(key, fallback) {
|
||||
return window.HyAppI18n && window.HyAppI18n.t
|
||||
? window.HyAppI18n.t(key, fallback)
|
||||
: fallback || key;
|
||||
}
|
||||
|
||||
function toast(message) {
|
||||
if (window.HyAppToast && window.HyAppToast.show) {
|
||||
window.HyAppToast.show(message);
|
||||
return;
|
||||
}
|
||||
window.alert(message);
|
||||
}
|
||||
|
||||
function showError(error) {
|
||||
var message =
|
||||
error && error.status === 403
|
||||
? 'Permission denied.'
|
||||
: (error && error.message) || 'Request failed.';
|
||||
toast(message);
|
||||
}
|
||||
|
||||
function commandId(prefix) {
|
||||
return [
|
||||
prefix || 'manager',
|
||||
Date.now(),
|
||||
Math.random().toString(16).slice(2, 10),
|
||||
].join('-');
|
||||
}
|
||||
|
||||
function userName(user) {
|
||||
return (
|
||||
user.username ||
|
||||
user.name ||
|
||||
user.target_username ||
|
||||
user.display_user_id ||
|
||||
user.target_display_user_id ||
|
||||
'User'
|
||||
);
|
||||
}
|
||||
|
||||
function userDisplayId(user) {
|
||||
return user.display_user_id || user.target_display_user_id || '';
|
||||
}
|
||||
|
||||
function userId(user) {
|
||||
return user.user_id || user.target_user_id || '';
|
||||
}
|
||||
|
||||
function avatarUrl(user) {
|
||||
return user.avatar || user.target_avatar || '';
|
||||
}
|
||||
|
||||
function initial(text) {
|
||||
return String(text || 'M')
|
||||
.trim()
|
||||
.charAt(0)
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
function createNode(tag, className, text) {
|
||||
var node = document.createElement(tag);
|
||||
if (className) node.className = className;
|
||||
if (text !== undefined) node.textContent = text;
|
||||
return node;
|
||||
}
|
||||
|
||||
function setAvatar(node, user) {
|
||||
var url = avatarUrl(user);
|
||||
node.innerHTML = '';
|
||||
if (url) {
|
||||
var img = document.createElement('img');
|
||||
img.src = url;
|
||||
img.alt = '';
|
||||
node.appendChild(img);
|
||||
return;
|
||||
}
|
||||
node.textContent = initial(userName(user) || userDisplayId(user));
|
||||
}
|
||||
|
||||
function formatDateTime(ms) {
|
||||
if (!ms) return 'Never';
|
||||
var date = new Date(Number(ms));
|
||||
var year = date.getFullYear();
|
||||
var month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
var day = String(date.getDate()).padStart(2, '0');
|
||||
var hour = String(date.getHours()).padStart(2, '0');
|
||||
var minute = String(date.getMinutes()).padStart(2, '0');
|
||||
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute;
|
||||
}
|
||||
|
||||
function setPageLoading(loading) {
|
||||
pageState.loading = loading;
|
||||
var container = root();
|
||||
if (!container) return;
|
||||
container.classList.toggle('is-loading', loading);
|
||||
}
|
||||
|
||||
function setDenied(denied) {
|
||||
pageState.denied = denied;
|
||||
var container = root();
|
||||
if (!container) return;
|
||||
container.classList.toggle('is-denied', denied);
|
||||
}
|
||||
|
||||
function normalizeUser(item) {
|
||||
return {
|
||||
user_id: item.user_id || item.userId || item.target_user_id || '',
|
||||
display_user_id:
|
||||
item.display_user_id ||
|
||||
item.displayUserId ||
|
||||
item.target_display_user_id ||
|
||||
'',
|
||||
username: item.username || item.name || item.target_username || '',
|
||||
avatar: item.avatar || item.target_avatar || '',
|
||||
country: item.country || '',
|
||||
levels: item.levels || {},
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeBlock(item) {
|
||||
return {
|
||||
block_id: item.block_id || item.blockId || '',
|
||||
target_user_id: item.target_user_id || item.targetUserId || '',
|
||||
target_display_user_id:
|
||||
item.target_display_user_id || item.targetDisplayUserId || '',
|
||||
target_username: item.target_username || item.targetUsername || '',
|
||||
target_avatar: item.target_avatar || item.targetAvatar || '',
|
||||
country: item.country || '',
|
||||
blocked_until_ms:
|
||||
item.blocked_until_ms || item.blockedUntilMs || item.unblockAtMs || 0,
|
||||
status: item.status || 'active',
|
||||
};
|
||||
}
|
||||
|
||||
function apiSearchUser(keyword) {
|
||||
if (!api || !api.searchUsers) {
|
||||
return Promise.reject(new Error('API not available.'));
|
||||
}
|
||||
return api.searchUsers(keyword, 20).then(function (data) {
|
||||
var items = (data && data.items) || [];
|
||||
return items.map(normalizeUser);
|
||||
});
|
||||
}
|
||||
|
||||
function loadOverview() {
|
||||
if (!api || !api.overview) {
|
||||
setDenied(true);
|
||||
toast('Permission denied.');
|
||||
return Promise.resolve();
|
||||
}
|
||||
setPageLoading(true);
|
||||
return api
|
||||
.overview()
|
||||
.then(function (data) {
|
||||
pageState.overview = data || {};
|
||||
setDenied(false);
|
||||
return refreshBlocks();
|
||||
})
|
||||
.catch(function (error) {
|
||||
setDenied(true);
|
||||
showError(error);
|
||||
})
|
||||
.finally(function () {
|
||||
setPageLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
function refreshBlocks() {
|
||||
if (!api || !api.listBlocks) return Promise.resolve();
|
||||
return api
|
||||
.listBlocks('active', 50)
|
||||
.then(function (data) {
|
||||
blockState.blockedUsers = ((data && data.items) || []).map(
|
||||
normalizeBlock
|
||||
);
|
||||
renderBlockedList();
|
||||
})
|
||||
.catch(showError);
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (window.HyAppBridge && window.HyAppBridge.back) {
|
||||
window.HyAppBridge.back();
|
||||
return;
|
||||
}
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
}
|
||||
}
|
||||
|
||||
function renderUserRow(user, actionText, onAction) {
|
||||
var row = createNode('div', 'searched-user');
|
||||
var avatar = createNode('div', 'searched-avatar');
|
||||
setAvatar(avatar, user);
|
||||
var copy = createNode('div', 'blocked-row-main');
|
||||
copy.appendChild(createNode('div', 'searched-name', userName(user)));
|
||||
copy.appendChild(
|
||||
createNode('div', 'searched-meta', 'UID: ' + userDisplayId(user))
|
||||
);
|
||||
var button = createNode('button', 'user-block-button', actionText);
|
||||
button.type = 'button';
|
||||
button.addEventListener('click', onAction);
|
||||
row.appendChild(avatar);
|
||||
row.appendChild(copy);
|
||||
row.appendChild(button);
|
||||
return row;
|
||||
}
|
||||
|
||||
function renderSearchResult() {
|
||||
var container = $('blockSearchResult');
|
||||
if (!container) return;
|
||||
container.innerHTML = '';
|
||||
if (!blockState.searchedUser) return;
|
||||
container.appendChild(
|
||||
renderUserRow(blockState.searchedUser, 'Block', function () {
|
||||
selectDuration('86400000', '1 day');
|
||||
var panel = $('blockDurationPanel');
|
||||
if (panel) panel.hidden = false;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function setupLevelGrid() {
|
||||
var grid = $('levelGrid');
|
||||
if (!grid || grid.children.length) return;
|
||||
for (var level = 1; level <= 11; level += 1) {
|
||||
var button = createNode('button', '', 'Lv. ' + level);
|
||||
button.type = 'button';
|
||||
button.setAttribute('data-level', String(level));
|
||||
grid.appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureTrackSelector() {
|
||||
var panel = $('levelPanel');
|
||||
if (!panel || $('levelTrackGrid')) return;
|
||||
var title = createNode('div', 'block-section-title', 'Select level type');
|
||||
var grid = createNode('div', 'track-grid');
|
||||
grid.id = 'levelTrackGrid';
|
||||
Object.keys(TRACK_LABELS).forEach(function (track) {
|
||||
var button = createNode('button', '', TRACK_LABELS[track]);
|
||||
button.type = 'button';
|
||||
button.setAttribute('data-track', track);
|
||||
button.addEventListener('click', function () {
|
||||
levelState.selectedTrack = track;
|
||||
renderLevelButtons();
|
||||
renderLevelSearchResult();
|
||||
});
|
||||
grid.appendChild(button);
|
||||
});
|
||||
panel.insertBefore(grid, panel.firstChild);
|
||||
panel.insertBefore(title, grid);
|
||||
}
|
||||
|
||||
function currentTrackLevel(user) {
|
||||
var track = levelState.selectedTrack;
|
||||
var levels = (user && user.levels) || {};
|
||||
return levels[track] && levels[track].level ? levels[track].level : 0;
|
||||
}
|
||||
|
||||
function renderLevelSearchResult() {
|
||||
var container = $('levelSearchResult');
|
||||
if (!container) return;
|
||||
container.innerHTML = '';
|
||||
if (!levelState.searchedUser) return;
|
||||
var user = levelState.searchedUser;
|
||||
var row = createNode('div', 'searched-user');
|
||||
var avatar = createNode('div', 'searched-avatar');
|
||||
setAvatar(avatar, user);
|
||||
var copy = createNode('div', 'blocked-row-main');
|
||||
copy.appendChild(createNode('div', 'searched-name', userName(user)));
|
||||
copy.appendChild(
|
||||
createNode(
|
||||
'div',
|
||||
'searched-meta',
|
||||
'UID: ' +
|
||||
userDisplayId(user) +
|
||||
' · ' +
|
||||
TRACK_LABELS[levelState.selectedTrack] +
|
||||
' Lv. ' +
|
||||
currentTrackLevel(user)
|
||||
)
|
||||
);
|
||||
var badge = createNode(
|
||||
'div',
|
||||
'block-list-count',
|
||||
'Lv. ' + currentTrackLevel(user)
|
||||
);
|
||||
row.appendChild(avatar);
|
||||
row.appendChild(copy);
|
||||
row.appendChild(badge);
|
||||
container.appendChild(row);
|
||||
}
|
||||
|
||||
function renderLevelButtons() {
|
||||
var trackButtons = document.querySelectorAll('#levelTrackGrid [data-track]');
|
||||
trackButtons.forEach(function (button) {
|
||||
button.classList.toggle(
|
||||
'is-selected',
|
||||
button.getAttribute('data-track') === levelState.selectedTrack
|
||||
);
|
||||
});
|
||||
var buttons = document.querySelectorAll('#levelGrid [data-level]');
|
||||
buttons.forEach(function (button) {
|
||||
var isSelected =
|
||||
Number(button.getAttribute('data-level')) ===
|
||||
levelState.selectedLevel;
|
||||
button.classList.toggle('is-selected', isSelected);
|
||||
});
|
||||
var confirmButton = $('confirmLevelButton');
|
||||
if (confirmButton) {
|
||||
confirmButton.disabled =
|
||||
!levelState.searchedUser ||
|
||||
!levelState.selectedLevel ||
|
||||
levelState.submitting;
|
||||
}
|
||||
}
|
||||
|
||||
function selectLevel(level) {
|
||||
levelState.selectedLevel = Number(level) || 0;
|
||||
renderLevelButtons();
|
||||
}
|
||||
|
||||
function renderDurationButtons() {
|
||||
var buttons = document.querySelectorAll('#durationGrid [data-duration-ms]');
|
||||
buttons.forEach(function (button) {
|
||||
var isSelected =
|
||||
Number(button.getAttribute('data-duration-ms')) ===
|
||||
blockState.selectedDurationMs;
|
||||
button.classList.toggle('is-selected', isSelected);
|
||||
});
|
||||
var confirmButton = $('confirmBlockButton');
|
||||
if (confirmButton) {
|
||||
confirmButton.disabled =
|
||||
!blockState.searchedUser || blockState.submitting;
|
||||
}
|
||||
}
|
||||
|
||||
function selectDuration(durationMs, label) {
|
||||
blockState.selectedDurationMs = Number(durationMs) || 0;
|
||||
blockState.selectedDurationLabel = label || 'Permanent';
|
||||
renderDurationButtons();
|
||||
}
|
||||
|
||||
function renderBlockedList() {
|
||||
var container = $('blockedList');
|
||||
var count = $('blockListCount');
|
||||
if (!container) return;
|
||||
container.innerHTML = '';
|
||||
if (count) count.textContent = String(blockState.blockedUsers.length);
|
||||
if (!blockState.blockedUsers.length) {
|
||||
container.appendChild(
|
||||
createNode('div', 'blocked-empty', 'No blocked users.')
|
||||
);
|
||||
return;
|
||||
}
|
||||
blockState.blockedUsers.forEach(function (user) {
|
||||
var row = createNode('div', 'blocked-row');
|
||||
var avatar = createNode('div', 'blocked-avatar');
|
||||
setAvatar(avatar, user);
|
||||
var copy = createNode('div', 'blocked-row-main');
|
||||
copy.appendChild(createNode('div', 'blocked-name', userName(user)));
|
||||
copy.appendChild(
|
||||
createNode('div', 'blocked-meta', 'UID: ' + userDisplayId(user))
|
||||
);
|
||||
copy.appendChild(
|
||||
createNode(
|
||||
'div',
|
||||
'blocked-time',
|
||||
'Unblock time: ' + formatDateTime(user.blocked_until_ms)
|
||||
)
|
||||
);
|
||||
var unblockButton = createNode(
|
||||
'button',
|
||||
'user-unblock-button',
|
||||
'Unblock'
|
||||
);
|
||||
unblockButton.type = 'button';
|
||||
unblockButton.addEventListener('click', function () {
|
||||
unblockUser(user.block_id);
|
||||
});
|
||||
row.appendChild(avatar);
|
||||
row.appendChild(copy);
|
||||
row.appendChild(unblockButton);
|
||||
container.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function unblockUser(blockId) {
|
||||
if (!blockId || blockState.submitting) return;
|
||||
blockState.submitting = true;
|
||||
api.unblockUser(blockId, { reason: 'manager_center' })
|
||||
.then(function () {
|
||||
toast('User unblocked.');
|
||||
return refreshBlocks();
|
||||
})
|
||||
.catch(showError)
|
||||
.finally(function () {
|
||||
blockState.submitting = false;
|
||||
});
|
||||
}
|
||||
|
||||
function resetBlockSearch() {
|
||||
var input = $('blockSearchInput');
|
||||
var panel = $('blockDurationPanel');
|
||||
blockState.searchedUser = null;
|
||||
blockState.selectedDurationMs = 0;
|
||||
blockState.selectedDurationLabel = '';
|
||||
if (input) input.value = '';
|
||||
if (panel) panel.hidden = true;
|
||||
renderSearchResult();
|
||||
renderDurationButtons();
|
||||
}
|
||||
|
||||
function openBlockModal() {
|
||||
if (pageState.denied) {
|
||||
toast('Permission denied.');
|
||||
return;
|
||||
}
|
||||
var modal = $('blockModal');
|
||||
if (!modal) return;
|
||||
resetBlockSearch();
|
||||
refreshBlocks();
|
||||
modal.hidden = false;
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
setTimeout(function () {
|
||||
var input = $('blockSearchInput');
|
||||
if (input) input.focus();
|
||||
}, 30);
|
||||
}
|
||||
|
||||
function closeBlockModal() {
|
||||
var modal = $('blockModal');
|
||||
if (!modal) return;
|
||||
modal.hidden = true;
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
function resetLevelSearch() {
|
||||
var input = $('levelSearchInput');
|
||||
var panel = $('levelPanel');
|
||||
levelState.searchedUser = null;
|
||||
levelState.selectedTrack = 'wealth';
|
||||
levelState.selectedLevel = 0;
|
||||
if (input) input.value = '';
|
||||
if (panel) panel.hidden = true;
|
||||
renderLevelSearchResult();
|
||||
renderLevelButtons();
|
||||
}
|
||||
|
||||
function openLevelModal() {
|
||||
if (pageState.denied) {
|
||||
toast('Permission denied.');
|
||||
return;
|
||||
}
|
||||
var modal = $('levelModal');
|
||||
if (!modal) return;
|
||||
resetLevelSearch();
|
||||
modal.hidden = false;
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
setTimeout(function () {
|
||||
var input = $('levelSearchInput');
|
||||
if (input) input.focus();
|
||||
}, 30);
|
||||
}
|
||||
|
||||
function closeLevelModal() {
|
||||
var modal = $('levelModal');
|
||||
if (!modal) return;
|
||||
modal.hidden = true;
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
function searchBlockUser() {
|
||||
var input = $('blockSearchInput');
|
||||
var value = input ? input.value.trim() : '';
|
||||
var panel = $('blockDurationPanel');
|
||||
if (!value) {
|
||||
toast('Enter user ID.');
|
||||
return;
|
||||
}
|
||||
apiSearchUser(value)
|
||||
.then(function (items) {
|
||||
if (!items.length) {
|
||||
blockState.searchedUser = null;
|
||||
toast('No user found.');
|
||||
return;
|
||||
}
|
||||
blockState.searchedUser = items[0];
|
||||
blockState.selectedDurationMs = 0;
|
||||
blockState.selectedDurationLabel = '';
|
||||
if (panel) panel.hidden = true;
|
||||
renderSearchResult();
|
||||
renderDurationButtons();
|
||||
})
|
||||
.catch(showError);
|
||||
}
|
||||
|
||||
function confirmBlockUser() {
|
||||
if (!blockState.searchedUser) {
|
||||
toast('Search a user first.');
|
||||
return;
|
||||
}
|
||||
if (blockState.submitting) return;
|
||||
var durationMs = blockState.selectedDurationMs || PERMANENT_BLOCK_MS;
|
||||
blockState.submitting = true;
|
||||
renderDurationButtons();
|
||||
api.blockUser({
|
||||
command_id: commandId('manager-block'),
|
||||
target_user_id: userId(blockState.searchedUser),
|
||||
blocked_until_ms: Date.now() + durationMs,
|
||||
reason: 'manager_center',
|
||||
})
|
||||
.then(function () {
|
||||
toast('User blocked.');
|
||||
resetBlockSearch();
|
||||
return refreshBlocks();
|
||||
})
|
||||
.catch(showError)
|
||||
.finally(function () {
|
||||
blockState.submitting = false;
|
||||
renderDurationButtons();
|
||||
});
|
||||
}
|
||||
|
||||
function searchLevelUser() {
|
||||
var input = $('levelSearchInput');
|
||||
var value = input ? input.value.trim() : '';
|
||||
var panel = $('levelPanel');
|
||||
if (!value) {
|
||||
toast('Enter user ID.');
|
||||
return;
|
||||
}
|
||||
apiSearchUser(value)
|
||||
.then(function (items) {
|
||||
if (!items.length) {
|
||||
levelState.searchedUser = null;
|
||||
toast('No user found.');
|
||||
return;
|
||||
}
|
||||
levelState.searchedUser = items[0];
|
||||
levelState.selectedLevel = 0;
|
||||
if (panel) panel.hidden = false;
|
||||
renderLevelSearchResult();
|
||||
renderLevelButtons();
|
||||
})
|
||||
.catch(showError);
|
||||
}
|
||||
|
||||
function confirmLevelUpdate() {
|
||||
if (!levelState.searchedUser) {
|
||||
toast('Search a user first.');
|
||||
return;
|
||||
}
|
||||
if (!levelState.selectedLevel) {
|
||||
toast('Select a user level.');
|
||||
return;
|
||||
}
|
||||
if (levelState.submitting) return;
|
||||
levelState.submitting = true;
|
||||
renderLevelButtons();
|
||||
api.updateLevel({
|
||||
command_id: commandId('manager-level'),
|
||||
target_user_id: userId(levelState.searchedUser),
|
||||
track: levelState.selectedTrack,
|
||||
level: levelState.selectedLevel,
|
||||
reason: 'manager_center',
|
||||
})
|
||||
.then(function () {
|
||||
toast('User level updated.');
|
||||
closeLevelModal();
|
||||
})
|
||||
.catch(showError)
|
||||
.finally(function () {
|
||||
levelState.submitting = false;
|
||||
renderLevelButtons();
|
||||
});
|
||||
}
|
||||
|
||||
function ensureGrantModal() {
|
||||
if ($('grantModal')) return;
|
||||
var modal = createNode('div', 'block-modal grant-modal');
|
||||
modal.id = 'grantModal';
|
||||
modal.hidden = true;
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
modal.innerHTML =
|
||||
'<button class="block-backdrop" id="grantBackdrop" type="button" aria-label="Close"></button>' +
|
||||
'<section class="block-dialog" role="dialog" aria-modal="true" aria-labelledby="grantDialogTitle">' +
|
||||
'<div class="block-dialog-header"><h2 id="grantDialogTitle">Send</h2>' +
|
||||
'<button class="block-close" id="grantCloseButton" type="button" aria-label="Close"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg></button></div>' +
|
||||
'<div class="block-dialog-body"><div class="block-field"><label for="grantSearchInput">User ID</label>' +
|
||||
'<div class="block-search-row"><input id="grantSearchInput" type="text" inputmode="numeric" autocomplete="off" placeholder="Enter user ID"/><button id="grantSearchButton" type="button">Search</button></div></div>' +
|
||||
'<div class="block-search-result" id="grantSearchResult" aria-live="polite"></div>' +
|
||||
'<div class="level-panel" id="grantPanel" hidden><div class="block-section-title" id="grantOptionTitle">Select item</div><div class="grant-option-grid" id="grantOptionGrid"></div><div class="block-section-title" id="grantDurationTitle" hidden>Select duration</div><div class="duration-grid grant-duration-grid" id="grantDurationGrid" hidden></div><button class="confirm-level-button" id="confirmGrantButton" type="button" disabled>Send now</button></div></div></section>';
|
||||
document.querySelector('.manager-center').appendChild(modal);
|
||||
$('grantBackdrop').addEventListener('click', closeGrantModal);
|
||||
$('grantCloseButton').addEventListener('click', closeGrantModal);
|
||||
$('grantSearchButton').addEventListener('click', searchGrantUser);
|
||||
$('grantSearchInput').addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Enter') searchGrantUser();
|
||||
});
|
||||
$('confirmGrantButton').addEventListener('click', confirmGrant);
|
||||
}
|
||||
|
||||
function openGrantModal(action) {
|
||||
if (pageState.denied) {
|
||||
toast('Permission denied.');
|
||||
return;
|
||||
}
|
||||
ensureGrantModal();
|
||||
grantState.action = action;
|
||||
grantState.resourceType = RESOURCE_TYPES[action] || '';
|
||||
grantState.title = ACTION_LABELS[action] || 'Send';
|
||||
grantState.searchedUser = null;
|
||||
grantState.selectedResourceId = '';
|
||||
grantState.selectedResourceDurationMs = DEFAULT_RESOURCE_GRANT_DURATION_MS;
|
||||
grantState.selectedVipLevel = 0;
|
||||
$('grantDialogTitle').textContent = grantState.title;
|
||||
$('grantSearchInput').value = '';
|
||||
$('grantSearchResult').innerHTML = '';
|
||||
$('grantPanel').hidden = true;
|
||||
$('grantModal').hidden = false;
|
||||
$('grantModal').setAttribute('aria-hidden', 'false');
|
||||
renderGrantOptions();
|
||||
if (grantState.resourceType) loadGrantResources(grantState.resourceType);
|
||||
setTimeout(function () {
|
||||
$('grantSearchInput').focus();
|
||||
}, 30);
|
||||
}
|
||||
|
||||
function closeGrantModal() {
|
||||
var modal = $('grantModal');
|
||||
if (!modal) return;
|
||||
modal.hidden = true;
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
function loadGrantResources(resourceType) {
|
||||
if (pageState.resourcesByType[resourceType]) {
|
||||
renderGrantOptions();
|
||||
return;
|
||||
}
|
||||
api.listResources(resourceType, 20)
|
||||
.then(function (data) {
|
||||
pageState.resourcesByType[resourceType] =
|
||||
(data && data.items) || [];
|
||||
renderGrantOptions();
|
||||
})
|
||||
.catch(showError);
|
||||
}
|
||||
|
||||
function searchGrantUser() {
|
||||
var value = $('grantSearchInput').value.trim();
|
||||
if (!value) {
|
||||
toast('Enter user ID.');
|
||||
return;
|
||||
}
|
||||
apiSearchUser(value)
|
||||
.then(function (items) {
|
||||
if (!items.length) {
|
||||
grantState.searchedUser = null;
|
||||
toast('No user found.');
|
||||
return;
|
||||
}
|
||||
grantState.searchedUser = items[0];
|
||||
var container = $('grantSearchResult');
|
||||
container.innerHTML = '';
|
||||
container.appendChild(
|
||||
renderUserRow(grantState.searchedUser, 'Select', function () {
|
||||
$('grantPanel').hidden = false;
|
||||
renderGrantOptions();
|
||||
})
|
||||
);
|
||||
$('grantPanel').hidden = false;
|
||||
renderGrantOptions();
|
||||
})
|
||||
.catch(showError);
|
||||
}
|
||||
|
||||
function renderGrantOptions() {
|
||||
var grid = $('grantOptionGrid');
|
||||
if (!grid) return;
|
||||
grid.innerHTML = '';
|
||||
var durationTitle = $('grantDurationTitle');
|
||||
var durationGrid = $('grantDurationGrid');
|
||||
if (durationTitle) {
|
||||
durationTitle.hidden = false;
|
||||
durationTitle.textContent =
|
||||
grantState.action === 'send-vip'
|
||||
? 'Duration: 30 days'
|
||||
: 'Select duration';
|
||||
}
|
||||
if (durationGrid) {
|
||||
durationGrid.innerHTML = '';
|
||||
durationGrid.hidden = grantState.action === 'send-vip';
|
||||
}
|
||||
if (grantState.action === 'send-vip') {
|
||||
$('grantOptionTitle').textContent = 'Select VIP level';
|
||||
[1, 2, 3].forEach(function (level) {
|
||||
var button = createNode('button', '', 'VIP ' + level);
|
||||
button.type = 'button';
|
||||
button.classList.toggle(
|
||||
'is-selected',
|
||||
grantState.selectedVipLevel === level
|
||||
);
|
||||
button.addEventListener('click', function () {
|
||||
grantState.selectedVipLevel = level;
|
||||
renderGrantOptions();
|
||||
});
|
||||
grid.appendChild(button);
|
||||
});
|
||||
} else {
|
||||
$('grantOptionTitle').textContent = 'Select resource';
|
||||
var resources = pageState.resourcesByType[grantState.resourceType] || [];
|
||||
if (!resources.length) {
|
||||
grid.appendChild(createNode('div', 'blocked-empty', 'No resource available.'));
|
||||
}
|
||||
resources.forEach(function (resource) {
|
||||
var button = createNode('button', 'grant-resource-option');
|
||||
button.type = 'button';
|
||||
button.classList.toggle(
|
||||
'is-selected',
|
||||
String(grantState.selectedResourceId) ===
|
||||
String(resource.resource_id)
|
||||
);
|
||||
button.textContent = resource.name || 'Resource #' + resource.resource_id;
|
||||
button.addEventListener('click', function () {
|
||||
grantState.selectedResourceId = resource.resource_id;
|
||||
renderGrantOptions();
|
||||
});
|
||||
grid.appendChild(button);
|
||||
});
|
||||
if (durationGrid) {
|
||||
RESOURCE_GRANT_DURATIONS.forEach(function (item) {
|
||||
var durationButton = createNode('button', '', item.label);
|
||||
durationButton.type = 'button';
|
||||
durationButton.classList.toggle(
|
||||
'is-selected',
|
||||
grantState.selectedResourceDurationMs === item.ms
|
||||
);
|
||||
durationButton.addEventListener('click', function () {
|
||||
grantState.selectedResourceDurationMs = item.ms;
|
||||
renderGrantOptions();
|
||||
});
|
||||
durationGrid.appendChild(durationButton);
|
||||
});
|
||||
}
|
||||
}
|
||||
var confirm = $('confirmGrantButton');
|
||||
if (confirm) {
|
||||
confirm.disabled =
|
||||
!grantState.searchedUser ||
|
||||
grantState.submitting ||
|
||||
(grantState.action === 'send-vip'
|
||||
? !grantState.selectedVipLevel
|
||||
: !grantState.selectedResourceId ||
|
||||
!grantState.selectedResourceDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
function confirmGrant() {
|
||||
if (!grantState.searchedUser || grantState.submitting) return;
|
||||
grantState.submitting = true;
|
||||
renderGrantOptions();
|
||||
var payload = {
|
||||
command_id: commandId('manager-grant'),
|
||||
target_user_id: userId(grantState.searchedUser),
|
||||
reason: 'manager_center',
|
||||
};
|
||||
var request;
|
||||
if (grantState.action === 'send-vip') {
|
||||
payload.level = grantState.selectedVipLevel;
|
||||
request = api.grantVip(payload);
|
||||
} else {
|
||||
payload.resource_id = grantState.selectedResourceId;
|
||||
payload.duration_ms = grantState.selectedResourceDurationMs;
|
||||
request = api.grantResource(payload);
|
||||
}
|
||||
request
|
||||
.then(function () {
|
||||
toast('Sent successfully.');
|
||||
closeGrantModal();
|
||||
})
|
||||
.catch(showError)
|
||||
.finally(function () {
|
||||
grantState.submitting = false;
|
||||
renderGrantOptions();
|
||||
});
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
var backButton = $('backButton');
|
||||
if (backButton) backButton.addEventListener('click', goBack);
|
||||
document.querySelectorAll('[data-action]').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
var action = button.getAttribute('data-action');
|
||||
if (action === 'block-user') {
|
||||
openBlockModal();
|
||||
return;
|
||||
}
|
||||
if (action === 'update-user-level') {
|
||||
openLevelModal();
|
||||
return;
|
||||
}
|
||||
openGrantModal(action);
|
||||
});
|
||||
});
|
||||
setupLevelGrid();
|
||||
ensureTrackSelector();
|
||||
var closeButton = $('blockCloseButton');
|
||||
var backdrop = $('blockBackdrop');
|
||||
var searchButton = $('blockSearchButton');
|
||||
var searchInput = $('blockSearchInput');
|
||||
var confirmButton = $('confirmBlockButton');
|
||||
var levelCloseButton = $('levelCloseButton');
|
||||
var levelBackdrop = $('levelBackdrop');
|
||||
var levelSearchButton = $('levelSearchButton');
|
||||
var levelSearchInput = $('levelSearchInput');
|
||||
var confirmLevelButton = $('confirmLevelButton');
|
||||
if (closeButton) closeButton.addEventListener('click', closeBlockModal);
|
||||
if (backdrop) backdrop.addEventListener('click', closeBlockModal);
|
||||
if (levelCloseButton) levelCloseButton.addEventListener('click', closeLevelModal);
|
||||
if (levelBackdrop) levelBackdrop.addEventListener('click', closeLevelModal);
|
||||
if (searchButton) searchButton.addEventListener('click', searchBlockUser);
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Enter') searchBlockUser();
|
||||
});
|
||||
}
|
||||
if (levelSearchButton) levelSearchButton.addEventListener('click', searchLevelUser);
|
||||
if (levelSearchInput) {
|
||||
levelSearchInput.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Enter') searchLevelUser();
|
||||
});
|
||||
}
|
||||
if (confirmButton) confirmButton.addEventListener('click', confirmBlockUser);
|
||||
if (confirmLevelButton) {
|
||||
confirmLevelButton.addEventListener('click', confirmLevelUpdate);
|
||||
}
|
||||
document
|
||||
.querySelectorAll('#durationGrid [data-duration-ms]')
|
||||
.forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
selectDuration(
|
||||
button.getAttribute('data-duration-ms'),
|
||||
button.getAttribute('data-duration-label')
|
||||
);
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('#levelGrid [data-level]').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
selectLevel(button.getAttribute('data-level'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
bindEvents();
|
||||
renderBlockedList();
|
||||
loadOverview();
|
||||
if (window.HyAppBridge && window.HyAppBridge.ready) {
|
||||
window.HyAppBridge.ready({ page: 'manager-center' });
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
590
gonghui/manager-center/style.css
Normal file
@ -0,0 +1,590 @@
|
||||
.manager-panel {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(125, 87, 199, 0.06);
|
||||
}
|
||||
|
||||
.manager-center.is-loading .manager-grid {
|
||||
min-height: 410px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.manager-center.is-loading .manager-panel::after {
|
||||
position: absolute;
|
||||
inset: 16px;
|
||||
content: "";
|
||||
border-radius: 10px;
|
||||
background:
|
||||
linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.72), transparent)
|
||||
0 0 / 180px 100% no-repeat,
|
||||
linear-gradient(#f1e7ff, #f1e7ff) 0 0 / 100% 132px no-repeat,
|
||||
linear-gradient(#f1e7ff, #f1e7ff) 0 144px / 100% 132px no-repeat,
|
||||
linear-gradient(#f1e7ff, #f1e7ff) 0 288px / 100% 104px no-repeat;
|
||||
animation: managerSkeleton 1.1s linear infinite;
|
||||
}
|
||||
|
||||
.manager-center.is-denied .manager-grid {
|
||||
opacity: 0.46;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes managerSkeleton {
|
||||
0% {
|
||||
background-position:
|
||||
-180px 0,
|
||||
0 0,
|
||||
0 144px,
|
||||
0 288px;
|
||||
}
|
||||
100% {
|
||||
background-position:
|
||||
calc(100% + 180px) 0,
|
||||
0 0,
|
||||
0 144px,
|
||||
0 288px;
|
||||
}
|
||||
}
|
||||
|
||||
.manager-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.manager-tile {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 132px;
|
||||
overflow: hidden;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #fbfcfc;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.95),
|
||||
0 6px 16px rgba(99, 69, 159, 0.05);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.manager-tile:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.manager-tile:focus-visible {
|
||||
outline: 2px solid rgba(125, 87, 199, 0.28);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.manager-tile-wide {
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: 92px minmax(0, 1fr);
|
||||
flex-direction: row;
|
||||
min-height: 104px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.tile-title {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
order: -1;
|
||||
align-self: start;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
color: var(--text);
|
||||
font-size: 17px;
|
||||
font-weight: 950;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.16;
|
||||
}
|
||||
|
||||
.manager-tile-wide .tile-title {
|
||||
order: 0;
|
||||
align-self: center;
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
.tile-icon {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-self: center;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
margin: 14px auto 0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.manager-tile-wide .tile-icon {
|
||||
flex: 0 0 72px;
|
||||
justify-self: start;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tile-icon svg {
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
}
|
||||
|
||||
.tile-icon path {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 2.8;
|
||||
}
|
||||
|
||||
.level-icon {
|
||||
color: #7d57c7;
|
||||
background: #f4ebff;
|
||||
}
|
||||
|
||||
.frame-icon {
|
||||
color: #7d57c7;
|
||||
background: #f4ebff;
|
||||
}
|
||||
|
||||
.car-icon {
|
||||
color: #4e78d7;
|
||||
background: #eef4ff;
|
||||
}
|
||||
|
||||
.vip-icon {
|
||||
color: #d5a032;
|
||||
background: #fff7dc;
|
||||
}
|
||||
|
||||
.block-icon {
|
||||
color: #d95d72;
|
||||
background: #fff1f4;
|
||||
}
|
||||
|
||||
.block-modal[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.block-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 80;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.block-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border: 0;
|
||||
background: rgba(24, 21, 33, 0.48);
|
||||
}
|
||||
|
||||
.block-dialog {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
width: min(100%, 430px);
|
||||
max-height: min(86dvh, 720px);
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border-radius: 18px;
|
||||
background: #fff;
|
||||
box-shadow: 0 -8px 34px rgba(43, 34, 65, 0.18);
|
||||
}
|
||||
|
||||
.block-dialog-header {
|
||||
display: flex;
|
||||
min-height: 64px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 18px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.block-dialog-header h2 {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-size: 20px;
|
||||
font-weight: 950;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.block-close {
|
||||
display: flex;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--primary-soft);
|
||||
color: var(--primary-deep);
|
||||
}
|
||||
|
||||
.block-close svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.block-close path {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.block-dialog-body {
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 16px 18px 20px;
|
||||
}
|
||||
|
||||
.block-field {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.block-field label,
|
||||
.block-section-title {
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.block-search-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 100px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.block-search-row input {
|
||||
min-width: 0;
|
||||
height: 48px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: rgba(252, 249, 255, 0.86);
|
||||
color: var(--text);
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.block-search-row input:focus {
|
||||
border-color: rgba(125, 87, 199, 0.42);
|
||||
box-shadow: 0 0 0 3px rgba(125, 87, 199, 0.12);
|
||||
}
|
||||
|
||||
.block-search-row input::placeholder {
|
||||
color: rgba(42, 38, 57, 0.38);
|
||||
}
|
||||
|
||||
.block-search-row button,
|
||||
.confirm-block-button,
|
||||
.confirm-level-button,
|
||||
.user-level-button,
|
||||
.user-unblock-button,
|
||||
.user-block-button {
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: var(--button);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 950;
|
||||
}
|
||||
|
||||
.block-search-row button {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.block-search-result {
|
||||
min-height: 28px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.searched-user,
|
||||
.blocked-row {
|
||||
display: grid;
|
||||
grid-template-columns: 48px minmax(0, 1fr) auto;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
}
|
||||
|
||||
.searched-user {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.searched-avatar,
|
||||
.blocked-avatar {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: var(--primary);
|
||||
color: var(--primary-deep);
|
||||
font-size: 18px;
|
||||
font-weight: 950;
|
||||
}
|
||||
|
||||
.searched-avatar img,
|
||||
.blocked-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.searched-name,
|
||||
.blocked-name {
|
||||
overflow: hidden;
|
||||
color: var(--text);
|
||||
font-size: 15px;
|
||||
font-weight: 950;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.searched-meta,
|
||||
.blocked-meta,
|
||||
.blocked-time {
|
||||
margin-top: 3px;
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.user-block-button {
|
||||
min-width: 70px;
|
||||
height: 40px;
|
||||
padding: 0 14px;
|
||||
background: #d95d72;
|
||||
}
|
||||
|
||||
.user-unblock-button {
|
||||
min-width: 76px;
|
||||
height: 38px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid rgba(125, 87, 199, 0.22);
|
||||
background: #fff;
|
||||
color: var(--primary-deep);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.block-duration-panel {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
padding: 14px;
|
||||
border: 1px solid rgba(125, 87, 199, 0.12);
|
||||
border-radius: 10px;
|
||||
background: rgba(248, 242, 255, 0.68);
|
||||
}
|
||||
|
||||
.block-duration-panel[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.level-panel {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
padding: 14px;
|
||||
border: 1px solid rgba(125, 87, 199, 0.12);
|
||||
border-radius: 10px;
|
||||
background: rgba(248, 242, 255, 0.68);
|
||||
}
|
||||
|
||||
.level-panel[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.duration-grid,
|
||||
.track-grid,
|
||||
.grant-option-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.level-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.duration-grid button,
|
||||
.level-grid button,
|
||||
.track-grid button,
|
||||
.grant-option-grid button {
|
||||
min-height: 38px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.duration-grid button.is-selected,
|
||||
.level-grid button.is-selected,
|
||||
.track-grid button.is-selected,
|
||||
.grant-option-grid button.is-selected {
|
||||
border-color: var(--primary-deep);
|
||||
background: var(--primary-deep);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.grant-option-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.grant-resource-option {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.confirm-block-button,
|
||||
.confirm-level-button {
|
||||
height: 46px;
|
||||
}
|
||||
|
||||
.confirm-block-button:disabled,
|
||||
.confirm-level-button:disabled {
|
||||
background: var(--button-disabled);
|
||||
color: rgba(42, 38, 57, 0.42);
|
||||
}
|
||||
|
||||
.block-list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 18px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.block-list-count {
|
||||
min-width: 28px;
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--primary-soft);
|
||||
color: var(--primary-deep);
|
||||
font-size: 12px;
|
||||
font-weight: 950;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.blocked-list {
|
||||
display: grid;
|
||||
max-height: 216px;
|
||||
margin-top: 10px;
|
||||
overflow-y: auto;
|
||||
gap: 10px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.blocked-row {
|
||||
min-height: 76px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.blocked-row-main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.blocked-time {
|
||||
color: #d95d72;
|
||||
}
|
||||
|
||||
.blocked-empty {
|
||||
display: flex;
|
||||
min-height: 92px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed var(--line);
|
||||
border-radius: 8px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
font-weight: 850;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.manager-panel {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.manager-grid {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.manager-tile {
|
||||
min-height: 126px;
|
||||
padding: 12px 10px 14px;
|
||||
}
|
||||
|
||||
.tile-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tile-icon {
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
.tile-icon svg {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
}
|
||||
|
||||
.block-modal {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.block-dialog-header {
|
||||
min-height: 58px;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.block-dialog-body {
|
||||
padding: 14px 16px 18px;
|
||||
}
|
||||
|
||||
.block-search-row {
|
||||
grid-template-columns: minmax(0, 1fr) 88px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.duration-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.level-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||