diff --git a/activity/cp/assets/avatar-sample.png b/activity/cp/assets/avatar-sample.png new file mode 100644 index 0000000..4b3958a Binary files /dev/null and b/activity/cp/assets/avatar-sample.png differ diff --git a/activity/cp/assets/countdown-heart.png b/activity/cp/assets/countdown-heart.png new file mode 100644 index 0000000..6092ae4 Binary files /dev/null and b/activity/cp/assets/countdown-heart.png differ diff --git a/activity/cp/assets/crown-bronze.png b/activity/cp/assets/crown-bronze.png new file mode 100644 index 0000000..30f4c4c Binary files /dev/null and b/activity/cp/assets/crown-bronze.png differ diff --git a/activity/cp/assets/crown-gold.png b/activity/cp/assets/crown-gold.png new file mode 100644 index 0000000..6a8d2df Binary files /dev/null and b/activity/cp/assets/crown-gold.png differ diff --git a/activity/cp/assets/crown-silver.png b/activity/cp/assets/crown-silver.png new file mode 100644 index 0000000..bfc33f3 Binary files /dev/null and b/activity/cp/assets/crown-silver.png differ diff --git a/activity/cp/assets/deco-line.png b/activity/cp/assets/deco-line.png new file mode 100644 index 0000000..6c89a9d Binary files /dev/null and b/activity/cp/assets/deco-line.png differ diff --git a/activity/cp/assets/gift-plane.png b/activity/cp/assets/gift-plane.png new file mode 100644 index 0000000..c17113a Binary files /dev/null and b/activity/cp/assets/gift-plane.png differ diff --git a/activity/cp/assets/hero-bg.png b/activity/cp/assets/hero-bg.png new file mode 100644 index 0000000..fdf8c8a Binary files /dev/null and b/activity/cp/assets/hero-bg.png differ diff --git a/activity/cp/assets/panel-bottom.png b/activity/cp/assets/panel-bottom.png new file mode 100644 index 0000000..a83699d Binary files /dev/null and b/activity/cp/assets/panel-bottom.png differ diff --git a/activity/cp/assets/panel-mid.png b/activity/cp/assets/panel-mid.png new file mode 100644 index 0000000..90d9d5e Binary files /dev/null and b/activity/cp/assets/panel-mid.png differ diff --git a/activity/cp/assets/panel-top.png b/activity/cp/assets/panel-top.png new file mode 100644 index 0000000..6b31291 Binary files /dev/null and b/activity/cp/assets/panel-top.png differ diff --git a/activity/cp/assets/rank-crown-gold.png b/activity/cp/assets/rank-crown-gold.png new file mode 100644 index 0000000..8f94635 Binary files /dev/null and b/activity/cp/assets/rank-crown-gold.png differ diff --git a/activity/cp/assets/rank-crown-silver.png b/activity/cp/assets/rank-crown-silver.png new file mode 100644 index 0000000..1dfcfdc Binary files /dev/null and b/activity/cp/assets/rank-crown-silver.png differ diff --git a/activity/cp/assets/rank-wing-heart.png b/activity/cp/assets/rank-wing-heart.png new file mode 100644 index 0000000..b865472 Binary files /dev/null and b/activity/cp/assets/rank-wing-heart.png differ diff --git a/activity/cp/assets/reward-frame.png b/activity/cp/assets/reward-frame.png new file mode 100644 index 0000000..29eb8fa Binary files /dev/null and b/activity/cp/assets/reward-frame.png differ diff --git a/activity/cp/assets/score-heart.png b/activity/cp/assets/score-heart.png new file mode 100644 index 0000000..d878b42 Binary files /dev/null and b/activity/cp/assets/score-heart.png differ diff --git a/activity/cp/assets/tab-active.png b/activity/cp/assets/tab-active.png new file mode 100644 index 0000000..b85f27f Binary files /dev/null and b/activity/cp/assets/tab-active.png differ diff --git a/activity/cp/assets/tab-inactive.png b/activity/cp/assets/tab-inactive.png new file mode 100644 index 0000000..5236404 Binary files /dev/null and b/activity/cp/assets/tab-inactive.png differ diff --git a/activity/cp/index.html b/activity/cp/index.html new file mode 100644 index 0000000..5f21eb1 --- /dev/null +++ b/activity/cp/index.html @@ -0,0 +1,109 @@ + + + + + + Last Weekly CP + + + + +
+
+
+
+
+ + +
+ + + +
+
+ + 02 + D +
+
+ + 02 + H +
+
+ + 02 + M +
+
+ + 02 + S +
+
+ + + +
+ +
+
+
+
+ + + + + + diff --git a/activity/cp/script.js b/activity/cp/script.js new file mode 100644 index 0000000..8615733 --- /dev/null +++ b/activity/cp/script.js @@ -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, '''); + } + + 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 [ + '
', + '', + '', + '', + '
', + bodyHTML, + '
', + '
', + ].join(''); + } + + function renderCPRow(row) { + var center = row.type === 'score' + ? [ + '', + '', + escapeHTML(row.score), + '', + ].join('') + : [ + '
', + '', + '
', + ].join(''); + + return [ + '
', + '
', + '
', + '', + escapeHTML(row.leftName), + '', + '', + escapeHTML(row.rightName), + '', + '', + '', + center, + '
', + ].join(''); + } + + function renderPlaza() { + var firstBody = [ + '

', + t('cp.plazaGiftTitle', 'Gifts exchanged between CPs ≥ 5000 gold will be displayed here'), + '

', + '

', + t('cp.plazaGiftSubtitle', '(Display the last 10 records, sorted in ascending order.)'), + '

', + '
', + mockData.plaza.exchangeRows.map(renderCPRow).join(''), + '
', + ].join(''); + var secondBody = [ + '

', + t('cp.previousNo1Title', 'The previous number one position'), + '

', + '

', + t('cp.plazaGiftSubtitle', '(Display the last 10 records, sorted in ascending order.)'), + '

', + '
', + mockData.plaza.previousRows.map(renderCPRow).join(''), + '
', + ].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 [ + '
', + units + .map(function (unit) { + return [ + '
', + '', + '02', + '', + t(unit[1], unit[2]), + '', + '
', + ].join(''); + }) + .join(''), + '
', + ].join(''); + } + + function renderPodiumCard(item) { + var crown = item.place === 1 ? 'rank-crown-gold.png' : item.place === 2 ? 'rank-crown-silver.png' : 'crown-bronze.png'; + return [ + '
', + '', + '', + '
', + '
', + '
', + escapeHTML(item.leftName), + '', + escapeHTML(item.rightName), + '
', + '
', + ].join(''); + } + + function renderRankRow(item) { + return renderCPRow({ + type: 'score', + leftName: item.leftName, + rightName: item.rightName, + score: item.score, + }); + } + + function renderRank() { + var podium = [ + '
', + mockData.rank.map(renderPodiumCard).join(''), + '
', + ].join(''); + var panelBody = [renderInlineCountdown(), '
', mockData.rank.concat(mockData.rank.slice(0, 1)).map(renderRankRow).join(''), '
'].join(''); + return podium + renderPanel('rank-panel', 2430, 1107, panelBody); + } + + function renderRewardItem(item) { + return [ + '
', + '
', + '
', + t(item.key, item.key), + '
', + item.noteKey ? '
' + t(item.noteKey, '') + '
' : '', + '
', + ].join(''); + } + + function renderRewardPanel(section, index) { + var className = ['reward-panel', 'reward-' + ['a', 'b', 'c'][index]].join(' '); + var top = [1816, 2784, 3752][index]; + var body = [ + '', + '
', + section.items.map(renderRewardItem).join(''), + '
', + ].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(); + } +})(); diff --git a/activity/cp/style.css b/activity/cp/style.css new file mode 100644 index 0000000..cf97208 --- /dev/null +++ b/activity/cp/style.css @@ -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; +} diff --git a/activity/room-reward/index.html b/activity/room-reward/index.html index 772fcb1..fc099b5 100644 --- a/activity/room-reward/index.html +++ b/activity/room-reward/index.html @@ -106,50 +106,50 @@
-
- -
Namename...
-
ID: 123456789
-
- This week's contribution: -
-
- This week's rewards: -
- - -
+
+ +
Namename...
+
ID: 123456789
+
+ This week's contribution: +
+
+ This week's rewards: +
+ + +
+ + + + + + + diff --git a/gonghui/manager-center/script.js b/gonghui/manager-center/script.js new file mode 100644 index 0000000..f43ffab --- /dev/null +++ b/gonghui/manager-center/script.js @@ -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 = + '' + + ''; + 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(); + } +})(); diff --git a/gonghui/manager-center/style.css b/gonghui/manager-center/style.css new file mode 100644 index 0000000..18f7645 --- /dev/null +++ b/gonghui/manager-center/style.css @@ -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)); + } +}