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 [
+ '',
+ '
',
+ '',
+ '',
+ ].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": "暂无关系数据"
}