diff --git a/activity/cp-space/assets/figma-brother.png b/activity/cp-space/assets/figma-brother.png new file mode 100644 index 0000000..d653eb1 Binary files /dev/null and b/activity/cp-space/assets/figma-brother.png differ diff --git a/activity/cp-space/assets/figma-cp.png b/activity/cp-space/assets/figma-cp.png new file mode 100644 index 0000000..179d383 Binary files /dev/null and b/activity/cp-space/assets/figma-cp.png differ diff --git a/activity/cp-space/assets/figma-sister.png b/activity/cp-space/assets/figma-sister.png new file mode 100644 index 0000000..46f6e9b Binary files /dev/null and b/activity/cp-space/assets/figma-sister.png differ diff --git a/activity/cp-space/assets/level-1@4x.png b/activity/cp-space/assets/level-1@4x.png new file mode 100644 index 0000000..d2d1a56 Binary files /dev/null and b/activity/cp-space/assets/level-1@4x.png differ diff --git a/activity/cp-space/assets/level-2@4x.png b/activity/cp-space/assets/level-2@4x.png new file mode 100644 index 0000000..2ee4242 Binary files /dev/null and b/activity/cp-space/assets/level-2@4x.png differ diff --git a/activity/cp-space/assets/level-3@4x.png b/activity/cp-space/assets/level-3@4x.png new file mode 100644 index 0000000..9cfa7ca Binary files /dev/null and b/activity/cp-space/assets/level-3@4x.png differ diff --git a/activity/cp-space/assets/level-4@4x.png b/activity/cp-space/assets/level-4@4x.png new file mode 100644 index 0000000..d890bbf Binary files /dev/null and b/activity/cp-space/assets/level-4@4x.png differ diff --git a/activity/cp-space/assets/level-5@4x.png b/activity/cp-space/assets/level-5@4x.png new file mode 100644 index 0000000..83ecc01 Binary files /dev/null and b/activity/cp-space/assets/level-5@4x.png differ diff --git a/activity/cp-space/assets/space-bg.svg b/activity/cp-space/assets/space-bg.svg new file mode 100644 index 0000000..f148f3f --- /dev/null +++ b/activity/cp-space/assets/space-bg.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/activity/cp-space/assets/tab-active.png b/activity/cp-space/assets/tab-active.png new file mode 100644 index 0000000..dafad41 Binary files /dev/null and b/activity/cp-space/assets/tab-active.png differ diff --git a/activity/cp-space/index.html b/activity/cp-space/index.html new file mode 100644 index 0000000..762c60b --- /dev/null +++ b/activity/cp-space/index.html @@ -0,0 +1,77 @@ + + + + + + Best Friend + + + + + +
+
+ + +
+ + + +
+ +
+
+
+ + + + + + + + diff --git a/activity/cp-space/script.js b/activity/cp-space/script.js new file mode 100644 index 0000000..327e8d8 --- /dev/null +++ b/activity/cp-space/script.js @@ -0,0 +1,188 @@ +(function () { + var VALID_TABS = ['cp', 'brother', 'sister']; + var LEVEL_IMAGES = { + 1: './assets/level-1@4x.png', + 2: './assets/level-2@4x.png', + 3: './assets/level-3@4x.png', + 4: './assets/level-4@4x.png', + 5: './assets/level-5@4x.png', + }; + + var page = document.getElementById('page'); + var contentPanel = document.getElementById('contentPanel'); + var backButton = document.getElementById('backButton'); + var tabButtons = Array.prototype.slice.call( + document.querySelectorAll('.tab-button[data-tab]') + ); + + var state = { + activeTab: normalizeTab(readParam('tab')) || 'cp', + tabs: { + cp: [normalizeLevel(readParam('level')) || 1], + brother: [2, 2], + sister: [4, 5], + }, + }; + + function readParam(name) { + var params = new URLSearchParams(window.location.search || ''); + return params.get(name); + } + + function normalizeTab(value) { + var tab = String(value || '') + .trim() + .toLowerCase(); + return VALID_TABS.indexOf(tab) >= 0 ? tab : ''; + } + + function normalizeLevel(value) { + var level = Number(value); + return Number.isFinite(level) && level >= 1 && level <= 5 + ? Math.floor(level) + : 0; + } + + 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 setActiveTab(tab) { + var nextTab = normalizeTab(tab) || 'cp'; + state.activeTab = nextTab; + page.setAttribute('data-tab', nextTab); + tabButtons.forEach(function (button) { + button.classList.toggle( + 'is-active', + button.getAttribute('data-tab') === nextTab + ); + }); + render(); + } + + function cardHTML(level, index) { + var safeLevel = normalizeLevel(level) || 1; + var label = t('cpSpace.levelCard', 'Best friend level {level}').replace( + '{level}', + String(safeLevel) + ); + + return [ + '
', + '',
+            escapeHTML(label),
+            '', + '', + '
', + ].join(''); + } + + function render() { + var levels = state.tabs[state.activeTab] || []; + if (!levels.length) { + contentPanel.innerHTML = + '
' + + escapeHTML(t('cpSpace.empty', 'No relationship data yet.')) + + '
'; + return; + } + + contentPanel.innerHTML = + '
' + + levels.map(cardHTML).join('') + + '
'; + } + + function applyExternalData(payload) { + var data = payload || {}; + var nextTabs = {}; + VALID_TABS.forEach(function (tab) { + var raw = data[tab]; + if (!Array.isArray(raw)) return; + var normalized = raw + .map(function (item) { + return normalizeLevel( + typeof item === 'object' ? item.level : item + ); + }) + .filter(Boolean); + if (normalized.length) nextTabs[tab] = normalized; + }); + + // Flutter 可以在 WebView 注入 window.HyAppCPSpaceData 或派发 hyapp:cp-space-data; + // 页面只接受 1-5 的等级编号,避免 H5 自己猜测关系状态或渲染后端未确认的数据。 + Object.keys(nextTabs).forEach(function (tab) { + state.tabs[tab] = nextTabs[tab]; + }); + if (normalizeTab(data.activeTab)) state.activeTab = data.activeTab; + setActiveTab(state.activeTab); + } + + function handleCancel(index) { + var level = (state.tabs[state.activeTab] || [])[Number(index)] || 0; + if (window.HyAppBridge && window.HyAppBridge.post) { + window.HyAppBridge.post('cpSpaceCancel', { + relation_type: state.activeTab, + level: level, + }); + } + } + + function bindEvents() { + if (backButton) { + backButton.addEventListener('click', function () { + if (window.HyAppBridge && window.HyAppBridge.back) { + window.HyAppBridge.back(); + return; + } + window.history.back(); + }); + } + + tabButtons.forEach(function (button) { + button.addEventListener('click', function () { + setActiveTab(button.getAttribute('data-tab')); + }); + }); + + contentPanel.addEventListener('click', function (event) { + var target = event.target.closest('[data-cancel-index]'); + if (!target) return; + handleCancel(target.getAttribute('data-cancel-index')); + }); + + window.addEventListener('hyapp:cp-space-data', function (event) { + applyExternalData(event.detail || {}); + }); + + window.addEventListener('hyapp:i18n-ready', render); + } + + bindEvents(); + if (window.HyAppCPSpaceData) applyExternalData(window.HyAppCPSpaceData); + else setActiveTab(state.activeTab); + + if (window.HyAppBridge && window.HyAppBridge.ready) { + window.HyAppBridge.ready({ page: 'cp-space' }); + } +})(); diff --git a/activity/cp-space/style.css b/activity/cp-space/style.css new file mode 100644 index 0000000..381ad45 --- /dev/null +++ b/activity/cp-space/style.css @@ -0,0 +1,217 @@ +* { + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; +} + +:root { + --cp-space-bg: #2d165d; + --cp-space-panel: #4d236d; + --cp-space-muted: #8c76d2; + --cp-space-card-top: 125px; +} + +html, +body { + margin: 0; + width: 100%; + min-height: 100%; + overflow-x: hidden; + background: var(--cp-space-bg); + 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; +} + +.app-shell { + width: 100%; + max-width: 768px; + min-height: 100vh; + margin: 0 auto; + background: var(--cp-space-bg); +} + +.cp-space { + position: relative; + width: min(100vw, 768px); + min-height: 100vh; + margin: 0 auto; + overflow: hidden; + background: var(--cp-space-bg); +} + +.cp-space::before, +.cp-space::after { + position: absolute; + inset: 0; + pointer-events: none; + content: ''; +} + +.cp-space::before { + z-index: 0; + background: url('./assets/space-bg.svg') center top / 100% auto repeat-y; +} + +.cp-space::after { + display: none; +} + +.top-nav { + position: relative; + z-index: 2; + height: 44px; +} + +.back-button { + position: absolute; + left: 16px; + top: 10px; + width: 24px; + height: 24px; +} + +.back-button span { + position: absolute; + left: 8px; + top: 5px; + width: 10px; + height: 10px; + border-left: 2px solid #fff; + border-bottom: 2px solid #fff; + transform: rotate(45deg); +} + +.top-nav h1 { + position: absolute; + left: 50%; + top: 12px; + margin: 0; + color: #fff; + font-size: 18px; + font-weight: 500; + line-height: 20px; + letter-spacing: 0; + text-align: center; + transform: translateX(-50%); +} + +.tab-bar { + position: relative; + z-index: 2; + display: grid; + grid-template-columns: repeat(3, 1fr); + align-items: center; + width: calc(100% - 32px); + max-width: 343px; + height: 47px; + margin: 16px auto 0; + padding: 0; + border-radius: 27.5px; + background: var(--cp-space-panel); +} + +.tab-button { + position: relative; + height: 47px; + color: var(--cp-space-muted); + font-size: 18px; + font-weight: 500; + line-height: 18px; + letter-spacing: 0; + text-align: center; +} + +.tab-button span { + position: relative; + z-index: 1; +} + +.tab-button.is-active { + color: #fff; +} + +.tab-button.is-active::before { + position: absolute; + left: 50%; + top: 0; + width: min(130px, calc(100% + 16px)); + height: 47px; + background: url('./assets/tab-active.png') center / 100% 100% no-repeat; + content: ''; + transform: translateX(-50%); +} + +.content-panel { + position: relative; + z-index: 2; + width: 100%; + padding: 18px 10px 40px; +} + +.relation-list { + display: grid; + gap: 10px; + justify-items: center; + width: 100%; +} + +.relation-card { + position: relative; + width: min(355px, calc(100vw - 20px)); + border-radius: 10px; +} + +.relation-card img { + display: block; + width: 100%; + height: auto; + user-select: none; + -webkit-user-drag: none; +} + +.cancel-hotspot { + position: absolute; + left: 35%; + bottom: 10%; + width: 30%; + height: 18%; + border-radius: 999px; +} + +.empty-state { + width: min(355px, calc(100vw - 20px)); + margin: 0 auto; + padding: 56px 18px; + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 12px; + background: rgba(77, 35, 109, 0.68); + color: rgba(255, 255, 255, 0.72); + font-size: 14px; + line-height: 20px; + text-align: center; +} + +[dir='rtl'] .back-button span { + transform: rotate(225deg); +} + +@media (min-width: 769px) { + .cp-space { + width: 768px; + } + + .content-panel { + padding-right: 206px; + padding-left: 206px; + } +} diff --git a/activity/wheel/script.js b/activity/wheel/script.js index 480b775..8cd468f 100644 --- a/activity/wheel/script.js +++ b/activity/wheel/script.js @@ -39,6 +39,7 @@ stageClass: 'wheel-classic', lightPrefix: 'lucky_box_', minSpeed: 2000, + slotCount: 8, sequence: [1, 2, 3, 5, 8, 7, 6, 4, 1, 2, 3, 5, 8, 7, 6, 4, 1], }, luxury: { @@ -62,6 +63,7 @@ stageClass: 'wheel-advanced', minSpeed: 1000, hasMarqueeBox: true, + slotCount: 12, sequence: [1, 2, 3, 4, 6, 8, 12, 11, 10, 9, 7, 5, 1], }, }; @@ -332,6 +334,14 @@ : []; } + function visibleWheelRewards(data, config) { + var items = normalizeWheelRewards(data); + var slotCount = Number(config && config.slotCount); + // H5 格子数量是固定视觉契约:classic 只有 8 个外圈奖品位,center 是抽奖按钮。 + // 如果旧配置或缓存仍返回第 9 个奖品,前端不渲染多余格子,避免按钮被奖品占位或抽奖动画索引错位。 + return slotCount > 0 ? items.slice(0, slotCount) : items; + } + function normalizeRewardItem(item) { var target = normalizeImageObject( item, @@ -1000,7 +1010,7 @@ ]) !== false; // 后端配置来自资源组:金币只要求预览图字段为空也能展示金额,礼物/道具/装扮则统一读取 metadata 的预览图和动态图。 state.gridItems = this.enabled[config.key] - ? normalizeWheelRewards(data) + ? visibleWheelRewards(data, config) : []; }.bind(this) ) diff --git a/common/locales/ar.json b/common/locales/ar.json index b7cc8fd..d7b0be4 100644 --- a/common/locales/ar.json +++ b/common/locales/ar.json @@ -595,5 +595,17 @@ "agencyOpening.guildName": "اسم النقابة:", "agencyOpening.created": "تاريخ الإنشاء:", "agencyOpening.recordRevenue": "إيراد الافتتاح:", - "agencyOpening.recordReward": "المكافأة:" + "agencyOpening.recordReward": "المكافأة:", + "cpSpace.pageTitle": "أفضل صديق", + "cpSpace.pageLabel": "مساحة أفضل صديق", + "cpSpace.navLabel": "تنقل أفضل صديق", + "cpSpace.back": "رجوع", + "cpSpace.title": "أفضل صديق", + "cpSpace.tabsLabel": "تبويبات أفضل صديق", + "cpSpace.tabCp": "CP", + "cpSpace.tabBrother": "Brother", + "cpSpace.tabSister": "Sister", + "cpSpace.levelCard": "مستوى أفضل صديق {level}", + "cpSpace.cancel": "إلغاء", + "cpSpace.empty": "لا توجد بيانات علاقة بعد." } diff --git a/common/locales/en.json b/common/locales/en.json index 8ea5f17..629dd12 100644 --- a/common/locales/en.json +++ b/common/locales/en.json @@ -601,5 +601,17 @@ "agencyOpening.guildName": "Guild Name:", "agencyOpening.created": "Created:", "agencyOpening.recordRevenue": "Opening Revenue:", - "agencyOpening.recordReward": "Reward:" + "agencyOpening.recordReward": "Reward:", + "cpSpace.pageTitle": "Best Friend", + "cpSpace.pageLabel": "Best Friend space", + "cpSpace.navLabel": "Best Friend navigation", + "cpSpace.back": "Back", + "cpSpace.title": "Best Friend", + "cpSpace.tabsLabel": "Best Friend tabs", + "cpSpace.tabCp": "CP", + "cpSpace.tabBrother": "Brother", + "cpSpace.tabSister": "Sister", + "cpSpace.levelCard": "Best friend level {level}", + "cpSpace.cancel": "Cancel", + "cpSpace.empty": "No relationship data yet." } diff --git a/common/locales/es.json b/common/locales/es.json index 207416a..9053e2b 100644 --- a/common/locales/es.json +++ b/common/locales/es.json @@ -595,5 +595,17 @@ "agencyOpening.guildName": "Nombre del gremio:", "agencyOpening.created": "Creado:", "agencyOpening.recordRevenue": "Ingresos de apertura:", - "agencyOpening.recordReward": "Recompensa:" + "agencyOpening.recordReward": "Recompensa:", + "cpSpace.pageTitle": "Mejor amigo", + "cpSpace.pageLabel": "Espacio de mejor amigo", + "cpSpace.navLabel": "Navegación de mejor amigo", + "cpSpace.back": "Volver", + "cpSpace.title": "Mejor amigo", + "cpSpace.tabsLabel": "Pestañas de mejor amigo", + "cpSpace.tabCp": "CP", + "cpSpace.tabBrother": "Brother", + "cpSpace.tabSister": "Sister", + "cpSpace.levelCard": "Nivel de mejor amigo {level}", + "cpSpace.cancel": "Cancelar", + "cpSpace.empty": "Aún no hay datos de relación." } diff --git a/common/locales/id.json b/common/locales/id.json index 2b461f5..a2bc8d6 100644 --- a/common/locales/id.json +++ b/common/locales/id.json @@ -562,5 +562,17 @@ "agencyOpening.guildName": "Nama guild:", "agencyOpening.created": "Dibuat:", "agencyOpening.recordRevenue": "Pendapatan pembukaan:", - "agencyOpening.recordReward": "Hadiah:" + "agencyOpening.recordReward": "Hadiah:", + "cpSpace.pageTitle": "Sahabat Terbaik", + "cpSpace.pageLabel": "Ruang sahabat terbaik", + "cpSpace.navLabel": "Navigasi sahabat terbaik", + "cpSpace.back": "Kembali", + "cpSpace.title": "Sahabat Terbaik", + "cpSpace.tabsLabel": "Tab sahabat terbaik", + "cpSpace.tabCp": "CP", + "cpSpace.tabBrother": "Brother", + "cpSpace.tabSister": "Sister", + "cpSpace.levelCard": "Level sahabat terbaik {level}", + "cpSpace.cancel": "Batal", + "cpSpace.empty": "Belum ada data hubungan." } diff --git a/common/locales/tr.json b/common/locales/tr.json index 26809b1..2461773 100644 --- a/common/locales/tr.json +++ b/common/locales/tr.json @@ -562,5 +562,17 @@ "agencyOpening.guildName": "Lonca adı:", "agencyOpening.created": "Oluşturuldu:", "agencyOpening.recordRevenue": "Açılış geliri:", - "agencyOpening.recordReward": "Ödül:" + "agencyOpening.recordReward": "Ödül:", + "cpSpace.pageTitle": "En İyi Arkadaş", + "cpSpace.pageLabel": "En iyi arkadaş alanı", + "cpSpace.navLabel": "En iyi arkadaş gezinmesi", + "cpSpace.back": "Geri", + "cpSpace.title": "En İyi Arkadaş", + "cpSpace.tabsLabel": "En iyi arkadaş sekmeleri", + "cpSpace.tabCp": "CP", + "cpSpace.tabBrother": "Brother", + "cpSpace.tabSister": "Sister", + "cpSpace.levelCard": "En iyi arkadaş seviyesi {level}", + "cpSpace.cancel": "İptal", + "cpSpace.empty": "Henüz ilişki verisi yok." } diff --git a/common/locales/zh.json b/common/locales/zh.json index 1539a7a..3e903a5 100644 --- a/common/locales/zh.json +++ b/common/locales/zh.json @@ -568,5 +568,17 @@ "agencyOpening.guildName": "公会名称:", "agencyOpening.created": "创建时间:", "agencyOpening.recordRevenue": "开业流水:", - "agencyOpening.recordReward": "奖励:" + "agencyOpening.recordReward": "奖励:", + "cpSpace.pageTitle": "挚友空间", + "cpSpace.pageLabel": "挚友空间", + "cpSpace.navLabel": "挚友空间导航", + "cpSpace.back": "返回", + "cpSpace.title": "Best Friend", + "cpSpace.tabsLabel": "挚友空间标签", + "cpSpace.tabCp": "CP", + "cpSpace.tabBrother": "Brother", + "cpSpace.tabSister": "Sister", + "cpSpace.levelCard": "挚友等级 {level}", + "cpSpace.cancel": "取消", + "cpSpace.empty": "暂无关系数据" }