任务和游戏
9
activity/tasks/assets/back.png
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="icon_20_返回">
|
||||||
|
<g id="Vector">
|
||||||
|
</g>
|
||||||
|
<g id="Vector_2">
|
||||||
|
</g>
|
||||||
|
<path id="Vector_3" d="M16 4L8 12L16 20" stroke="var(--stroke-0, white)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 419 B |
BIN
activity/tasks/assets/button-glow.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
activity/tasks/assets/coin.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
activity/tasks/assets/figma-game-icon.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
activity/tasks/assets/figma-task-full.png
Normal file
|
After Width: | Height: | Size: 397 KiB |
BIN
activity/tasks/assets/task-bg.png
Normal file
|
After Width: | Height: | Size: 4.7 MiB |
BIN
activity/tasks/assets/task-h5-preview-wide.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
activity/tasks/assets/task-h5-preview.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
84
activity/tasks/index.html
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
|
||||||
|
/>
|
||||||
|
<title data-i18n="tasks.pageTitle">Task</title>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="../../common/theme.css?v=20260611-task-h5"
|
||||||
|
/>
|
||||||
|
<link rel="stylesheet" href="./style.css?v=20260611-task-h5" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app-viewport" id="appViewport">
|
||||||
|
<main
|
||||||
|
class="task-page"
|
||||||
|
id="page"
|
||||||
|
aria-label="Task center"
|
||||||
|
data-i18n-aria="tasks.pageLabel"
|
||||||
|
>
|
||||||
|
<nav
|
||||||
|
class="task-nav"
|
||||||
|
aria-label="Task navigation"
|
||||||
|
data-i18n-aria="tasks.navLabel"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="back-button"
|
||||||
|
id="backButton"
|
||||||
|
type="button"
|
||||||
|
aria-label="Back"
|
||||||
|
data-i18n-aria="tasks.back"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<h1 data-i18n="tasks.title">Task</h1>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="task-section" data-section-block="daily">
|
||||||
|
<header class="section-head">
|
||||||
|
<h2 data-i18n="tasks.dailyTitle">Daily Tasks</h2>
|
||||||
|
<p data-refresh-text data-i18n="tasks.dailyReset">
|
||||||
|
Resets daily at midnight
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div
|
||||||
|
class="task-list"
|
||||||
|
id="dailyTasks"
|
||||||
|
aria-live="polite"
|
||||||
|
></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
class="task-section task-section-exclusive"
|
||||||
|
data-section-block="exclusive"
|
||||||
|
>
|
||||||
|
<header class="section-head">
|
||||||
|
<h2 data-i18n="tasks.exclusiveTitle">
|
||||||
|
Exclusive For Newcomers
|
||||||
|
</h2>
|
||||||
|
<p data-i18n="tasks.exclusiveLimit">
|
||||||
|
Limited to one time
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div
|
||||||
|
class="task-list"
|
||||||
|
id="exclusiveTasks"
|
||||||
|
aria-live="polite"
|
||||||
|
></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="task-state" id="taskState" hidden></div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../../common/i18n.js?v=20260611-task-h5"></script>
|
||||||
|
<script src="../../common/api.js?v=20260611-task-h5"></script>
|
||||||
|
<script src="../../common/jsbridge.js?v=20260611-task-h5"></script>
|
||||||
|
<script src="../../common/toast.js?v=20260611-task-h5"></script>
|
||||||
|
<script src="./script.js?v=20260611-task-h5"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
594
activity/tasks/script.js
Normal file
@ -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, '"')
|
||||||
|
.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 [
|
||||||
|
'<span class="task-icon is-image" data-icon-fallback="',
|
||||||
|
escapeAttr(iconKind(item)),
|
||||||
|
'"><img src="',
|
||||||
|
escapeAttr(iconURL),
|
||||||
|
'" alt="" /></span>',
|
||||||
|
].join('');
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
'<span class="task-icon">' + svgForIcon(iconKind(item)) + '</span>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function svgForIcon(kind) {
|
||||||
|
if (kind === 'mic') {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#fff" d="M12 3a4 4 0 0 0-4 4v5a4 4 0 0 0 8 0V7a4 4 0 0 0-4-4Zm-6 8.5a1 1 0 0 1 2 0V12a4 4 0 0 0 8 0v-.5a1 1 0 1 1 2 0V12a6 6 0 0 1-5 5.92V20h3a1 1 0 1 1 0 2H8a1 1 0 1 1 0-2h3v-2.08A6 6 0 0 1 6 12v-.5Z"/></svg>';
|
||||||
|
}
|
||||||
|
if (kind === 'gift') {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#fff" d="M8.2 4.2A3 3 0 0 1 12 5a3 3 0 0 1 3.8-.8A3.1 3.1 0 0 1 17 8h2a1 1 0 0 1 1 1v3h-7V8h-2v4H4V9a1 1 0 0 1 1-1h2a3.1 3.1 0 0 1 1.2-3.8ZM9 6a1 1 0 0 0 .2 2H11a2 2 0 0 0-2-2Zm4 2h1.8A1 1 0 1 0 13 6a2 2 0 0 0 0 2ZM5 14h6v6H6a1 1 0 0 1-1-1v-5Zm8 6v-6h6v5a1 1 0 0 1-1 1h-5Z"/></svg>';
|
||||||
|
}
|
||||||
|
if (kind === 'cp') {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#fff" d="M7.5 6A3.5 3.5 0 0 1 12 9.35 3.5 3.5 0 1 1 16.5 6c2.45 0 4.5 1.9 4.5 4.35 0 4.25-6.05 8.05-8.35 9.35a1.3 1.3 0 0 1-1.3 0C9.05 18.4 3 14.6 3 10.35 3 7.9 5.05 6 7.5 6Z"/></svg>';
|
||||||
|
}
|
||||||
|
if (kind === 'mood') {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#fff" d="M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm-3 7.2a1.2 1.2 0 1 1 0-2.4 1.2 1.2 0 0 1 0 2.4Zm6 0a1.2 1.2 0 1 1 0-2.4 1.2 1.2 0 0 1 0 2.4Zm-6.3 3.35a1 1 0 0 1 1.4.1 2.5 2.5 0 0 0 3.8 0 1 1 0 0 1 1.5 1.3 4.5 4.5 0 0 1-6.8 0 1 1 0 0 1 .1-1.4Z"/></svg>';
|
||||||
|
}
|
||||||
|
if (kind === 'coin') {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#fff" d="M12 3c4.42 0 8 2.02 8 4.5V16c0 2.76-3.58 5-8 5s-8-2.24-8-5V7.5C4 5.02 7.58 3 12 3Zm0 2C8.14 5 6 6.47 6 7.5S8.14 10 12 10s6-1.47 6-2.5S15.86 5 12 5Zm6 5.3c-1.45 1.05-3.6 1.7-6 1.7s-4.55-.65-6-1.7V12c0 1.2 2.33 3 6 3s6-1.8 6-3v-1.7Zm0 5c-1.45 1.05-3.6 1.7-6 1.7s-4.55-.65-6-1.7V16c0 1.2 2.33 3 6 3s6-1.8 6-3v-.7Z"/></svg>';
|
||||||
|
}
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#fff" d="M8 9h8a4 4 0 0 1 0 8h-.5a2 2 0 0 1-1.6-.8l-.9-1.2h-2l-.9 1.2a2 2 0 0 1-1.6.8H8a4 4 0 0 1 0-8Zm.5 3H7v1.5h1.5V15H10v-1.5h1.5V12H10v-1.5H8.5V12Zm6.5.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm2.5 2.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"/></svg>';
|
||||||
|
}
|
||||||
|
|
||||||
|
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 =
|
||||||
|
'<div class="task-state">' +
|
||||||
|
escapeHTML(t('tasks.empty', 'No tasks yet.')) +
|
||||||
|
'</div>';
|
||||||
|
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 [
|
||||||
|
'<article class="task-row" data-task-key="',
|
||||||
|
escapeAttr(key),
|
||||||
|
'">',
|
||||||
|
iconHTML(item),
|
||||||
|
'<div class="task-main">',
|
||||||
|
'<strong class="task-title">',
|
||||||
|
escapeHTML(
|
||||||
|
item.title || item.description || t('tasks.untitled', 'Task')
|
||||||
|
),
|
||||||
|
'</strong>',
|
||||||
|
'<span class="task-progress">',
|
||||||
|
escapeHTML(formatProgress(item)),
|
||||||
|
'</span>',
|
||||||
|
'<span class="task-reward"><img src="',
|
||||||
|
COIN_IMAGE,
|
||||||
|
'" alt="" aria-hidden="true" /><span>',
|
||||||
|
escapeHTML(
|
||||||
|
formatReward(item.reward_coin_amount || item.rewardCoinAmount)
|
||||||
|
),
|
||||||
|
'</span></span>',
|
||||||
|
'</div>',
|
||||||
|
'<button class="task-action ',
|
||||||
|
action.className,
|
||||||
|
loading ? ' is-loading' : '',
|
||||||
|
'" type="button" data-task-action="',
|
||||||
|
escapeAttr(key),
|
||||||
|
'"',
|
||||||
|
action.disabled || loading ? ' disabled' : '',
|
||||||
|
'>',
|
||||||
|
escapeHTML(label),
|
||||||
|
'</button>',
|
||||||
|
'</article>',
|
||||||
|
].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' });
|
||||||
|
}
|
||||||
|
})();
|
||||||
325
activity/tasks/style.css
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,7 +27,9 @@ var default_api = 'https://api.global-interaction.com/';
|
|||||||
|
|
||||||
function decodeBase64URLJSON(value) {
|
function decodeBase64URLJSON(value) {
|
||||||
try {
|
try {
|
||||||
var normalized = String(value || '').replace(/-/g, '+').replace(/_/g, '/');
|
var normalized = String(value || '')
|
||||||
|
.replace(/-/g, '+')
|
||||||
|
.replace(/_/g, '/');
|
||||||
while (normalized.length % 4) normalized += '=';
|
while (normalized.length % 4) normalized += '=';
|
||||||
return JSON.parse(window.atob(normalized));
|
return JSON.parse(window.atob(normalized));
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
@ -120,7 +122,8 @@ var default_api = 'https://api.global-interaction.com/';
|
|||||||
return memoryAccessToken;
|
return memoryAccessToken;
|
||||||
if (memoryAccessToken && isExpiredToken(memoryAccessToken)) {
|
if (memoryAccessToken && isExpiredToken(memoryAccessToken)) {
|
||||||
memoryAccessToken = '';
|
memoryAccessToken = '';
|
||||||
if (shouldPersist()) window.localStorage.removeItem(ACCESS_TOKEN_KEY);
|
if (shouldPersist())
|
||||||
|
window.localStorage.removeItem(ACCESS_TOKEN_KEY);
|
||||||
}
|
}
|
||||||
var queryToken =
|
var queryToken =
|
||||||
normalizeToken(readQuery('token')) ||
|
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 = {
|
var weeklyStarAPI = {
|
||||||
current: function () {
|
current: function () {
|
||||||
return request('/api/v1/activities/weekly-star/current', {
|
return request('/api/v1/activities/weekly-star/current', {
|
||||||
@ -734,6 +751,7 @@ var default_api = 'https://api.global-interaction.com/';
|
|||||||
managerCenter: managerCenterAPI,
|
managerCenter: managerCenterAPI,
|
||||||
superadminCenter: superadminCenterAPI,
|
superadminCenter: superadminCenterAPI,
|
||||||
level: levelAPI,
|
level: levelAPI,
|
||||||
|
task: taskAPI,
|
||||||
weeklyStar: weeklyStarAPI,
|
weeklyStar: weeklyStarAPI,
|
||||||
game: gameAPI,
|
game: gameAPI,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -62,6 +62,9 @@
|
|||||||
board_type: String(boardType || ''),
|
board_type: String(boardType || ''),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
openTaskAction: function (payload) {
|
||||||
|
return post('taskAction', payload || {});
|
||||||
|
},
|
||||||
ready: function (payload) {
|
ready: function (payload) {
|
||||||
return post('ready', payload || {});
|
return post('ready', payload || {});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
"diceMatch.apiUnavailable": "واجهة لعبة النرد غير متاحة",
|
"diceMatch.apiUnavailable": "واجهة لعبة النرد غير متاحة",
|
||||||
"diceMatch.selectStake": "اختر قيمة الرهان",
|
"diceMatch.selectStake": "اختر قيمة الرهان",
|
||||||
"diceMatch.requestFailed": "فشل الطلب",
|
"diceMatch.requestFailed": "فشل الطلب",
|
||||||
"diceMatch.configFeeNote": "الرسوم {fee}% · المجمع {pool}%",
|
"diceMatch.configFeeNote": "الرسوم {fee}%",
|
||||||
"roomReward.pageTitle": "فعالية مكافآت الغرفة",
|
"roomReward.pageTitle": "فعالية مكافآت الغرفة",
|
||||||
"roomReward.pageLabel": "فعالية مكافآت الغرفة",
|
"roomReward.pageLabel": "فعالية مكافآت الغرفة",
|
||||||
"roomReward.currentTarget": "هدف المكافأة الحالي",
|
"roomReward.currentTarget": "هدف المكافأة الحالي",
|
||||||
@ -458,5 +458,26 @@
|
|||||||
"recharge.status.pending": "قيد الانتظار",
|
"recharge.status.pending": "قيد الانتظار",
|
||||||
"recharge.status.redirected": "بانتظار الدفع",
|
"recharge.status.redirected": "بانتظار الدفع",
|
||||||
"recharge.status.credited": "تمت الإضافة",
|
"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": "لم يتم إعداد الانتقال."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
"diceMatch.apiUnavailable": "Dice API unavailable",
|
"diceMatch.apiUnavailable": "Dice API unavailable",
|
||||||
"diceMatch.selectStake": "Select a stake",
|
"diceMatch.selectStake": "Select a stake",
|
||||||
"diceMatch.requestFailed": "Request failed",
|
"diceMatch.requestFailed": "Request failed",
|
||||||
"diceMatch.configFeeNote": "Fee {fee}% · Pool {pool}%",
|
"diceMatch.configFeeNote": "Fee {fee}%",
|
||||||
"roomReward.pageTitle": "Room Reward Event",
|
"roomReward.pageTitle": "Room Reward Event",
|
||||||
"roomReward.pageLabel": "Room Reward Event",
|
"roomReward.pageLabel": "Room Reward Event",
|
||||||
"roomReward.currentTarget": "Current reward target",
|
"roomReward.currentTarget": "Current reward target",
|
||||||
@ -464,5 +464,26 @@
|
|||||||
"recharge.status.pending": "Pending",
|
"recharge.status.pending": "Pending",
|
||||||
"recharge.status.redirected": "Waiting for payment",
|
"recharge.status.redirected": "Waiting for payment",
|
||||||
"recharge.status.credited": "Credited",
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
"diceMatch.apiUnavailable": "API del juego de dados no disponible",
|
"diceMatch.apiUnavailable": "API del juego de dados no disponible",
|
||||||
"diceMatch.selectStake": "Selecciona una apuesta",
|
"diceMatch.selectStake": "Selecciona una apuesta",
|
||||||
"diceMatch.requestFailed": "Error de solicitud",
|
"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.pageTitle": "Evento de recompensas de sala",
|
||||||
"roomReward.pageLabel": "Evento de recompensas de sala",
|
"roomReward.pageLabel": "Evento de recompensas de sala",
|
||||||
"roomReward.currentTarget": "Objetivo de recompensa actual",
|
"roomReward.currentTarget": "Objetivo de recompensa actual",
|
||||||
@ -458,5 +458,26 @@
|
|||||||
"recharge.status.pending": "Pendiente",
|
"recharge.status.pending": "Pendiente",
|
||||||
"recharge.status.redirected": "Esperando pago",
|
"recharge.status.redirected": "Esperando pago",
|
||||||
"recharge.status.credited": "Acreditado",
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -447,5 +447,26 @@
|
|||||||
"recharge.status.pending": "Menunggu",
|
"recharge.status.pending": "Menunggu",
|
||||||
"recharge.status.redirected": "Menunggu pembayaran",
|
"recharge.status.redirected": "Menunggu pembayaran",
|
||||||
"recharge.status.credited": "Masuk",
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -447,5 +447,26 @@
|
|||||||
"recharge.status.pending": "Bekliyor",
|
"recharge.status.pending": "Bekliyor",
|
||||||
"recharge.status.redirected": "Ödeme bekleniyor",
|
"recharge.status.redirected": "Ödeme bekleniyor",
|
||||||
"recharge.status.credited": "Yüklendi",
|
"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ı."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -453,5 +453,26 @@
|
|||||||
"recharge.status.pending": "待支付",
|
"recharge.status.pending": "待支付",
|
||||||
"recharge.status.redirected": "等待支付",
|
"recharge.status.redirected": "等待支付",
|
||||||
"recharge.status.credited": "已到账",
|
"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": "未配置跳转"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,6 +77,11 @@
|
|||||||
animation: screen-impact-shake 520ms ease-out 420ms both;
|
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 {
|
.game-content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@ -182,12 +187,14 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: flex-end;
|
||||||
width: 102px;
|
width: max-content;
|
||||||
|
min-width: 102px;
|
||||||
|
max-width: 176px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
flex: 0 0 102px;
|
flex: 0 1 auto;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
padding: 0 7px;
|
padding: 0 7px;
|
||||||
/* border: 1px solid rgba(108, 113, 194, 0.96);
|
/* border: 1px solid rgba(108, 113, 194, 0.96);
|
||||||
border-radius: 4px; */
|
border-radius: 4px; */
|
||||||
@ -218,16 +225,16 @@
|
|||||||
|
|
||||||
.top-coin span {
|
.top-coin span {
|
||||||
display: block;
|
display: block;
|
||||||
min-width: 0;
|
min-width: max-content;
|
||||||
max-width: 48px;
|
max-width: none;
|
||||||
flex: 0 1 auto;
|
flex: 0 0 auto;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-coin.is-compact span {
|
.top-coin.is-compact span {
|
||||||
max-width: 38px;
|
max-width: none;
|
||||||
font-size: 16px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-coin img {
|
.top-coin img {
|
||||||
@ -933,6 +940,108 @@
|
|||||||
opacity: 1;
|
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 {
|
.countdown-number {
|
||||||
min-width: 132px;
|
min-width: 132px;
|
||||||
color: transparent;
|
color: transparent;
|
||||||
@ -1264,6 +1373,10 @@
|
|||||||
|
|
||||||
.dice-frame.is-matching .vs-badge,
|
.dice-frame.is-matching .vs-badge,
|
||||||
.dice-frame.is-matching .vs-badge::after,
|
.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-matching,
|
||||||
.dice-frame.is-searching .center-search-bg,
|
.dice-frame.is-searching .center-search-bg,
|
||||||
.dice-frame.is-searching .search-indicator img,
|
.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 {
|
@keyframes search-orbit {
|
||||||
0% {
|
0% {
|
||||||
transform: rotate(0deg)
|
transform: rotate(0deg)
|
||||||
@ -2042,6 +2254,20 @@
|
|||||||
data-role="phase-text"
|
data-role="phase-text"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
></div>
|
></div>
|
||||||
|
<div
|
||||||
|
class="you-win-smash-layer"
|
||||||
|
data-role="you-win-smash"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<span class="you-win-smash-floor"></span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="you-win-impact"
|
||||||
|
data-role="you-win-impact"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
YOU WIN
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="countdown-layer"
|
class="countdown-layer"
|
||||||
data-role="countdown-layer"
|
data-role="countdown-layer"
|
||||||
@ -2459,30 +2685,12 @@
|
|||||||
return Math.trunc(number).toLocaleString('en-US');
|
return Math.trunc(number).toLocaleString('en-US');
|
||||||
}
|
}
|
||||||
|
|
||||||
function trimCompactDecimal(value) {
|
|
||||||
return String(value).replace(/\.0$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTopCoin(value) {
|
function formatTopCoin(value) {
|
||||||
var number = Number(value || 0);
|
var number = Number(value || 0);
|
||||||
if (!Number.isFinite(number)) number = 0;
|
if (!Number.isFinite(number)) number = 0;
|
||||||
number = Math.max(0, Math.trunc(number));
|
number = Math.max(0, Math.trunc(number));
|
||||||
if (number < 100000) return formatCoin(number);
|
if (number < 1000000) return formatCoin(number);
|
||||||
if (number < 1000000)
|
return Math.floor(number / 1000) + 'K';
|
||||||
return Math.floor(number / 1000) + 'K';
|
|
||||||
if (number < 10000000)
|
|
||||||
return (
|
|
||||||
trimCompactDecimal(
|
|
||||||
(Math.floor(number / 100000) / 10).toFixed(1)
|
|
||||||
) + 'M'
|
|
||||||
);
|
|
||||||
if (number < 1000000000)
|
|
||||||
return Math.floor(number / 1000000) + 'M';
|
|
||||||
return (
|
|
||||||
trimCompactDecimal(
|
|
||||||
(Math.floor(number / 100000000) / 10).toFixed(1)
|
|
||||||
) + 'B'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCoin(value) {
|
function setCoin(value) {
|
||||||
@ -2495,7 +2703,7 @@
|
|||||||
if (topCoin) {
|
if (topCoin) {
|
||||||
topCoin.classList.toggle(
|
topCoin.classList.toggle(
|
||||||
'is-compact',
|
'is-compact',
|
||||||
number >= 100000
|
number >= 1000000
|
||||||
);
|
);
|
||||||
topCoin.setAttribute('title', fullLabel);
|
topCoin.setAttribute('title', fullLabel);
|
||||||
topCoin.setAttribute(
|
topCoin.setAttribute(
|
||||||
@ -2711,16 +2919,17 @@
|
|||||||
selectedButton || document.querySelector('.stake')
|
selectedButton || document.querySelector('.stake')
|
||||||
);
|
);
|
||||||
if (config) {
|
if (config) {
|
||||||
var fee = Number(config.fee_bps || 0) / 100;
|
var totalFee =
|
||||||
var pool = Number(config.pool_bps || 0) / 100;
|
(Number(config.fee_bps || 0) +
|
||||||
|
Number(config.pool_bps || 0)) /
|
||||||
|
100;
|
||||||
setText(
|
setText(
|
||||||
document.querySelector('.fee-note'),
|
document.querySelector('.fee-note'),
|
||||||
i18nFormat(
|
i18nFormat(
|
||||||
'diceMatch.configFeeNote',
|
'diceMatch.configFeeNote',
|
||||||
'Fee {fee}% · Pool {pool}%',
|
'Fee {fee}%',
|
||||||
{
|
{
|
||||||
fee: fee,
|
fee: totalFee,
|
||||||
pool: pool,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -2734,6 +2943,7 @@
|
|||||||
'is-matching',
|
'is-matching',
|
||||||
'is-countdown',
|
'is-countdown',
|
||||||
'is-victory',
|
'is-victory',
|
||||||
|
'is-self-win-compare',
|
||||||
'has-scores'
|
'has-scores'
|
||||||
);
|
);
|
||||||
setCountdownNumber('');
|
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) {
|
function renderMatchParticipants(match) {
|
||||||
var self = findSelfParticipant(match);
|
var self = findSelfParticipant(match);
|
||||||
var opponent = findOpponentParticipant(match, self);
|
var opponent = findOpponentParticipant(match, self);
|
||||||
@ -3064,12 +3290,18 @@
|
|||||||
stopRollEffects();
|
stopRollEffects();
|
||||||
renderMatchParticipants(match);
|
renderMatchParticipants(match);
|
||||||
frame.classList.add('has-scores');
|
frame.classList.add('has-scores');
|
||||||
|
var drawReroll = isDrawReroll(match);
|
||||||
|
var selfWinCompare =
|
||||||
|
!drawReroll && isSelfWinner(match);
|
||||||
setPhaseText(
|
setPhaseText(
|
||||||
isDrawReroll(match)
|
selfWinCompare
|
||||||
? i18nText('diceMatch.draw', 'Draw')
|
? ''
|
||||||
: i18nText('diceMatch.compare', 'Compare')
|
: drawReroll
|
||||||
|
? i18nText('diceMatch.draw', 'Draw')
|
||||||
|
: i18nText('diceMatch.compare', 'Compare')
|
||||||
);
|
);
|
||||||
if (isDrawReroll(match)) {
|
setSelfWinCompare(selfWinCompare);
|
||||||
|
if (drawReroll) {
|
||||||
return delay(2000).then(function () {
|
return delay(2000).then(function () {
|
||||||
if (sequence !== roundSequence) return;
|
if (sequence !== roundSequence) return;
|
||||||
rollAndAnimate(sequence);
|
rollAndAnimate(sequence);
|
||||||
|
|||||||