diff --git a/activity/tasks/assets/back.png b/activity/tasks/assets/back.png
new file mode 100644
index 0000000..d2935f3
--- /dev/null
+++ b/activity/tasks/assets/back.png
@@ -0,0 +1,9 @@
+
diff --git a/activity/tasks/assets/button-glow.png b/activity/tasks/assets/button-glow.png
new file mode 100644
index 0000000..4591774
Binary files /dev/null and b/activity/tasks/assets/button-glow.png differ
diff --git a/activity/tasks/assets/coin.png b/activity/tasks/assets/coin.png
new file mode 100644
index 0000000..f61a28e
Binary files /dev/null and b/activity/tasks/assets/coin.png differ
diff --git a/activity/tasks/assets/figma-game-icon.png b/activity/tasks/assets/figma-game-icon.png
new file mode 100644
index 0000000..34568c3
Binary files /dev/null and b/activity/tasks/assets/figma-game-icon.png differ
diff --git a/activity/tasks/assets/figma-task-full.png b/activity/tasks/assets/figma-task-full.png
new file mode 100644
index 0000000..f166f06
Binary files /dev/null and b/activity/tasks/assets/figma-task-full.png differ
diff --git a/activity/tasks/assets/task-bg.png b/activity/tasks/assets/task-bg.png
new file mode 100644
index 0000000..96c9acb
Binary files /dev/null and b/activity/tasks/assets/task-bg.png differ
diff --git a/activity/tasks/assets/task-h5-preview-wide.png b/activity/tasks/assets/task-h5-preview-wide.png
new file mode 100644
index 0000000..c0ebc6e
Binary files /dev/null and b/activity/tasks/assets/task-h5-preview-wide.png differ
diff --git a/activity/tasks/assets/task-h5-preview.png b/activity/tasks/assets/task-h5-preview.png
new file mode 100644
index 0000000..0537359
Binary files /dev/null and b/activity/tasks/assets/task-h5-preview.png differ
diff --git a/activity/tasks/index.html b/activity/tasks/index.html
new file mode 100644
index 0000000..e22f520
--- /dev/null
+++ b/activity/tasks/index.html
@@ -0,0 +1,84 @@
+
+
+
+
+
+ Task
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Exclusive For Newcomers
+
+
+ Limited to one time
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/activity/tasks/script.js b/activity/tasks/script.js
new file mode 100644
index 0000000..0aa4611
--- /dev/null
+++ b/activity/tasks/script.js
@@ -0,0 +1,594 @@
+(function () {
+ var viewport = document.getElementById('appViewport');
+ var page = document.getElementById('page');
+ var dailyRoot = document.getElementById('dailyTasks');
+ var exclusiveRoot = document.getElementById('exclusiveTasks');
+ var stateRoot = document.getElementById('taskState');
+ var backButton = document.getElementById('backButton');
+ var COMMAND_PREFIX = 'task_claim_command:';
+ var COIN_IMAGE = './assets/coin.png';
+ var GAME_IMAGE = './assets/figma-game-icon.png';
+
+ var state = {
+ loading: true,
+ error: '',
+ sections: [],
+ claiming: {},
+ };
+
+ var mockData = {
+ server_time_ms: Date.now(),
+ next_refresh_at_ms: Date.now() + 6 * 60 * 60 * 1000,
+ sections: [
+ {
+ section: 'daily',
+ items: [
+ mockTask(
+ 'daily-mic',
+ 'daily',
+ 'Use the microphone for 1 min',
+ 'mic_online_minute',
+ 0,
+ 1000000,
+ 1100,
+ 'in_progress',
+ false,
+ 'mic'
+ ),
+ mockTask(
+ 'daily-game',
+ 'daily',
+ 'Use the microphone for 1 min',
+ 'game_spend_coin',
+ 1000000,
+ 1000000,
+ 1100,
+ 'completed',
+ true,
+ 'gamepad'
+ ),
+ mockTask(
+ 'daily-gift',
+ 'daily',
+ 'Use the microphone for 1 min',
+ 'gift_spend_coin',
+ 1000000,
+ 1000000,
+ 1100,
+ 'claimed',
+ false,
+ 'gift'
+ ),
+ ],
+ },
+ {
+ section: 'exclusive',
+ items: [
+ mockTask(
+ 'exclusive-game-preview',
+ 'exclusive',
+ 'Use the microphone for 1 min',
+ 'game_spend_coin',
+ 0,
+ 1000000,
+ 1100,
+ 'in_progress',
+ false,
+ 'game_preview'
+ ),
+ mockTask(
+ 'exclusive-game',
+ 'exclusive',
+ 'Use the microphone for 1 min',
+ 'game_spend_coin',
+ 1000000,
+ 1000000,
+ 1100,
+ 'completed',
+ true,
+ 'gamepad'
+ ),
+ mockTask(
+ 'exclusive-gift',
+ 'exclusive',
+ 'Use the microphone for 1 min',
+ 'gift_spend_coin',
+ 1000000,
+ 1000000,
+ 1100,
+ 'claimed',
+ false,
+ 'gift'
+ ),
+ ],
+ },
+ ],
+ };
+
+ function t(key, fallback) {
+ if (window.HyAppI18n && window.HyAppI18n.t) {
+ return window.HyAppI18n.t(key, fallback);
+ }
+ return fallback || key;
+ }
+
+ function mockTask(
+ id,
+ type,
+ title,
+ metric,
+ progress,
+ target,
+ reward,
+ status,
+ claimable,
+ iconKey
+ ) {
+ return {
+ task_id: id,
+ task_type: type,
+ task_day:
+ type === 'daily'
+ ? new Date().toISOString().slice(0, 10)
+ : 'lifetime',
+ title: title,
+ metric_type: metric,
+ progress_value: progress,
+ target_value: target,
+ reward_coin_amount: reward,
+ status: status,
+ claimable: claimable,
+ icon_key: iconKey,
+ icon_url: iconKey === 'game_preview' ? GAME_IMAGE : '',
+ action_type:
+ iconKey === 'gift'
+ ? 'gift_panel'
+ : iconKey === 'gamepad'
+ ? 'room_random_game'
+ : 'wallet',
+ action_param: iconKey === 'gamepad' ? 'dice' : '',
+ action_payload: iconKey === 'gamepad' ? { game_id: 'dice' } : {},
+ };
+ }
+
+ function isMockMode() {
+ var params = new URLSearchParams(window.location.search || '');
+ return params.get('mock') === '1' || params.get('mock') === 'true';
+ }
+
+ function escapeHTML(value) {
+ return String(value == null ? '' : value)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ }
+
+ function escapeAttr(value) {
+ return escapeHTML(value).replace(/`/g, '`');
+ }
+
+ function clampProgress(item) {
+ var target = Math.max(
+ 0,
+ Number(item.target_value || item.targetValue || 0)
+ );
+ var progress = Math.max(
+ 0,
+ Number(item.progress_value || item.progressValue || 0)
+ );
+ return {
+ target: target,
+ progress: target > 0 ? Math.min(progress, target) : progress,
+ };
+ }
+
+ function formatProgress(item) {
+ var values = clampProgress(item);
+ return String(values.progress) + '/' + String(values.target);
+ }
+
+ function formatReward(value) {
+ var amount = Number(value || 0);
+ if (!Number.isFinite(amount)) return '0';
+ if (amount >= 1000000) return trimNumber(amount / 1000000) + 'M';
+ if (amount >= 1000) return trimNumber(amount / 1000) + 'k';
+ return String(amount);
+ }
+
+ function trimNumber(value) {
+ var rounded = Math.round(value * 10) / 10;
+ return String(rounded).replace(/\.0$/, '');
+ }
+
+ function claimKey(item) {
+ return [
+ item.task_id || item.taskId,
+ item.task_type || item.taskType,
+ item.task_day || item.taskDay,
+ ].join('|');
+ }
+
+ function commandStorageKey(item) {
+ return COMMAND_PREFIX + claimKey(item);
+ }
+
+ function commandID(item) {
+ var storageKey = commandStorageKey(item);
+ var cached = window.sessionStorage.getItem(storageKey);
+ if (cached) return cached;
+ // 领取命令以任务主键和页面时间组成,保存在 sessionStorage;如果请求超时但服务端已入账,用户重试会继续用同一 command_id 命中后端幂等。
+ var next =
+ 'h5-task-claim-' +
+ Date.now() +
+ '-' +
+ Math.random().toString(16).slice(2) +
+ '-' +
+ String(item.task_id || item.taskId || '');
+ window.sessionStorage.setItem(storageKey, next);
+ return next;
+ }
+
+ function clearCommandID(item) {
+ window.sessionStorage.removeItem(commandStorageKey(item));
+ }
+
+ function normalizeSections(payload) {
+ var sections =
+ payload && Array.isArray(payload.sections) ? payload.sections : [];
+ var byName = sections.reduce(function (map, section) {
+ map[String(section.section || '')] = Array.isArray(section.items)
+ ? section.items
+ : [];
+ return map;
+ }, {});
+ return [
+ { section: 'daily', items: byName.daily || [] },
+ { section: 'exclusive', items: byName.exclusive || [] },
+ ];
+ }
+
+ function iconKind(item) {
+ var key = String(item.icon_key || item.iconKey || '').toLowerCase();
+ var metric = String(
+ item.metric_type || item.metricType || ''
+ ).toLowerCase();
+ if (key.indexOf('mic') >= 0 || metric.indexOf('mic') >= 0) return 'mic';
+ if (key.indexOf('gift') >= 0 || metric.indexOf('gift') >= 0)
+ return 'gift';
+ if (key.indexOf('game') >= 0 || metric.indexOf('game') >= 0)
+ return 'gamepad';
+ if (key.indexOf('cp') >= 0 || metric.indexOf('cp') >= 0) return 'cp';
+ if (key.indexOf('mood') >= 0 || metric.indexOf('mood') >= 0)
+ return 'mood';
+ return 'coin';
+ }
+
+ function iconHTML(item) {
+ var iconURL = item.icon_url || item.iconUrl || '';
+ if (iconURL) {
+ // 后台 icon_url 是可配置资源,可能因为地址过期、跨域或资源未上传而加载失败;失败时用内置图标兜底,不能让任务列表留下空方块。
+ return [
+ '
',
+ ].join('');
+ }
+ return (
+ '' + svgForIcon(iconKind(item)) + ''
+ );
+ }
+
+ function svgForIcon(kind) {
+ if (kind === 'mic') {
+ return '';
+ }
+ if (kind === 'gift') {
+ return '';
+ }
+ if (kind === 'cp') {
+ return '';
+ }
+ if (kind === 'mood') {
+ return '';
+ }
+ if (kind === 'coin') {
+ return '';
+ }
+ return '';
+ }
+
+ function actionState(item) {
+ if (item.status === 'claimed') {
+ return {
+ label: t('tasks.claimed', 'Claimed'),
+ className: 'is-claimed',
+ disabled: true,
+ };
+ }
+ if (item.claimable) {
+ return {
+ label: t('tasks.claim', 'Claim'),
+ className: 'is-claimable',
+ disabled: false,
+ };
+ }
+ return {
+ label: t('tasks.go', 'Go'),
+ className: 'is-go',
+ disabled: false,
+ };
+ }
+
+ function renderSection(root, items) {
+ if (!items.length) {
+ root.innerHTML =
+ '' +
+ escapeHTML(t('tasks.empty', 'No tasks yet.')) +
+ '
';
+ return;
+ }
+ root.innerHTML = items.map(renderTaskRow).join('');
+ }
+
+ function renderTaskRow(item) {
+ var action = actionState(item);
+ var key = claimKey(item);
+ var loading = Boolean(state.claiming[key]);
+ var label = loading ? t('tasks.claiming', 'Claiming...') : action.label;
+ return [
+ '',
+ iconHTML(item),
+ '',
+ '
',
+ escapeHTML(
+ item.title || item.description || t('tasks.untitled', 'Task')
+ ),
+ '',
+ '
',
+ escapeHTML(formatProgress(item)),
+ '',
+ '
',
+ escapeHTML(
+ formatReward(item.reward_coin_amount || item.rewardCoinAmount)
+ ),
+ '',
+ '
',
+ '',
+ '',
+ ].join('');
+ }
+
+ function render() {
+ var sections = normalizeSections({ sections: state.sections });
+ var daily = sections[0].items;
+ var exclusive = sections[1].items;
+ renderSection(dailyRoot, daily);
+ renderSection(exclusiveRoot, exclusive);
+ bindIconFallbacks();
+ renderState();
+ updateScale();
+ }
+
+ function bindIconFallbacks() {
+ Array.prototype.slice
+ .call(document.querySelectorAll('[data-icon-fallback] img'))
+ .forEach(function (image) {
+ image.addEventListener(
+ 'error',
+ function () {
+ var wrapper = image.parentElement;
+ if (!wrapper) return;
+ wrapper.classList.remove('is-image');
+ wrapper.innerHTML = svgForIcon(
+ wrapper.getAttribute('data-icon-fallback') || 'coin'
+ );
+ },
+ { once: true }
+ );
+ });
+ }
+
+ function renderState() {
+ if (state.loading) {
+ stateRoot.hidden = false;
+ stateRoot.textContent = t('tasks.loading', 'Loading...');
+ return;
+ }
+ if (state.error) {
+ stateRoot.hidden = false;
+ stateRoot.textContent = state.error;
+ return;
+ }
+ stateRoot.hidden = true;
+ stateRoot.textContent = '';
+ }
+
+ function loadTasks() {
+ state.loading = true;
+ state.error = '';
+ renderState();
+ if (isMockMode()) {
+ applyPayload(mockData);
+ return;
+ }
+ if (!window.HyAppAPI || !window.HyAppAPI.task) {
+ state.loading = false;
+ state.error = t('tasks.apiUnavailable', 'Task API unavailable.');
+ render();
+ return;
+ }
+ window.HyAppAPI.task
+ .tabs()
+ .then(applyPayload)
+ .catch(function (error) {
+ state.loading = false;
+ state.error =
+ error.message || t('tasks.loadFailed', 'Load failed.');
+ state.sections = [];
+ render();
+ });
+ }
+
+ function applyPayload(payload) {
+ var sections = normalizeSections(payload);
+ state.sections = sections.map(function (section) {
+ return {
+ section: section.section,
+ items: section.items,
+ };
+ });
+ state.loading = false;
+ state.error = '';
+ render();
+ }
+
+ function findTask(key) {
+ var sections = normalizeSections({ sections: state.sections });
+ for (
+ var sectionIndex = 0;
+ sectionIndex < sections.length;
+ sectionIndex += 1
+ ) {
+ var items = sections[sectionIndex].items;
+ for (var itemIndex = 0; itemIndex < items.length; itemIndex += 1) {
+ if (claimKey(items[itemIndex]) === key) return items[itemIndex];
+ }
+ }
+ return null;
+ }
+
+ function claimTask(item) {
+ var key = claimKey(item);
+ state.claiming[key] = true;
+ render();
+ if (isMockMode()) {
+ // mock 只服务本地视觉和交互验收,不触发真实发奖;真实页面仍必须通过后端 claim 接口刷新钱包和任务状态。
+ item.status = 'claimed';
+ item.claimable = false;
+ state.claiming[key] = false;
+ showToast(t('tasks.claimSuccess', 'Reward claimed.'));
+ render();
+ return;
+ }
+ // H5 只提交任务列表返回的 task_day/task_type/task_id,避免页面自行推算每日刷新周期;服务端负责判断当天是否仍可领取。
+ window.HyAppAPI.task
+ .claim({
+ task_id: item.task_id || item.taskId,
+ task_type: item.task_type || item.taskType,
+ task_day: item.task_day || item.taskDay,
+ command_id: commandID(item),
+ })
+ .then(function () {
+ clearCommandID(item);
+ showToast(t('tasks.claimSuccess', 'Reward claimed.'));
+ return window.HyAppAPI.task.tabs();
+ })
+ .then(applyPayload)
+ .catch(function (error) {
+ state.claiming[key] = false;
+ showToast(
+ error.message || t('tasks.claimFailed', 'Claim failed.')
+ );
+ render();
+ });
+ }
+
+ function goTask(item) {
+ var actionType = item.action_type || item.actionType || 'none';
+ if (!actionType || actionType === 'none') {
+ showToast(t('tasks.noAction', 'No action configured.'));
+ return;
+ }
+ var payload = {
+ task_id: item.task_id || item.taskId || '',
+ task_type: item.task_type || item.taskType || '',
+ task_day: item.task_day || item.taskDay || '',
+ action_type: actionType,
+ action_param: item.action_param || item.actionParam || '',
+ action_payload: item.action_payload || item.actionPayload || {},
+ };
+ // H5 不猜测原生房间/钱包/礼物页面的具体路由,只把后台配置的跳转元数据原样交给 App;App 侧可按 action_type 做统一分发。
+ if (window.HyAppBridge && window.HyAppBridge.openTaskAction) {
+ window.HyAppBridge.openTaskAction(payload);
+ return;
+ }
+ if (window.HyAppBridge && window.HyAppBridge.post) {
+ window.HyAppBridge.post('taskAction', payload);
+ return;
+ }
+ window.dispatchEvent(
+ new CustomEvent('hyapp:task-action', {
+ detail: payload,
+ })
+ );
+ }
+
+ function showToast(message) {
+ if (window.HyAppToast && window.HyAppToast.show) {
+ window.HyAppToast.show(message);
+ return;
+ }
+ window.dispatchEvent(
+ new CustomEvent('hyapp:toast', {
+ detail: { message: message },
+ })
+ );
+ }
+
+ function onTaskAction(event) {
+ var button = event.target.closest('[data-task-action]');
+ if (!button) return;
+ var item = findTask(button.getAttribute('data-task-action'));
+ if (!item) return;
+ if (item.claimable) {
+ claimTask(item);
+ return;
+ }
+ goTask(item);
+ }
+
+ function updateScale() {
+ document.documentElement.style.setProperty('--app-scale', '1');
+ if (!viewport || !page) return;
+ var height = Math.max(page.scrollHeight, window.innerHeight || 0);
+ viewport.style.minHeight = height + 'px';
+ document.body.style.minHeight = height + 'px';
+ }
+
+ function bindEvents() {
+ page.addEventListener('click', onTaskAction);
+ window.addEventListener('resize', updateScale);
+ if (backButton) {
+ backButton.addEventListener('click', function () {
+ if (window.HyAppBridge && window.HyAppBridge.back) {
+ window.HyAppBridge.back();
+ return;
+ }
+ window.history.back();
+ });
+ }
+ }
+
+ bindEvents();
+ loadTasks();
+ if (window.HyAppBridge && window.HyAppBridge.ready) {
+ window.HyAppBridge.ready({ page: 'tasks' });
+ }
+})();
diff --git a/activity/tasks/style.css b/activity/tasks/style.css
new file mode 100644
index 0000000..a0a8d99
--- /dev/null
+++ b/activity/tasks/style.css
@@ -0,0 +1,325 @@
+* {
+ box-sizing: border-box;
+ -webkit-tap-highlight-color: transparent;
+}
+
+:root {
+ --app-scale: 1;
+ --task-width: 100%;
+ --task-bg: #130a2b;
+ --task-white: #ffffff;
+ --task-muted: rgba(255, 255, 255, 0.62);
+ --task-line: rgba(255, 255, 255, 0.16);
+ --task-purple: rgba(217, 0, 255, 0.32);
+ --task-gold: #ffd155;
+ --task-gold-border: #fff09b;
+ --task-red-shadow: #be1e21;
+}
+
+html,
+body {
+ margin: 0;
+ width: 100%;
+ min-height: 100%;
+ overflow-x: hidden;
+ background: var(--task-bg);
+ color: var(--task-white);
+ font-family:
+ 'Source Han Sans 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: var(--task-width);
+ min-height: 100vh;
+ margin: 0;
+}
+
+.task-page {
+ position: relative;
+ width: 100%;
+ min-height: 100vh;
+ overflow-x: hidden;
+ padding: 44px 0 36px;
+ background: var(--task-bg);
+}
+
+.task-page::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+ pointer-events: none;
+ background:
+ linear-gradient(180deg, rgba(19, 10, 43, 0.08), rgba(19, 10, 43, 0.12)),
+ url('./assets/task-bg.png') center top / 100% 100% no-repeat;
+}
+
+.task-nav {
+ position: relative;
+ z-index: 2;
+ width: 100%;
+ height: 44px;
+}
+
+.task-nav h1 {
+ position: absolute;
+ left: 50%;
+ top: 13px;
+ width: 180px;
+ margin: 0 0 0 -90px;
+ color: var(--task-white);
+ font-size: 18px;
+ font-weight: 500;
+ line-height: 18px;
+ text-align: center;
+ text-transform: capitalize;
+}
+
+.back-button {
+ position: absolute;
+ left: 16px;
+ top: 10px;
+ width: 24px;
+ height: 24px;
+}
+
+.back-button span {
+ position: absolute;
+ left: 7px;
+ top: 4px;
+ width: 14px;
+ height: 14px;
+ border-left: 2px solid var(--task-white);
+ border-bottom: 2px solid var(--task-white);
+ transform: rotate(45deg);
+}
+
+.task-section {
+ position: relative;
+ z-index: 1;
+ margin-top: 16px;
+}
+
+.task-section-exclusive {
+ margin-top: 18px;
+}
+
+.section-head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
+ min-height: 20px;
+ padding: 0 16px;
+}
+
+.section-head h2 {
+ margin: 0;
+ color: var(--task-white);
+ font-size: 16px;
+ font-weight: 800;
+ line-height: 16px;
+ text-transform: capitalize;
+ text-shadow:
+ 0 2px 0 var(--task-red-shadow),
+ 0 0 4px rgba(244, 203, 255, 0.55);
+}
+
+.section-head p {
+ margin: 2px 0 0;
+ color: var(--task-white);
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 12px;
+ text-align: right;
+ white-space: nowrap;
+}
+
+.task-list {
+ margin-top: 12px;
+}
+
+.task-row {
+ position: relative;
+ display: grid;
+ grid-template-columns: 42px minmax(0, 1fr) 52px;
+ column-gap: 9px;
+ align-items: center;
+ min-height: 65px;
+ padding: 0 16px;
+}
+
+.task-row::after {
+ content: '';
+ position: absolute;
+ left: 12px;
+ right: 12px;
+ bottom: 0;
+ height: 1px;
+ background: linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0),
+ var(--task-line) 50%,
+ rgba(255, 255, 255, 0)
+ );
+}
+
+.task-row:last-child::after {
+ opacity: 0.55;
+}
+
+.task-icon {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 42px;
+ height: 42px;
+ overflow: hidden;
+ border-radius: 50%;
+ background: var(--task-purple);
+}
+
+.task-icon img,
+.task-icon svg {
+ display: block;
+ width: 29px;
+ height: 29px;
+}
+
+.task-icon.is-image {
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.1);
+}
+
+.task-icon.is-image img {
+ width: 38px;
+ height: 38px;
+ border-radius: 8px;
+ object-fit: cover;
+}
+
+.task-main {
+ min-width: 0;
+ padding-top: 1px;
+}
+
+.task-title {
+ display: block;
+ max-width: 205px;
+ overflow: hidden;
+ color: var(--task-white);
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 14px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.task-progress {
+ display: block;
+ margin-top: 2px;
+ color: var(--task-muted);
+ font-size: 10px;
+ font-weight: 400;
+ line-height: 10px;
+}
+
+.task-reward {
+ display: inline-flex;
+ align-items: center;
+ gap: 2px;
+ margin-top: 7px;
+ color: var(--task-gold);
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 12px;
+}
+
+.task-reward img {
+ width: 12px;
+ height: 12px;
+}
+
+.task-action {
+ position: relative;
+ width: 52px;
+ height: 20px;
+ border: 1px solid var(--task-gold-border);
+ background:
+ linear-gradient(
+ 175deg,
+ rgba(70, 42, 120, 0.72) 0%,
+ rgba(59, 36, 100, 0) 72%
+ ),
+ #130828;
+ color: var(--task-white);
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 18px;
+ text-align: center;
+}
+
+.task-action.is-claimable {
+ border-color: rgba(255, 240, 155, 0.95);
+ background:
+ linear-gradient(
+ 90deg,
+ rgba(129, 53, 159, 0.15),
+ rgba(255, 0, 199, 0.62)
+ ),
+ url('./assets/button-glow.png') center / 98px 28px no-repeat,
+ #130828;
+}
+
+.task-action.is-claimed,
+.task-action:disabled {
+ cursor: default;
+}
+
+.task-action.is-claimed {
+ border-color: rgba(255, 255, 255, 0.72);
+ background:
+ linear-gradient(175deg, rgba(70, 42, 120, 0.4), rgba(59, 36, 100, 0)),
+ #3a3348;
+ color: rgba(255, 255, 255, 0.86);
+}
+
+.task-action.is-loading {
+ color: rgba(255, 255, 255, 0.72);
+}
+
+.task-state {
+ position: relative;
+ z-index: 1;
+ margin: 64px 24px 0;
+ padding: 14px 16px;
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ background: rgba(19, 8, 40, 0.62);
+ color: rgba(255, 255, 255, 0.86);
+ font-size: 13px;
+ line-height: 18px;
+ text-align: center;
+}
+
+.task-list .task-state {
+ margin: 4px 16px 0;
+}
+
+@media (max-width: 374px) {
+ .app-viewport {
+ margin-left: 0;
+ margin-right: 0;
+ }
+}
diff --git a/common/api.js b/common/api.js
index a65cd5a..bb402ed 100644
--- a/common/api.js
+++ b/common/api.js
@@ -27,7 +27,9 @@ var default_api = 'https://api.global-interaction.com/';
function decodeBase64URLJSON(value) {
try {
- var normalized = String(value || '').replace(/-/g, '+').replace(/_/g, '/');
+ var normalized = String(value || '')
+ .replace(/-/g, '+')
+ .replace(/_/g, '/');
while (normalized.length % 4) normalized += '=';
return JSON.parse(window.atob(normalized));
} catch (_) {
@@ -120,7 +122,8 @@ var default_api = 'https://api.global-interaction.com/';
return memoryAccessToken;
if (memoryAccessToken && isExpiredToken(memoryAccessToken)) {
memoryAccessToken = '';
- if (shouldPersist()) window.localStorage.removeItem(ACCESS_TOKEN_KEY);
+ if (shouldPersist())
+ window.localStorage.removeItem(ACCESS_TOKEN_KEY);
}
var queryToken =
normalizeToken(readQuery('token')) ||
@@ -636,6 +639,20 @@ var default_api = 'https://api.global-interaction.com/';
},
};
+ // 任务页 API 只封装 H5 需要的两个 App 合约;登录态、app_code、env 和错误 envelope 仍由 request 统一处理。
+ // 领取奖励的 command_id 必须来自点击动作本身,页面脚本负责生成并在同一次重试中复用,避免重复点击造成多次发奖。
+ var taskAPI = {
+ tabs: function () {
+ return request('/api/v1/tasks/tabs', { method: 'GET' });
+ },
+ claim: function (payload) {
+ return request('/api/v1/tasks/claim', {
+ method: 'POST',
+ body: payload || {},
+ });
+ },
+ };
+
var weeklyStarAPI = {
current: function () {
return request('/api/v1/activities/weekly-star/current', {
@@ -734,6 +751,7 @@ var default_api = 'https://api.global-interaction.com/';
managerCenter: managerCenterAPI,
superadminCenter: superadminCenterAPI,
level: levelAPI,
+ task: taskAPI,
weeklyStar: weeklyStarAPI,
game: gameAPI,
};
diff --git a/common/jsbridge.js b/common/jsbridge.js
index 3be34f9..3bf35fc 100644
--- a/common/jsbridge.js
+++ b/common/jsbridge.js
@@ -62,6 +62,9 @@
board_type: String(boardType || ''),
});
},
+ openTaskAction: function (payload) {
+ return post('taskAction', payload || {});
+ },
ready: function (payload) {
return post('ready', payload || {});
},
diff --git a/common/locales/ar.json b/common/locales/ar.json
index 96211f3..5f3befb 100644
--- a/common/locales/ar.json
+++ b/common/locales/ar.json
@@ -18,7 +18,7 @@
"diceMatch.apiUnavailable": "واجهة لعبة النرد غير متاحة",
"diceMatch.selectStake": "اختر قيمة الرهان",
"diceMatch.requestFailed": "فشل الطلب",
- "diceMatch.configFeeNote": "الرسوم {fee}% · المجمع {pool}%",
+ "diceMatch.configFeeNote": "الرسوم {fee}%",
"roomReward.pageTitle": "فعالية مكافآت الغرفة",
"roomReward.pageLabel": "فعالية مكافآت الغرفة",
"roomReward.currentTarget": "هدف المكافأة الحالي",
@@ -458,5 +458,26 @@
"recharge.status.pending": "قيد الانتظار",
"recharge.status.redirected": "بانتظار الدفع",
"recharge.status.credited": "تمت الإضافة",
- "recharge.status.failed": "فشل"
+ "recharge.status.failed": "فشل",
+ "tasks.pageTitle": "المهام",
+ "tasks.pageLabel": "مركز المهام",
+ "tasks.navLabel": "تنقل المهام",
+ "tasks.back": "رجوع",
+ "tasks.title": "المهام",
+ "tasks.dailyTitle": "المهام اليومية",
+ "tasks.dailyReset": "تتم إعادة التعيين يوميا عند منتصف الليل",
+ "tasks.exclusiveTitle": "حصري للوافدين الجدد",
+ "tasks.exclusiveLimit": "مرة واحدة فقط",
+ "tasks.go": "اذهب",
+ "tasks.claim": "استلام",
+ "tasks.claiming": "جار الاستلام...",
+ "tasks.claimed": "تم الاستلام",
+ "tasks.empty": "لا توجد مهام بعد.",
+ "tasks.loading": "جار التحميل...",
+ "tasks.untitled": "مهمة",
+ "tasks.apiUnavailable": "واجهة المهام غير متاحة.",
+ "tasks.loadFailed": "فشل التحميل.",
+ "tasks.claimSuccess": "تم استلام المكافأة.",
+ "tasks.claimFailed": "فشل الاستلام.",
+ "tasks.noAction": "لم يتم إعداد الانتقال."
}
diff --git a/common/locales/en.json b/common/locales/en.json
index d28306d..5ea502d 100644
--- a/common/locales/en.json
+++ b/common/locales/en.json
@@ -18,7 +18,7 @@
"diceMatch.apiUnavailable": "Dice API unavailable",
"diceMatch.selectStake": "Select a stake",
"diceMatch.requestFailed": "Request failed",
- "diceMatch.configFeeNote": "Fee {fee}% · Pool {pool}%",
+ "diceMatch.configFeeNote": "Fee {fee}%",
"roomReward.pageTitle": "Room Reward Event",
"roomReward.pageLabel": "Room Reward Event",
"roomReward.currentTarget": "Current reward target",
@@ -464,5 +464,26 @@
"recharge.status.pending": "Pending",
"recharge.status.redirected": "Waiting for payment",
"recharge.status.credited": "Credited",
- "recharge.status.failed": "Failed"
+ "recharge.status.failed": "Failed",
+ "tasks.pageTitle": "Task",
+ "tasks.pageLabel": "Task center",
+ "tasks.navLabel": "Task navigation",
+ "tasks.back": "Back",
+ "tasks.title": "Task",
+ "tasks.dailyTitle": "Daily Tasks",
+ "tasks.dailyReset": "Resets daily at midnight",
+ "tasks.exclusiveTitle": "Exclusive For Newcomers",
+ "tasks.exclusiveLimit": "Limited to one time",
+ "tasks.go": "Go",
+ "tasks.claim": "Claim",
+ "tasks.claiming": "Claiming...",
+ "tasks.claimed": "Claimed",
+ "tasks.empty": "No tasks yet.",
+ "tasks.loading": "Loading...",
+ "tasks.untitled": "Task",
+ "tasks.apiUnavailable": "Task API unavailable.",
+ "tasks.loadFailed": "Load failed.",
+ "tasks.claimSuccess": "Reward claimed.",
+ "tasks.claimFailed": "Claim failed.",
+ "tasks.noAction": "No action configured."
}
diff --git a/common/locales/es.json b/common/locales/es.json
index f28b61b..4b01621 100644
--- a/common/locales/es.json
+++ b/common/locales/es.json
@@ -18,7 +18,7 @@
"diceMatch.apiUnavailable": "API del juego de dados no disponible",
"diceMatch.selectStake": "Selecciona una apuesta",
"diceMatch.requestFailed": "Error de solicitud",
- "diceMatch.configFeeNote": "Comisión {fee}% · Bolsa {pool}%",
+ "diceMatch.configFeeNote": "Comisión {fee}%",
"roomReward.pageTitle": "Evento de recompensas de sala",
"roomReward.pageLabel": "Evento de recompensas de sala",
"roomReward.currentTarget": "Objetivo de recompensa actual",
@@ -458,5 +458,26 @@
"recharge.status.pending": "Pendiente",
"recharge.status.redirected": "Esperando pago",
"recharge.status.credited": "Acreditado",
- "recharge.status.failed": "Fallido"
+ "recharge.status.failed": "Fallido",
+ "tasks.pageTitle": "Tareas",
+ "tasks.pageLabel": "Centro de tareas",
+ "tasks.navLabel": "Navegación de tareas",
+ "tasks.back": "Atrás",
+ "tasks.title": "Tareas",
+ "tasks.dailyTitle": "Tareas diarias",
+ "tasks.dailyReset": "Se reinicia cada día a medianoche",
+ "tasks.exclusiveTitle": "Exclusivo para nuevos usuarios",
+ "tasks.exclusiveLimit": "Limitado a una vez",
+ "tasks.go": "Ir",
+ "tasks.claim": "Reclamar",
+ "tasks.claiming": "Reclamando...",
+ "tasks.claimed": "Reclamado",
+ "tasks.empty": "Aún no hay tareas.",
+ "tasks.loading": "Cargando...",
+ "tasks.untitled": "Tarea",
+ "tasks.apiUnavailable": "API de tareas no disponible.",
+ "tasks.loadFailed": "Error al cargar.",
+ "tasks.claimSuccess": "Recompensa reclamada.",
+ "tasks.claimFailed": "Error al reclamar.",
+ "tasks.noAction": "No hay acción configurada."
}
diff --git a/common/locales/id.json b/common/locales/id.json
index 5f390e1..faae215 100644
--- a/common/locales/id.json
+++ b/common/locales/id.json
@@ -447,5 +447,26 @@
"recharge.status.pending": "Menunggu",
"recharge.status.redirected": "Menunggu pembayaran",
"recharge.status.credited": "Masuk",
- "recharge.status.failed": "Gagal"
+ "recharge.status.failed": "Gagal",
+ "tasks.pageTitle": "Tugas",
+ "tasks.pageLabel": "Pusat tugas",
+ "tasks.navLabel": "Navigasi tugas",
+ "tasks.back": "Kembali",
+ "tasks.title": "Tugas",
+ "tasks.dailyTitle": "Tugas Harian",
+ "tasks.dailyReset": "Reset setiap tengah malam",
+ "tasks.exclusiveTitle": "Khusus pengguna baru",
+ "tasks.exclusiveLimit": "Terbatas satu kali",
+ "tasks.go": "Pergi",
+ "tasks.claim": "Klaim",
+ "tasks.claiming": "Mengklaim...",
+ "tasks.claimed": "Diklaim",
+ "tasks.empty": "Belum ada tugas.",
+ "tasks.loading": "Memuat...",
+ "tasks.untitled": "Tugas",
+ "tasks.apiUnavailable": "API tugas tidak tersedia.",
+ "tasks.loadFailed": "Gagal memuat.",
+ "tasks.claimSuccess": "Hadiah diklaim.",
+ "tasks.claimFailed": "Gagal klaim.",
+ "tasks.noAction": "Aksi belum dikonfigurasi."
}
diff --git a/common/locales/tr.json b/common/locales/tr.json
index 683eba2..f2ba698 100644
--- a/common/locales/tr.json
+++ b/common/locales/tr.json
@@ -447,5 +447,26 @@
"recharge.status.pending": "Bekliyor",
"recharge.status.redirected": "Ödeme bekleniyor",
"recharge.status.credited": "Yüklendi",
- "recharge.status.failed": "Başarısız"
+ "recharge.status.failed": "Başarısız",
+ "tasks.pageTitle": "Görev",
+ "tasks.pageLabel": "Görev merkezi",
+ "tasks.navLabel": "Görev navigasyonu",
+ "tasks.back": "Geri",
+ "tasks.title": "Görev",
+ "tasks.dailyTitle": "Günlük Görevler",
+ "tasks.dailyReset": "Her gün gece yarısı yenilenir",
+ "tasks.exclusiveTitle": "Yeni kullanıcıya özel",
+ "tasks.exclusiveLimit": "Bir kez ile sınırlı",
+ "tasks.go": "Git",
+ "tasks.claim": "Al",
+ "tasks.claiming": "Alınıyor...",
+ "tasks.claimed": "Alındı",
+ "tasks.empty": "Henüz görev yok.",
+ "tasks.loading": "Yükleniyor...",
+ "tasks.untitled": "Görev",
+ "tasks.apiUnavailable": "Görev API kullanılamıyor.",
+ "tasks.loadFailed": "Yükleme başarısız.",
+ "tasks.claimSuccess": "Ödül alındı.",
+ "tasks.claimFailed": "Alma başarısız.",
+ "tasks.noAction": "Eylem yapılandırılmadı."
}
diff --git a/common/locales/zh.json b/common/locales/zh.json
index bc2c983..81470f9 100644
--- a/common/locales/zh.json
+++ b/common/locales/zh.json
@@ -453,5 +453,26 @@
"recharge.status.pending": "待支付",
"recharge.status.redirected": "等待支付",
"recharge.status.credited": "已到账",
- "recharge.status.failed": "失败"
+ "recharge.status.failed": "失败",
+ "tasks.pageTitle": "任务",
+ "tasks.pageLabel": "任务中心",
+ "tasks.navLabel": "任务导航",
+ "tasks.back": "返回",
+ "tasks.title": "任务",
+ "tasks.dailyTitle": "每日任务",
+ "tasks.dailyReset": "每日0点重置",
+ "tasks.exclusiveTitle": "新人专属",
+ "tasks.exclusiveLimit": "限一次",
+ "tasks.go": "去",
+ "tasks.claim": "领取",
+ "tasks.claiming": "领取中...",
+ "tasks.claimed": "已领取",
+ "tasks.empty": "暂无任务",
+ "tasks.loading": "加载中...",
+ "tasks.untitled": "任务",
+ "tasks.apiUnavailable": "任务接口不可用",
+ "tasks.loadFailed": "加载失败",
+ "tasks.claimSuccess": "领取成功",
+ "tasks.claimFailed": "领取失败",
+ "tasks.noAction": "未配置跳转"
}
diff --git a/game/touzi/index.html b/game/touzi/index.html
index 102ffa5..118e76a 100644
--- a/game/touzi/index.html
+++ b/game/touzi/index.html
@@ -77,6 +77,11 @@
animation: screen-impact-shake 520ms ease-out 420ms both;
}
+ .dice-frame.is-self-win-compare {
+ animation: you-win-screen-smash 780ms
+ cubic-bezier(0.14, 0.84, 0.18, 1) 430ms both;
+ }
+
.game-content {
position: absolute;
inset: 0;
@@ -182,12 +187,14 @@
position: relative;
display: flex;
align-items: center;
- justify-content: center;
- width: 102px;
+ justify-content: flex-end;
+ width: max-content;
+ min-width: 102px;
+ max-width: 176px;
height: 32px;
- flex: 0 0 102px;
+ flex: 0 1 auto;
gap: 4px;
- overflow: hidden;
+ overflow: visible;
padding: 0 7px;
/* border: 1px solid rgba(108, 113, 194, 0.96);
border-radius: 4px; */
@@ -218,16 +225,16 @@
.top-coin span {
display: block;
- min-width: 0;
- max-width: 48px;
- flex: 0 1 auto;
- overflow: hidden;
+ min-width: max-content;
+ max-width: none;
+ flex: 0 0 auto;
+ overflow: visible;
line-height: 20px;
}
.top-coin.is-compact span {
- max-width: 38px;
- font-size: 16px;
+ max-width: none;
+ font-size: 18px;
}
.top-coin img {
@@ -933,6 +940,108 @@
opacity: 1;
}
+ .you-win-smash-layer,
+ .you-win-impact {
+ position: absolute;
+ visibility: hidden;
+ opacity: 0;
+ pointer-events: none;
+ }
+
+ .you-win-smash-layer {
+ inset: 0;
+ z-index: 23;
+ overflow: hidden;
+ }
+
+ .you-win-smash-layer::before {
+ position: absolute;
+ inset: 0;
+ content: '';
+ background:
+ radial-gradient(
+ circle at 50% 39%,
+ rgba(255, 248, 179, 0.72) 0%,
+ rgba(255, 126, 20, 0.3) 18%,
+ rgba(255, 73, 0, 0.08) 35%,
+ rgba(255, 73, 0, 0) 58%
+ ),
+ rgba(255, 255, 255, 0.2);
+ opacity: 0;
+ }
+
+ .you-win-smash-floor {
+ position: absolute;
+ top: 362px;
+ left: 50%;
+ width: 312px;
+ height: 92px;
+ border-radius: 50%;
+ background: radial-gradient(
+ ellipse at center,
+ rgba(255, 215, 75, 0.68) 0%,
+ rgba(255, 103, 15, 0.34) 30%,
+ rgba(120, 16, 0, 0.28) 54%,
+ rgba(0, 0, 0, 0) 76%
+ );
+ opacity: 0;
+ filter: blur(1px) drop-shadow(0 0 18px rgba(255, 118, 22, 0.76));
+ transform: translateX(-50%) scale(0.38, 0.2);
+ transform-origin: 50% 50%;
+ }
+
+ .dice-frame.is-self-win-compare .you-win-smash-layer {
+ visibility: visible;
+ opacity: 1;
+ }
+
+ .dice-frame.is-self-win-compare .you-win-smash-layer::before {
+ animation: you-win-screen-flash 760ms ease-out 430ms both;
+ }
+
+ .dice-frame.is-self-win-compare .you-win-smash-floor {
+ animation: you-win-floor-impact 900ms ease-out 430ms both;
+ }
+
+ .you-win-impact {
+ top: 300px;
+ left: 0;
+ z-index: 25;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 375px;
+ height: 92px;
+ color: transparent;
+ font-size: 56px;
+ font-weight: 1000;
+ line-height: 62px;
+ text-align: center;
+ background: linear-gradient(
+ 180deg,
+ #ffffff 0%,
+ #fff5b6 19%,
+ #ffcf44 48%,
+ #ff7a00 74%,
+ #9b2d00 100%
+ );
+ -webkit-background-clip: text;
+ background-clip: text;
+ -webkit-text-stroke: 2px #7d2700;
+ filter: drop-shadow(0 5px 0 rgba(94, 17, 0, 0.9))
+ drop-shadow(0 0 22px rgba(255, 205, 46, 0.9))
+ drop-shadow(0 0 36px rgba(255, 77, 0, 0.78));
+ transform-origin: 50% 78%;
+ will-change: opacity, transform, filter;
+ }
+
+ .dice-frame.is-self-win-compare .you-win-impact {
+ visibility: visible;
+ opacity: 1;
+ animation: you-win-drop-impact 980ms
+ cubic-bezier(0.13, 0.92, 0.18, 1) both;
+ }
+
.countdown-number {
min-width: 132px;
color: transparent;
@@ -1264,6 +1373,10 @@
.dice-frame.is-matching .vs-badge,
.dice-frame.is-matching .vs-badge::after,
+ .dice-frame.is-self-win-compare,
+ .dice-frame.is-self-win-compare .you-win-smash-layer::before,
+ .dice-frame.is-self-win-compare .you-win-smash-floor,
+ .dice-frame.is-self-win-compare .you-win-impact,
.dice-frame.is-matching,
.dice-frame.is-searching .center-search-bg,
.dice-frame.is-searching .search-indicator img,
@@ -1343,6 +1456,105 @@
}
}
+ @keyframes you-win-drop-impact {
+ 0% {
+ opacity: 0;
+ filter: brightness(1.9) drop-shadow(0 0 36px #ff79ff);
+ transform: translateY(-650px) scale(3.9) rotate(-7deg);
+ }
+ 34% {
+ opacity: 1;
+ filter: brightness(1.75) drop-shadow(0 0 32px #4cc9ff);
+ transform: translateY(-122px) scale(2.05) rotate(4deg);
+ }
+ 58% {
+ opacity: 1;
+ filter: brightness(1.55) drop-shadow(0 0 30px #ffd557);
+ transform: translateY(28px) scale(0.78) rotate(1deg);
+ }
+ 72% {
+ filter: brightness(1.42) drop-shadow(0 0 24px #ff79ff);
+ transform: translateY(-16px) scale(1.16) rotate(-0.8deg);
+ }
+ 86% {
+ filter: brightness(1.18) drop-shadow(0 0 16px #4cc9ff);
+ transform: translateY(4px) scale(0.96) rotate(0.3deg);
+ }
+ 100% {
+ opacity: 1;
+ filter: drop-shadow(0 5px 0 rgba(94, 17, 0, 0.9))
+ drop-shadow(0 0 22px rgba(255, 205, 46, 0.9))
+ drop-shadow(0 0 36px rgba(255, 77, 0, 0.78));
+ transform: translateY(0) scale(1) rotate(0);
+ }
+ }
+
+ @keyframes you-win-screen-smash {
+ 0%,
+ 100% {
+ transform: translate3d(0, 0, 0) scale(var(--dice-scale));
+ filter: none;
+ }
+ 12% {
+ transform: translate3d(0, 14px, 0) scale(var(--dice-scale));
+ filter: brightness(1.22) contrast(1.08);
+ }
+ 24% {
+ transform: translate3d(-12px, -8px, 0)
+ scale(var(--dice-scale));
+ }
+ 36% {
+ transform: translate3d(10px, 7px, 0)
+ scale(var(--dice-scale));
+ }
+ 50% {
+ transform: translate3d(-7px, 4px, 0)
+ scale(var(--dice-scale));
+ }
+ 66% {
+ transform: translate3d(5px, -3px, 0)
+ scale(var(--dice-scale));
+ filter: brightness(1.08) contrast(1.03);
+ }
+ 82% {
+ transform: translate3d(-2px, 1px, 0)
+ scale(var(--dice-scale));
+ }
+ }
+
+ @keyframes you-win-screen-flash {
+ 0% {
+ opacity: 0;
+ }
+ 8% {
+ opacity: 1;
+ }
+ 22% {
+ opacity: 0.46;
+ }
+ 100% {
+ opacity: 0;
+ }
+ }
+
+ @keyframes you-win-floor-impact {
+ 0% {
+ opacity: 0;
+ transform: translateX(-50%) scale(0.38, 0.2);
+ }
+ 16% {
+ opacity: 1;
+ transform: translateX(-50%) scale(1.05, 0.52);
+ }
+ 58% {
+ opacity: 0.62;
+ }
+ 100% {
+ opacity: 0;
+ transform: translateX(-50%) scale(1.32, 0.72);
+ }
+ }
+
@keyframes search-orbit {
0% {
transform: rotate(0deg)
@@ -2042,6 +2254,20 @@
data-role="phase-text"
aria-live="polite"
>
+
+
+
+
+ YOU WIN
+
= 100000
+ number >= 1000000
);
topCoin.setAttribute('title', fullLabel);
topCoin.setAttribute(
@@ -2711,16 +2919,17 @@
selectedButton || document.querySelector('.stake')
);
if (config) {
- var fee = Number(config.fee_bps || 0) / 100;
- var pool = Number(config.pool_bps || 0) / 100;
+ var totalFee =
+ (Number(config.fee_bps || 0) +
+ Number(config.pool_bps || 0)) /
+ 100;
setText(
document.querySelector('.fee-note'),
i18nFormat(
'diceMatch.configFeeNote',
- 'Fee {fee}% · Pool {pool}%',
+ 'Fee {fee}%',
{
- fee: fee,
- pool: pool,
+ fee: totalFee,
}
)
);
@@ -2734,6 +2943,7 @@
'is-matching',
'is-countdown',
'is-victory',
+ 'is-self-win-compare',
'has-scores'
);
setCountdownNumber('');
@@ -2867,6 +3077,22 @@
);
}
+ function isSelfWinner(match) {
+ var self = findSelfParticipant(match);
+ var winner = findWinnerParticipant(match);
+ if (!self) return false;
+ if (winner && sameParticipant(winner, self)) return true;
+ return self.result === 'win';
+ }
+
+ function setSelfWinCompare(visible) {
+ frame.classList.remove('is-self-win-compare');
+ if (!visible) return;
+ // 每一局 Compare 都要重新播放“砸下”效果,因此先读一次布局强制浏览器提交移除状态。
+ void frame.offsetWidth;
+ frame.classList.add('is-self-win-compare');
+ }
+
function renderMatchParticipants(match) {
var self = findSelfParticipant(match);
var opponent = findOpponentParticipant(match, self);
@@ -3064,12 +3290,18 @@
stopRollEffects();
renderMatchParticipants(match);
frame.classList.add('has-scores');
+ var drawReroll = isDrawReroll(match);
+ var selfWinCompare =
+ !drawReroll && isSelfWinner(match);
setPhaseText(
- isDrawReroll(match)
- ? i18nText('diceMatch.draw', 'Draw')
- : i18nText('diceMatch.compare', 'Compare')
+ selfWinCompare
+ ? ''
+ : drawReroll
+ ? i18nText('diceMatch.draw', 'Draw')
+ : i18nText('diceMatch.compare', 'Compare')
);
- if (isDrawReroll(match)) {
+ setSelfWinCompare(selfWinCompare);
+ if (drawReroll) {
return delay(2000).then(function () {
if (sequence !== roundSequence) return;
rollAndAnimate(sequence);