diff --git a/activity/cp/index.html b/activity/cp/index.html index e223447..3990d78 100644 --- a/activity/cp/index.html +++ b/activity/cp/index.html @@ -10,7 +10,7 @@ @@ -177,7 +177,7 @@ })(); - - + + diff --git a/activity/cp/script.js b/activity/cp/script.js index db09378..9dbda1d 100644 --- a/activity/cp/script.js +++ b/activity/cp/script.js @@ -10,6 +10,11 @@ var REWARD_PANEL_BOTTOM_PADDING = 80; var REWARD_STAGE_BOTTOM_PADDING = 133; var REWARD_MIN_PANEL_HEIGHT = 320; + var PLAZA_VISIBLE_ROWS = 4; + var PLAZA_ROW_HEIGHT = 199; + var PLAZA_ROW_GAP = 24; + var PLAZA_SECONDS_PER_ROW = 3.2; + var PLAZA_FEED_LIMIT = 20; var STORAGE_KEY = 'hy_cp_activity_end_at'; var stageHeights = { plaza: 4283, @@ -210,10 +215,14 @@ renderAvatar('avatar-left', row.leftAvatar), renderAvatar('avatar-right', row.rightAvatar), '', + '', escapeHTML(row.leftName), + '', '', '', + '', escapeHTML(row.rightName), + '', '', '', '', @@ -222,6 +231,40 @@ ].join(''); } + function renderPlazaCarousel(rows) { + var sourceRows = Array.isArray(rows) ? rows : []; + var shouldLoop = sourceRows.length > PLAZA_VISIBLE_ROWS; + var rowHTML = sourceRows.map(renderCPRow).join(''); + var loopDistance = + sourceRows.length * (PLAZA_ROW_HEIGHT + PLAZA_ROW_GAP); + var loopDuration = Math.max( + sourceRows.length * PLAZA_SECONDS_PER_ROW, + PLAZA_VISIBLE_ROWS * PLAZA_SECONDS_PER_ROW + ); + + // 一组数据末尾保留一个 row-gap,再紧接完全相同的第二组;轨道移动一整组高度后,第二组首行与初始首行位置一致,循环重置时不会闪白或跳帧。 + return [ + '', + ].join(''); + } + function renderPlaza() { var plazaRows = activityData.plazaRows || []; var previousRows = activityData.previousRows || []; @@ -229,12 +272,10 @@ '

', t( 'cp.plazaGiftTitle', - 'CP pairs with intimacy ≥ 5000 are shown here' + 'CP formation gifts worth more than 5,000 coins are shown here' ), '

', - '
', - plazaRows.map(renderCPRow).join(''), - '
', + renderPlazaCarousel(plazaRows), ].join(''); var panels = [renderPanel('plaza-panel-a', 1816, 1152, firstBody)]; if (previousRows.length) { @@ -463,21 +504,37 @@ }; } - function normalizePlazaEntry(entry) { - var intimacyValue = Number( - entry.intimacy_value || entry.intimacyValue || 0 + function normalizeFormationGiftEntry(entry) { + var giftCoinValue = Number( + (entry && + (entry.gift_coin_value || + entry.giftCoinValue || + entry.coin_spent || + entry.coinSpent)) || + 0 ); - var userA = normalizePairUser(entry.user_a); - var userB = normalizePairUser(entry.user_b); - if (intimacyValue < 5000 || !userA || !userB) return null; + var requester = normalizePairUser( + entry && (entry.requester || entry.user_a || entry.userA) + ); + var target = normalizePairUser( + entry && (entry.target || entry.user_b || entry.userB) + ); + // 业务定义是“超过 5000 金币”,不是大于等于;即使旧网关误返回 5000 或更低的记录,H5 也不能把它展示出来。 + if ( + !Number.isFinite(giftCoinValue) || + giftCoinValue <= 5000 || + !requester || + !target + ) + return null; return { type: 'score', - rawScore: intimacyValue, - score: formatScore(intimacyValue), - leftName: userA.name, - rightName: userB.name, - leftAvatar: userA.avatar, - rightAvatar: userB.avatar, + rawGiftCoinValue: giftCoinValue, + score: formatScore(giftCoinValue), + leftName: requester.name, + rightName: target.name, + leftAvatar: requester.avatar, + rightAvatar: target.avatar, }; } @@ -549,18 +606,14 @@ } } - function applyPlazaLeaderboard(data) { + function applyPlazaFormationGiftFeed(data) { var items = data && Array.isArray(data.items) ? data.items : []; - // CP 广场只展示真实接口里亲密值达到门槛的 CP;接口为空或低于门槛时保持空列表,不再用 mock 用户兜底。 + // feed 由后端按 CP 成立时间倒序返回;H5 只做口径校验并保留返回顺序,不再按金币额排成另一个榜单。 activityData.plazaRows = items - .map(normalizePlazaEntry) + .map(normalizeFormationGiftEntry) .filter(function (item) { return Boolean(item); - }) - .sort(function (left, right) { - return right.rawScore - left.rawScore; - }) - .slice(0, 4); + }); } function loadActivityStatus() { @@ -575,23 +628,19 @@ .catch(function () {}); } - function loadPlazaLeaderboard() { + function loadPlazaFormationGiftFeed() { if ( !window.HyAppAPI || !window.HyAppAPI.cpSpace || - !window.HyAppAPI.cpSpace.intimacyLeaderboard + !window.HyAppAPI.cpSpace.formationGiftFeed ) { return; } - // 后端亲密榜已按亲密值降序聚合;H5 仍二次过滤 5000 门槛并限制 4 个,防止旧数据或异常分页把低亲密 CP 展示出来。 + // 20 条可让四行视窗持续轮播,同时限制头像 DOM 和图片解码数量;gateway/user-service 还会再次夹紧上限,避免参数被篡改后无界拉取。 window.HyAppAPI.cpSpace - .intimacyLeaderboard({ - relation_type: 'cp', - page: 1, - page_size: 4, - }) + .formationGiftFeed({ limit: PLAZA_FEED_LIMIT }) .then(function (data) { - applyPlazaLeaderboard(data); + applyPlazaFormationGiftFeed(data); render(); }) .catch(function () {}); @@ -668,7 +717,7 @@ countdownEndAt = resolveFallbackEndAt(); bindTabs(); render(); - loadPlazaLeaderboard(); + loadPlazaFormationGiftFeed(); loadActivityStatus(); countdownTimer = window.setInterval(updateCountdown, 1000); window.addEventListener('resize', updateStageHeight); diff --git a/activity/cp/style.css b/activity/cp/style.css index 2e35d7c..12249ce 100644 --- a/activity/cp/style.css +++ b/activity/cp/style.css @@ -372,6 +372,38 @@ button { top: 170px; } +.plaza-carousel-viewport { + position: absolute; + left: 60px; + top: 170px; + width: 840px; + height: 868px; + overflow: hidden; +} + +.plaza-carousel-track { + width: 840px; + will-change: auto; +} + +.plaza-carousel-track.is-looping { + animation: plaza-carousel-scroll var(--plaza-loop-duration) linear infinite; + will-change: transform; +} + +.plaza-carousel-set { + display: grid; + row-gap: 24px; + width: 840px; + padding-bottom: 24px; +} + +@keyframes plaza-carousel-scroll { + to { + transform: translateY(calc(-1 * var(--plaza-loop-distance))); + } +} + .panel-title { position: absolute; left: 55px; @@ -454,6 +486,7 @@ button { text-align: center; white-space: nowrap; text-overflow: ellipsis; + unicode-bidi: plaintext; } .cp-row .name-left { @@ -492,15 +525,17 @@ button { .score-value { position: absolute; - left: 359px; + left: 308px; top: 71px; - width: 120px; + width: 222px; height: 36px; color: #fff7f7; font-size: 30px; font-weight: 900; line-height: 36px; text-align: center; + white-space: nowrap; + font-variant-numeric: tabular-nums; text-shadow: 0 2px 0 #a51f32, 0 0 8px rgba(255, 255, 255, 0.32); @@ -729,3 +764,11 @@ html[dir='rtl'] .reward-note { html[dir='rtl'] .language-switcher { direction: ltr; } + +@media (prefers-reduced-motion: reduce) { + .plaza-carousel-track.is-looping { + animation: none; + transform: none; + will-change: auto; + } +} diff --git a/common/api.js b/common/api.js index aa905f0..7e92451 100644 --- a/common/api.js +++ b/common/api.js @@ -1892,6 +1892,12 @@ var default_api = 'https://api.global-interaction.com/'; query: params || {}, }); }, + formationGiftFeed: function (params) { + return request('/api/v1/cp/formation-gift-feed', { + method: 'GET', + query: params || {}, + }); + }, intimacyLeaderboard: function (params) { return request('/api/v1/cp/intimacy-leaderboard', { method: 'GET', diff --git a/common/locales/ar.json b/common/locales/ar.json index 6582f9e..db08d05 100644 --- a/common/locales/ar.json +++ b/common/locales/ar.json @@ -520,8 +520,8 @@ "cp.tabPlaza": "ساحة CP", "cp.tabRank": "ترتيب CP", "cp.tabRewards": "المكافآت", - "cp.plazaGiftTitle": "سيتم عرض أزواج CP التي لديها قيمة ألفة >= 5000 هنا", - "cp.plazaGiftSubtitle": "(عرض آخر 10 سجلات مرتبة تصاعديا.)", + "cp.plazaGiftTitle": "تظهر هنا هدايا تكوين CP التي تزيد قيمتها عن 5,000 عملة", + "cp.plazaGiftSubtitle": "تُعرض أحدث أزواج CP المؤهلة هنا باستمرار.", "cp.previousNo1Title": "أفضل 3 في الفترة السابقة", "cp.rewardMedal7": "وسام*7 أيام", "cp.rewardMedalNote": "(وسام دائم بعد الحصول عليه 3 مرات)", diff --git a/common/locales/en.json b/common/locales/en.json index aa30016..5666231 100644 --- a/common/locales/en.json +++ b/common/locales/en.json @@ -526,8 +526,8 @@ "cp.tabPlaza": "CP Plaza", "cp.tabRank": "CP Rank", "cp.tabRewards": "Rewards", - "cp.plazaGiftTitle": "CP pairs with intimacy >= 5000 are shown here", - "cp.plazaGiftSubtitle": "(Display the last 10 records, sorted in ascending order.)", + "cp.plazaGiftTitle": "CP formation gifts worth more than 5,000 coins are shown here", + "cp.plazaGiftSubtitle": "The latest qualifying CP pairs scroll here continuously.", "cp.previousNo1Title": "Previous Top 3", "cp.rewardMedal7": "Medal*7 days", "cp.rewardMedalNote": "(Permanent medal after getting 3 times)", diff --git a/common/locales/es.json b/common/locales/es.json index faf6f21..231d40b 100644 --- a/common/locales/es.json +++ b/common/locales/es.json @@ -520,8 +520,8 @@ "cp.tabPlaza": "Plaza CP", "cp.tabRank": "Ranking CP", "cp.tabRewards": "Recompensas", - "cp.plazaGiftTitle": "Los CP con intimidad >= 5000 se mostrarán aquí", - "cp.plazaGiftSubtitle": "(Muestra los últimos 10 registros en orden ascendente.)", + "cp.plazaGiftTitle": "Aquí se muestran los regalos para formar CP que valen más de 5.000 monedas", + "cp.plazaGiftSubtitle": "Las parejas CP elegibles más recientes se desplazan aquí continuamente.", "cp.previousNo1Title": "Top 3 anterior", "cp.rewardMedal7": "Medalla*7 días", "cp.rewardMedalNote": "(Medalla permanente tras obtenerla 3 veces)", diff --git a/common/locales/id.json b/common/locales/id.json index 398e984..a6dcc5a 100644 --- a/common/locales/id.json +++ b/common/locales/id.json @@ -487,8 +487,8 @@ "cp.tabPlaza": "CP Plaza", "cp.tabRank": "Peringkat CP", "cp.tabRewards": "Hadiah", - "cp.plazaGiftTitle": "CP dengan intimacy >= 5000 akan ditampilkan di sini", - "cp.plazaGiftSubtitle": "(Menampilkan 10 catatan terbaru, diurutkan naik.)", + "cp.plazaGiftTitle": "Hadiah pembentukan CP bernilai lebih dari 5.000 koin ditampilkan di sini", + "cp.plazaGiftSubtitle": "Pasangan CP terbaru yang memenuhi syarat bergulir terus di sini.", "cp.previousNo1Title": "Top 3 periode sebelumnya", "cp.rewardMedal7": "Medali*7 hari", "cp.rewardMedalNote": "(Medali permanen setelah mendapatkannya 3 kali)", diff --git a/common/locales/tr.json b/common/locales/tr.json index 98e0319..b54e18b 100644 --- a/common/locales/tr.json +++ b/common/locales/tr.json @@ -487,8 +487,8 @@ "cp.tabPlaza": "CP Plaza", "cp.tabRank": "CP Sıralaması", "cp.tabRewards": "Ödüller", - "cp.plazaGiftTitle": "Samimiyet değeri >= 5000 olan CP çiftleri burada görünür", - "cp.plazaGiftSubtitle": "(Son 10 kayıt artan sırayla gösterilir.)", + "cp.plazaGiftTitle": "5.000 coinden daha değerli CP kurma hediyeleri burada gösterilir", + "cp.plazaGiftSubtitle": "Koşulları karşılayan en yeni CP çiftleri burada sürekli kayar.", "cp.previousNo1Title": "Önceki Top 3", "cp.rewardMedal7": "Madalya*7 gün", "cp.rewardMedalNote": "(3 kez alındıktan sonra kalıcı madalya)", diff --git a/common/locales/zh.json b/common/locales/zh.json index 02c44df..15a11bc 100644 --- a/common/locales/zh.json +++ b/common/locales/zh.json @@ -493,8 +493,8 @@ "cp.tabPlaza": "CP 广场", "cp.tabRank": "CP 排行", "cp.tabRewards": "奖励", - "cp.plazaGiftTitle": "CP 之间亲密值 >= 5000 的 CP 会展示在这里", - "cp.plazaGiftSubtitle": "(展示最近 10 条记录,按时间升序排列。)", + "cp.plazaGiftTitle": "组 CP 时赠送价值超过 5000 金币的礼物会展示在这里", + "cp.plazaGiftSubtitle": "最近符合条件的 CP 组合会在这里持续轮播。", "cp.previousNo1Title": "上期前三名", "cp.rewardMedal7": "勋章*7天", "cp.rewardMedalNote": "(获得 3 次后永久)",