feat(player): refresh dark gold betting theme
- add generated dark-gold WebP assets for PC/mobile backgrounds, cards, dialogs, tabs, mailbox, VS and bet buttons - apply the new assets across player home, match detail, outright cards, modals, wallet/profile/detail screens and bet slip controls - simplify confirm dialogs and reuse bet button resources for normal/pressed states - hide mobile announcement center, tighten mobile bet list layout, and remove extra dark page backgrounds/scrollbar styling - cache and deduplicate outright market loading to speed up repeated tab/detail navigation - document player image-generation constraints in AGENTS.md
@@ -90,6 +90,14 @@
|
||||
- 管理端切页慢的分析与优化任务见 `docs/admin-page-switch-performance.md`。
|
||||
- **不要**把 `apps/player/dist/` 提交进 Git;生产由 Docker 构建生成静态资源。
|
||||
|
||||
### 玩家端主题生图约束
|
||||
|
||||
- 生成 `apps/player` 主题图片资源时,必须先查看并参考现有同场景资源,优先保持当前暗金足球主题一致,不要另起一套风格。
|
||||
- PC 首页/赛事详情/弹窗类赛事面板应优先对齐 `apps/player/src/assets/images/generated/pc-featured-match-bg-v1.webp`:暗金球场、中央 V 型光线、薄金色边框、低亮度背景、内容区域干净。
|
||||
- 弹窗资源必须是“带完整边框的面板图”,不能只是普通背景图;四边边框要在图片内完整可见,中心区域要足够暗且简洁,保证文字可读。
|
||||
- 避免生成过度写实、照片质感过重、厚重金属、强眩光、烟雾、人物、旗帜、logo、文字、数字;也不要生成与主题不一致的纯矢量科幻线框风。
|
||||
- 生成资源默认输出 WebP,控制体积,文件放在 `apps/player/src/assets/images/generated/`,命名带清晰场景和版本号(如 `modal-gold-frame-v4.webp`)。
|
||||
|
||||
## i18n 改文案流程
|
||||
|
||||
支持语言均为 **zh-CN / en-US / ms-MY**(马来语)。改文案须**三语同步**,不要只改中文。
|
||||
@@ -209,4 +217,3 @@
|
||||
- 不要提交 `.env`、`.env.docker`、镜像 tar、`apps/*/dist/`。
|
||||
- 改 player 主题相关文件时,先确认当前分支是 `main` 还是 `theme-`*,避免用错 CSS 变量(如 theme-2 无 `--gold`)。
|
||||
- 管理端改列表页性能时,参考 `docs/admin-page-switch-performance.md` 任务清单,优先 KeepAlive + 缓存而非盲目减 API 字段。
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 86 KiB |
BIN
apps/player/src/assets/images/generated/modal-gold-frame-v1.webp
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
apps/player/src/assets/images/generated/modal-gold-frame-v2.webp
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
apps/player/src/assets/images/generated/modal-gold-frame-v3.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
apps/player/src/assets/images/generated/modal-gold-frame-v4.webp
Normal file
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 83 KiB |
BIN
apps/player/src/assets/images/generated/pc-global-bg-v1.webp
Normal file
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 30 KiB |
BIN
apps/player/src/assets/images/generated/pc-sidebar-bg-v1.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
apps/player/src/assets/images/generated/pc-top-nav-bg-v1.webp
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
apps/player/src/assets/images/generated/pc-wallet-banner-v1.webp
Normal file
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 114 KiB |
BIN
apps/player/src/assets/images/generated/vs-gold-alpha-v1.webp
Normal file
|
After Width: | Height: | Size: 25 KiB |
@@ -101,18 +101,33 @@ function onConfirm() {
|
||||
}
|
||||
|
||||
.confirm-modal {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 340px;
|
||||
background: linear-gradient(165deg, #1a1810 0%, #121212 45%, #0a0a0a 100%);
|
||||
border: 1px solid var(--border-gold-soft, rgba(200, 168, 78, 0.25));
|
||||
border-radius: 12px;
|
||||
padding: 22px 18px 16px;
|
||||
box-shadow: 0 0 24px rgba(212, 175, 55, 0.08);
|
||||
max-width: 374px;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(212, 175, 55, 0.08), transparent 38%),
|
||||
rgba(14, 14, 12, 0.96);
|
||||
border: 1px solid rgba(212, 175, 55, 0.34);
|
||||
border-radius: 8px;
|
||||
padding: 31px 28px 25px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.confirm-modal::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 24px;
|
||||
right: 24px;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, rgba(232, 200, 74, 0.65), transparent);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.confirm-title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 17px;
|
||||
margin: 0 0 12px;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
color: var(--gold, #c8a84e);
|
||||
text-align: center;
|
||||
@@ -120,7 +135,7 @@ function onConfirm() {
|
||||
}
|
||||
|
||||
.confirm-message {
|
||||
margin: 0 0 20px;
|
||||
margin: 0 0 26px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #c8c8c8;
|
||||
@@ -130,17 +145,17 @@ function onConfirm() {
|
||||
|
||||
.confirm-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
flex: 1;
|
||||
min-height: 44px;
|
||||
border-radius: 8px;
|
||||
min-height: 49px;
|
||||
border-radius: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s;
|
||||
transition: opacity 0.15s, filter 0.15s;
|
||||
}
|
||||
|
||||
.confirm-btn:disabled {
|
||||
@@ -149,20 +164,30 @@ function onConfirm() {
|
||||
}
|
||||
|
||||
.confirm-btn.cancel {
|
||||
border: 1px solid #333;
|
||||
background: transparent;
|
||||
color: #888;
|
||||
border: 0;
|
||||
background: url('../assets/images/generated/pc-betslip-btn-secondary-v1.webp') center / 100% 100% no-repeat;
|
||||
color: #9f9273;
|
||||
}
|
||||
|
||||
.confirm-btn.confirm {
|
||||
border: none;
|
||||
background: linear-gradient(135deg, #d4a017, #e8c84a);
|
||||
color: #1a1a1a;
|
||||
background: url('../assets/images/generated/pc-betslip-btn-primary-v1.webp') center / 100% 100% no-repeat;
|
||||
color: #1b1200;
|
||||
}
|
||||
|
||||
.confirm-btn.confirm.danger {
|
||||
background: linear-gradient(135deg, #c0392b, #e74c3c);
|
||||
color: #fff;
|
||||
.confirm-btn.cancel:not(:disabled):hover,
|
||||
.confirm-btn.confirm:not(:disabled):hover {
|
||||
filter: brightness(1.06);
|
||||
}
|
||||
|
||||
.confirm-btn.cancel:not(:disabled):active {
|
||||
background-image: url('../assets/images/generated/pc-betslip-btn-secondary-pressed-v1.webp');
|
||||
filter: brightness(0.96);
|
||||
}
|
||||
|
||||
.confirm-btn.confirm:not(:disabled):active {
|
||||
background-image: url('../assets/images/generated/pc-betslip-btn-primary-pressed-v1.webp');
|
||||
filter: brightness(0.98);
|
||||
}
|
||||
|
||||
.confirm-fade-enter-active,
|
||||
|
||||
@@ -317,47 +317,61 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.fab-btn {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
border: none;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
background: url('../assets/images/generated/floating-mailbox-fab-v1.webp') center / cover no-repeat;
|
||||
color: var(--primary-light);
|
||||
border: 0;
|
||||
box-shadow:
|
||||
0 8px 22px rgba(0, 0, 0, 0.42),
|
||||
0 0 16px rgba(212, 175, 55, 0.12);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, background 0.2s;
|
||||
transition: transform 0.2s, box-shadow 0.2s, color 0.2s, filter 0.2s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fab-btn:hover {
|
||||
transform: scale(1.05);
|
||||
background: var(--primary-light);
|
||||
transform: translateY(-1px);
|
||||
color: #fff1a8;
|
||||
filter: brightness(1.08);
|
||||
box-shadow:
|
||||
0 10px 26px rgba(0, 0, 0, 0.5),
|
||||
0 0 20px rgba(212, 175, 55, 0.18);
|
||||
}
|
||||
|
||||
.fab-active {
|
||||
transform: scale(0.95);
|
||||
transform: scale(0.96);
|
||||
color: #fff1a8;
|
||||
filter: brightness(0.94);
|
||||
}
|
||||
|
||||
.fab-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
top: -3px;
|
||||
right: -3px;
|
||||
background: var(--danger, #ff4d4f);
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
padding: 2px 6px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 5px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid var(--bg-card);
|
||||
border: 1px solid rgba(255, 255, 255, 0.9);
|
||||
line-height: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(255, 69, 58, 0.42);
|
||||
}
|
||||
|
||||
.popup-panel {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import MatchBetCard from './MatchBetCard.vue';
|
||||
import saishiImg from '../assets/images/saishi.webp';
|
||||
import cardBg from '../assets/images/card-bg.webp';
|
||||
import saishiImg from '../assets/images/generated/saishi-gold-trophy-v1.webp';
|
||||
import cardBg from '../assets/images/generated/pc-match-list-card-bg-v1.webp';
|
||||
|
||||
const matchCardBg = `url(${cardBg})`;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ function continueBrowsing() {
|
||||
<div v-if="auth.loginPromptVisible" class="login-overlay" @click.self="continueBrowsing">
|
||||
<div class="login-modal">
|
||||
<button type="button" class="close-btn" :aria-label="t('auth.continue_browsing')" @click="continueBrowsing">
|
||||
✕
|
||||
�
|
||||
</button>
|
||||
<h2 class="login-title">{{ t('auth.login_required') }}</h2>
|
||||
<p class="login-hint">{{ t('auth.login_hint') }}</p>
|
||||
@@ -64,10 +64,13 @@ function continueBrowsing() {
|
||||
position: relative;
|
||||
width: 90%;
|
||||
max-width: 360px;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid var(--border-gold-soft, rgba(200, 168, 78, 0.25));
|
||||
border-radius: 12px;
|
||||
padding: 28px 24px 24px;
|
||||
background:
|
||||
linear-gradient(rgba(16, 14, 10, 0.12), rgba(16, 14, 10, 0.16)),
|
||||
url('../assets/images/generated/modal-gold-frame-v4.webp') center / 102% 102% no-repeat;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 36px 30px 30px;
|
||||
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55), 0 0 24px rgba(212, 175, 55, 0.1);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
|
||||
@@ -9,6 +9,10 @@ import { useBetSlipStore } from '../stores/betSlip';
|
||||
import { useDesktopBetPopover } from '../composables/useDesktopBetPopover';
|
||||
import { resolveSelectionLabel } from '../utils/selectionLabel';
|
||||
import MarketSelectionsPanel from './match-detail/MarketSelectionsPanel.vue';
|
||||
import vsImg from '../assets/images/generated/vs-gold-alpha-v1.webp';
|
||||
import matchListCardBg from '../assets/images/generated/pc-match-list-card-bg-v1.webp';
|
||||
|
||||
const matchCardBg = `url(${matchListCardBg})`;
|
||||
|
||||
const props = defineProps<{
|
||||
match: {
|
||||
@@ -229,7 +233,9 @@ function goMatchDetail() {
|
||||
<div class="center-col">
|
||||
<span class="kickoff">{{ kickoffText }}</span>
|
||||
<span v-if="phase !== 'open' && liveScoreText" class="live-score">{{ liveScoreText }}</span>
|
||||
<span v-else class="vs">VS</span>
|
||||
<span v-else class="vs">
|
||||
<img :src="vsImg" alt="VS" class="vs-img" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="team">
|
||||
@@ -294,9 +300,9 @@ function goMatchDetail() {
|
||||
<style scoped>
|
||||
.match-card {
|
||||
position: relative;
|
||||
background: linear-gradient(180deg, #1a1a1a 0%, #0d0d0d 100%);
|
||||
border: 1px solid rgba(140, 140, 140, 0.35);
|
||||
border-radius: 6px;
|
||||
background: v-bind(matchCardBg) center / 100% 100% no-repeat;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 10px 8px 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -312,7 +318,7 @@ function goMatchDetail() {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, rgba(100, 100, 100, 0.08) 0%, transparent 50%, rgba(80, 80, 80, 0.05) 100%);
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.2));
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
@@ -412,13 +418,21 @@ function goMatchDetail() {
|
||||
}
|
||||
|
||||
.vs {
|
||||
font-size: 22px;
|
||||
font-weight: 900;
|
||||
color: #fff;
|
||||
letter-spacing: 0.08em;
|
||||
width: 48px;
|
||||
height: 28px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
|
||||
transition: font-size 0.25s ease, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), text-shadow 0.3s ease;
|
||||
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), filter 0.3s ease;
|
||||
}
|
||||
|
||||
.vs-img {
|
||||
width: 46px;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.9));
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
|
||||
@@ -36,7 +36,7 @@ function confirm() {
|
||||
<Teleport to="body">
|
||||
<div v-if="open" class="overlay" @click.self="close">
|
||||
<div class="modal" role="dialog" aria-modal="true" :aria-label="t('profile.avatar')">
|
||||
<button type="button" class="close-x" :aria-label="t('bet.cancel')" @click="close">✕</button>
|
||||
<button type="button" class="close-x" :aria-label="t('bet.cancel')" @click="close">✕</button>
|
||||
<h3 class="title">{{ t('profile.avatar') }}</h3>
|
||||
<PlayerAvatarPicker v-model="draft" />
|
||||
<div class="actions">
|
||||
@@ -69,11 +69,13 @@ function confirm() {
|
||||
max-width: 360px;
|
||||
max-height: 85vh;
|
||||
overflow-y: auto;
|
||||
background: linear-gradient(165deg, #1a1810 0%, #121212 45%, #0a0a0a 100%);
|
||||
border: 1px solid var(--border-gold-soft);
|
||||
border-radius: var(--radius);
|
||||
padding: 16px 14px 14px;
|
||||
box-shadow: var(--shadow), 0 0 24px rgba(212, 175, 55, 0.08);
|
||||
background:
|
||||
linear-gradient(rgba(16, 14, 10, 0.12), rgba(16, 14, 10, 0.16)),
|
||||
url('../assets/images/generated/modal-gold-frame-v4.webp') center / 102% 102% no-repeat;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 28px 24px 22px;
|
||||
box-shadow: var(--shadow), 0 0 24px rgba(212, 175, 55, 0.12);
|
||||
}
|
||||
|
||||
.close-x {
|
||||
|
||||
@@ -1181,7 +1181,9 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: var(--desktop-sidebar-panel-bg);
|
||||
background:
|
||||
linear-gradient(rgba(14, 12, 9, 0.58), rgba(14, 12, 9, 0.68)),
|
||||
url('../../assets/images/generated/pc-betslip-sidebar-bg-v1.webp') no-repeat center / cover;
|
||||
}
|
||||
|
||||
.panel-head {
|
||||
@@ -1233,14 +1235,22 @@ onUnmounted(() => {
|
||||
flex: 0 0 20%;
|
||||
min-width: 0;
|
||||
padding: 10px 4px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: url('../../assets/images/generated/pc-betslip-btn-secondary-v1.webp') center / 100% 100% no-repeat;
|
||||
color: var(--primary-light);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clear-slip-btn:hover {
|
||||
color: var(--primary-light);
|
||||
border-color: var(--border-gold-soft);
|
||||
background: rgba(212, 175, 55, 0.06);
|
||||
color: #fff0a8;
|
||||
filter: brightness(1.08);
|
||||
}
|
||||
|
||||
.clear-slip-btn.foot-clear:active {
|
||||
background-image: url('../../assets/images/generated/pc-betslip-btn-secondary-pressed-v1.webp');
|
||||
filter: brightness(0.96);
|
||||
}
|
||||
|
||||
.balance-info {
|
||||
@@ -1806,17 +1816,26 @@ onUnmounted(() => {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
background: linear-gradient(180deg, #F0D875 0%, #D4AF37 100%);
|
||||
color: #3D2800;
|
||||
border-radius: 0;
|
||||
background: url('../../assets/images/generated/pc-betslip-btn-primary-v1.webp') center / 100% 100% no-repeat;
|
||||
color: #1b1200;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
transition: opacity 0.2s;
|
||||
transition: opacity 0.2s, filter 0.15s;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.submit-btn:not(:disabled):hover {
|
||||
filter: brightness(1.06);
|
||||
}
|
||||
|
||||
.submit-btn:not(:disabled):active {
|
||||
background-image: url('../../assets/images/generated/pc-betslip-btn-primary-pressed-v1.webp');
|
||||
filter: brightness(0.98);
|
||||
}
|
||||
|
||||
.submit-btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
|
||||
@@ -83,16 +83,16 @@ function statusLabel(status: string) {
|
||||
}
|
||||
|
||||
const SEL_TRANS: Record<string, Record<string, string>> = {
|
||||
'主胜': { 'en-US': 'Home Win', 'ms-MY': 'Rumah Menang' },
|
||||
'客胜': { 'en-US': 'Away Win', 'ms-MY': 'Tandang Menang' },
|
||||
'和局': { 'en-US': 'Draw', 'ms-MY': 'Seri' },
|
||||
'主': { 'en-US': 'Home', 'ms-MY': 'Rumah' },
|
||||
'客': { 'en-US': 'Away', 'ms-MY': 'Tandang' },
|
||||
'大': { 'en-US': 'Over', 'ms-MY': 'Atas' },
|
||||
'小': { 'en-US': 'Under', 'ms-MY': 'Bawah' },
|
||||
'单': { 'en-US': 'Odd', 'ms-MY': 'Ganjil' },
|
||||
'双': { 'en-US': 'Even', 'ms-MY': 'Genap' },
|
||||
'冠军': { 'en-US': 'Winner', 'ms-MY': 'Juara' },
|
||||
'\u4e3b\u80dc': { 'en-US': 'Home Win', 'ms-MY': 'Rumah Menang' },
|
||||
'\u5ba2\u80dc': { 'en-US': 'Away Win', 'ms-MY': 'Tandang Menang' },
|
||||
'\u548c\u5c40': { 'en-US': 'Draw', 'ms-MY': 'Seri' },
|
||||
'\u4e3b': { 'en-US': 'Home', 'ms-MY': 'Rumah' },
|
||||
'\u5ba2': { 'en-US': 'Away', 'ms-MY': 'Tandang' },
|
||||
'\u5927': { 'en-US': 'Over', 'ms-MY': 'Atas' },
|
||||
'\u5c0f': { 'en-US': 'Under', 'ms-MY': 'Bawah' },
|
||||
'\u5355': { 'en-US': 'Odd', 'ms-MY': 'Ganjil' },
|
||||
'\u53cc': { 'en-US': 'Even', 'ms-MY': 'Genap' },
|
||||
'\u51a0\u519b': { 'en-US': 'Winner', 'ms-MY': 'Juara' },
|
||||
};
|
||||
|
||||
function translateSel(name: string): string {
|
||||
@@ -115,8 +115,8 @@ function translateSel(name: string): string {
|
||||
<div v-if="visible" class="modal-overlay" @click.self="close">
|
||||
<div class="modal-container" role="dialog" aria-modal="true">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">{{ t('bet.detail_title') || '注单详情' }}</span>
|
||||
<button type="button" class="modal-close" @click="close">✕</button>
|
||||
<span class="modal-title">{{ t('bet.detail_title') }}</span>
|
||||
<button type="button" class="modal-close" @click="close">✕</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
@@ -132,36 +132,36 @@ function translateSel(name: string): string {
|
||||
<template v-else-if="detail">
|
||||
<div class="info-rows">
|
||||
<div class="info-row">
|
||||
<span class="row-label">{{ t('bet.bet_no') || '注单号' }}</span>
|
||||
<span class="row-label">{{ t('bet.bet_no') }}</span>
|
||||
<span class="row-val mono">{{ detail.betNo }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="row-label">{{ t('common.status') || '状态' }}</span>
|
||||
<span class="row-label">{{ t('common.status') }}</span>
|
||||
<span class="row-val">
|
||||
<span class="st-badge" :class="statusClass(detail.status)">{{ statusLabel(detail.status) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="row-label">{{ t('bet.stake') || '投注额' }}</span>
|
||||
<span class="row-label">{{ t('bet.stake') }}</span>
|
||||
<span class="row-val">{{ detail.stake }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="row-label">{{ t('bet.potential_payout') || '预计派彩' }}</span>
|
||||
<span class="row-label">{{ t('bet.potential_payout') }}</span>
|
||||
<span class="row-val gold">{{ detail.potentialReturn || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="row-label">{{ t('bet.placed_at') || '下注时间' }}</span>
|
||||
<span class="row-label">{{ t('bet.placed_at') }}</span>
|
||||
<span class="row-val muted">{{ detail.placedAt ? new Date(detail.placedAt).toLocaleString() : '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sel-section">
|
||||
<div class="sel-section-title">{{ t('bet.selections') || '投注选项' }}</div>
|
||||
<div class="sel-section-title">{{ t('bet.selections') }}</div>
|
||||
<div v-for="(leg, idx) in detail.legs" :key="idx" class="sel-item">
|
||||
<div class="sel-item-main">
|
||||
<span class="sel-item-match">{{ leg.matchTitle || t('bet.match') }}</span>
|
||||
<span class="sel-item-pick">
|
||||
{{ leg.marketLabel || '' }} · {{ translateSel(leg.selectionName || '') }}
|
||||
{{ leg.marketLabel || '' }} - {{ translateSel(leg.selectionName || '') }}
|
||||
</span>
|
||||
<span v-if="leg.score?.ft" class="sel-item-score">{{ t('history.ft') }}: {{ leg.score.ft }}</span>
|
||||
</div>
|
||||
@@ -170,7 +170,7 @@ function translateSel(name: string): string {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-else class="modal-empty">{{ t('common.not_found') || '未找到记录' }}</div>
|
||||
<div v-else class="modal-empty">{{ t('common.not_found') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -191,15 +191,19 @@ function translateSel(name: string): string {
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--desktop-sidebar-bg, rgba(20, 20, 20, 0.95));
|
||||
border: 1px solid var(--desktop-border, #262626);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5);
|
||||
padding: 20px 22px 22px;
|
||||
background:
|
||||
linear-gradient(rgba(15, 14, 11, 0.18), rgba(15, 14, 11, 0.2)),
|
||||
url('../../assets/images/generated/modal-gold-frame-v4.webp') center / 102% 102% no-repeat;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.58), 0 0 28px rgba(212, 175, 55, 0.12);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -207,8 +211,8 @@ function translateSel(name: string): string {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--desktop-border, #262626);
|
||||
padding: 2px 0 12px;
|
||||
border-bottom: 1px solid rgba(212, 175, 55, 0.16);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -275,9 +279,7 @@ function translateSel(name: string): string {
|
||||
}
|
||||
|
||||
/* Info rows */
|
||||
.info-rows {
|
||||
border-bottom: 1px solid var(--desktop-border, #262626);
|
||||
}
|
||||
.info-rows { border-bottom: 1px solid rgba(212, 175, 55, 0.12); }
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
@@ -324,7 +326,7 @@ function translateSel(name: string): string {
|
||||
.st-default { background: rgba(100,100,100,0.1); color: #888; }
|
||||
|
||||
/* Selections */
|
||||
.sel-section { padding: 12px 16px 16px; }
|
||||
.sel-section { padding: 12px 0 2px; }
|
||||
|
||||
.sel-section-title {
|
||||
font-size: 12px;
|
||||
|
||||
@@ -47,7 +47,9 @@ watch(
|
||||
width: var(--desktop-right-betslip-w);
|
||||
min-width: 0;
|
||||
border-left: 1px solid var(--border);
|
||||
background: var(--desktop-sidebar-panel-bg);
|
||||
background:
|
||||
linear-gradient(rgba(14, 12, 9, 0.58), rgba(14, 12, 9, 0.68)),
|
||||
url('../../assets/images/generated/pc-betslip-sidebar-bg-v1.webp') no-repeat center / cover;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { OutrightEvent, OutrightSelection } from '../outright/OutrightEventSection.vue';
|
||||
import saishiImg from '../../assets/images/saishi.webp';
|
||||
import saishiImg from '../../assets/images/generated/saishi-gold-trophy-v1.webp';
|
||||
import outrightListCardBg from '../../assets/images/generated/pc-match-list-card-bg-v1.webp';
|
||||
|
||||
const outrightCardBg = `url(${outrightListCardBg})`;
|
||||
|
||||
const props = defineProps<{
|
||||
event: OutrightEvent;
|
||||
@@ -107,9 +110,9 @@ const topSelections = computed(() => {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 140px;
|
||||
border: 1px solid rgba(140, 140, 140, 0.35);
|
||||
border-radius: 6px;
|
||||
background: linear-gradient(180deg, #1a1a1a 0%, #0d0d0d 100%);
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: v-bind(outrightCardBg) center / 100% 100% no-repeat;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -82,8 +82,8 @@ function txLabel(tx: TxDetail): string {
|
||||
<div v-if="visible" class="modal-overlay" @click.self="close">
|
||||
<div class="modal-container" role="dialog" aria-modal="true">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">{{ t('wallet.transaction_detail') || '交易详情' }}</span>
|
||||
<button type="button" class="modal-close" @click="close">✕</button>
|
||||
<span class="modal-title">{{ t('wallet.transaction_detail') }}</span>
|
||||
<button type="button" class="modal-close" @click="close">✕</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
@@ -99,45 +99,45 @@ function txLabel(tx: TxDetail): string {
|
||||
<template v-else-if="detail">
|
||||
<div class="info-rows">
|
||||
<div class="info-row">
|
||||
<span class="row-label">{{ t('wallet.type') || '类型' }}</span>
|
||||
<span class="row-label">{{ t('wallet.type') }}</span>
|
||||
<span class="row-val">{{ txLabel(detail) }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="row-label">{{ t('wallet.amount') || '金额' }}</span>
|
||||
<span class="row-label">{{ t('wallet.amount') }}</span>
|
||||
<span class="row-val mono" :class="parseFloat(detail.amount) >= 0 ? 'pos' : 'neg'">{{ formatMoney(detail.amount, locale) }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="row-label">{{ t('wallet.note') || '备注' }}</span>
|
||||
<span class="row-label">{{ t('wallet.note') }}</span>
|
||||
<span class="row-val">{{ txSummaryLabel(detail, t) || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="row-label">{{ t('wallet.balance_before') || '变动前余额' }}</span>
|
||||
<span class="row-label">{{ t('wallet.balance_before') }}</span>
|
||||
<span class="row-val mono">{{ formatMoney(detail.balanceBefore, locale) }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="row-label">{{ t('wallet.balance_after') || '变动后余额' }}</span>
|
||||
<span class="row-label">{{ t('wallet.balance_after') }}</span>
|
||||
<span class="row-val mono">{{ formatMoney(detail.balanceAfter, locale) }}</span>
|
||||
</div>
|
||||
<div v-if="parseFloat(detail.frozenBefore || '0') > 0 || parseFloat(detail.frozenAfter || '0') > 0" class="info-row">
|
||||
<span class="row-label">{{ t('wallet.frozen_before') || '冻结前' }}</span>
|
||||
<span class="row-label">{{ t('wallet.frozen_before') }}</span>
|
||||
<span class="row-val mono">{{ formatMoney(detail.frozenBefore, locale) }}</span>
|
||||
</div>
|
||||
<div v-if="parseFloat(detail.frozenBefore || '0') > 0 || parseFloat(detail.frozenAfter || '0') > 0" class="info-row">
|
||||
<span class="row-label">{{ t('wallet.frozen_after') || '冻结后' }}</span>
|
||||
<span class="row-label">{{ t('wallet.frozen_after') }}</span>
|
||||
<span class="row-val mono">{{ formatMoney(detail.frozenAfter, locale) }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="row-label">{{ t('wallet.time') || '时间' }}</span>
|
||||
<span class="row-label">{{ t('wallet.time') }}</span>
|
||||
<span class="row-val muted">{{ new Date(detail.createdAt).toLocaleString() }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="row-label">{{ t('wallet.transaction_id') || '流水号' }}</span>
|
||||
<span class="row-label">{{ t('wallet.transaction_id') }}</span>
|
||||
<span class="row-val mono muted">{{ detail.transactionId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-else class="modal-empty">{{ t('common.not_found') || '未找到记录' }}</div>
|
||||
<div v-else class="modal-empty">{{ t('common.not_found') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,15 +158,19 @@ function txLabel(tx: TxDetail): string {
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 460px;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--desktop-sidebar-bg, rgba(20, 20, 20, 0.95));
|
||||
border: 1px solid var(--desktop-border, #262626);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5);
|
||||
padding: 20px 22px 22px;
|
||||
background:
|
||||
linear-gradient(rgba(15, 14, 11, 0.18), rgba(15, 14, 11, 0.2)),
|
||||
url('../../assets/images/generated/modal-gold-frame-v4.webp') center / 102% 102% no-repeat;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.58), 0 0 28px rgba(212, 175, 55, 0.12);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -174,8 +178,8 @@ function txLabel(tx: TxDetail): string {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--desktop-border, #262626);
|
||||
padding: 2px 0 12px;
|
||||
border-bottom: 1px solid rgba(212, 175, 55, 0.16);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ function onSubmitClick() {
|
||||
matchName: props.pick.eventTitle,
|
||||
marketId: '',
|
||||
marketName: props.pick.eventTitle,
|
||||
selectionName: `${props.pick.teamName} · ${props.pick.eventTitle}`,
|
||||
selectionName: `${props.pick.teamName} - ${props.pick.eventTitle}`,
|
||||
odds: oddsNum.value,
|
||||
marketType: 'OUTRIGHT',
|
||||
lineValue: null,
|
||||
@@ -304,7 +304,7 @@ function formatOdds(odds: string) {
|
||||
<template>
|
||||
<div v-if="open && pick" class="overlay" @click.self="close">
|
||||
<div class="modal" role="dialog" aria-modal="true">
|
||||
<button type="button" class="close-x" :aria-label="t('bet.cancel')" @click="close">✕</button>
|
||||
<button type="button" class="close-x" :aria-label="t('bet.cancel')" @click="close">✕</button>
|
||||
|
||||
<template v-if="step === 'form'">
|
||||
<p class="hint">{{ t('bet.outright_enter_stake') }}</p>
|
||||
@@ -321,7 +321,7 @@ function formatOdds(odds: string) {
|
||||
}"
|
||||
>
|
||||
<template v-if="oddsDelta && !oddsDelta.suspended">
|
||||
@ {{ oddsDelta.oldOdds.toFixed(2) }} → {{ oddsDelta.newOdds.toFixed(2) }}
|
||||
@ {{ oddsDelta.oldOdds.toFixed(2) }} -> {{ oddsDelta.newOdds.toFixed(2) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
@ {{ formatOdds(currentOdds || pick.odds) }}
|
||||
@@ -357,7 +357,7 @@ function formatOdds(odds: string) {
|
||||
</div>
|
||||
|
||||
<p class="est-return">
|
||||
{{ t('history.est_return') }}:<span class="est-value">{{ estReturnText }}</span>
|
||||
{{ t('history.est_return') }}: <span class="est-value">{{ estReturnText }}</span>
|
||||
</p>
|
||||
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
@@ -368,7 +368,7 @@ function formatOdds(odds: string) {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-confirm btn-gold-outline"
|
||||
class="btn-confirm"
|
||||
:disabled="loading || stake <= 0 || hasSuspendedSelection"
|
||||
@click="onSubmitClick"
|
||||
>
|
||||
@@ -378,7 +378,7 @@ function formatOdds(odds: string) {
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="success-icon">✓</div>
|
||||
<div class="success-icon">✓</div>
|
||||
<h3 class="success-title">{{ t('bet.outright_success') }}</h3>
|
||||
|
||||
<div class="hero hero--compact">
|
||||
@@ -400,7 +400,7 @@ function formatOdds(odds: string) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-done btn-gold-outline" @click="close">
|
||||
<button type="button" class="btn-done" @click="close">
|
||||
{{ t('bet.outright_done') }}
|
||||
</button>
|
||||
</template>
|
||||
@@ -437,12 +437,12 @@ function formatOdds(odds: string) {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
background: linear-gradient(165deg, #1a1810 0%, #121212 45%, #0a0a0a 100%);
|
||||
border: 1px solid var(--border-gold-soft);
|
||||
border-radius: var(--radius);
|
||||
padding: 18px 16px 16px;
|
||||
background: url('../../assets/images/generated/outright-bet-modal-frame-v2.webp') center / 100% 100% no-repeat;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 30px 24px 24px;
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow), 0 0 24px rgba(212, 175, 55, 0.08);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.close-x {
|
||||
@@ -644,22 +644,36 @@ function formatOdds(odds: string) {
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid var(--border);
|
||||
height: 46px;
|
||||
padding: 10px 12px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: url('../../assets/images/generated/pc-betslip-btn-secondary-v1.webp') center / 100% 100% no-repeat;
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.btn-cancel:active {
|
||||
background-image: url('../../assets/images/generated/pc-betslip-btn-secondary-pressed-v1.webp');
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
height: 46px;
|
||||
padding: 10px 12px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: url('../../assets/images/generated/pc-betslip-btn-primary-v1.webp') center / 100% 100% no-repeat;
|
||||
color: #100d04;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.btn-confirm:active {
|
||||
background-image: url('../../assets/images/generated/pc-betslip-btn-primary-pressed-v1.webp');
|
||||
}
|
||||
|
||||
.btn-cancel:disabled,
|
||||
.btn-confirm:disabled {
|
||||
opacity: 0.45;
|
||||
}
|
||||
@@ -708,9 +722,17 @@ function formatOdds(odds: string) {
|
||||
|
||||
.btn-done {
|
||||
width: 100%;
|
||||
padding: 11px;
|
||||
border-radius: 6px;
|
||||
height: 46px;
|
||||
padding: 11px 12px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: url('../../assets/images/generated/pc-betslip-btn-primary-v1.webp') center / 100% 100% no-repeat;
|
||||
color: #100d04;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.btn-done:active {
|
||||
background-image: url('../../assets/images/generated/pc-betslip-btn-primary-pressed-v1.webp');
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import OutrightOptionCard from './OutrightOptionCard.vue';
|
||||
import saishiImg from '../../assets/images/saishi.webp';
|
||||
import cardBg from '../../assets/images/card-bg.webp';
|
||||
import saishiImg from '../../assets/images/generated/saishi-gold-trophy-v1.webp';
|
||||
import cardBg from '../../assets/images/generated/pc-match-list-card-bg-v1.webp';
|
||||
|
||||
const matchCardBg = `url(${cardBg})`;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import OutrightEventSection, {
|
||||
} from './OutrightEventSection.vue';
|
||||
import OutrightBetModal, { type OutrightPick } from './OutrightBetModal.vue';
|
||||
import { useAuthStore } from '../../stores/auth';
|
||||
import emptyMatchesImg from '../../assets/images/empty-matches.svg';
|
||||
import emptyMatchesImg from '../../assets/images/generated/empty-matches-stadium-v1.webp';
|
||||
import GoldSpinner from '../../components/GoldSpinner.vue';
|
||||
import { useOutrightEvents } from '../../composables/useOutrightEvents';
|
||||
|
||||
@@ -159,16 +159,25 @@ function closeModal() {
|
||||
|
||||
.state,
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
padding: 48px 20px;
|
||||
padding: 34px 20px 44px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
margin-bottom: 14px;
|
||||
width: min(270px, 82vw);
|
||||
aspect-ratio: 16 / 9;
|
||||
height: auto;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
opacity: 0.86;
|
||||
mask-image: linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import { ref } from 'vue';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import api from '../api';
|
||||
import type { OutrightEvent } from '../components/outright/OutrightEventSection.vue';
|
||||
import type { OutrightSelection } from '../components/outright/OutrightEventSection.vue';
|
||||
import { useOnLocaleChange } from './useOnLocaleChange';
|
||||
|
||||
export function useOutrightEvents() {
|
||||
const { t } = useI18n();
|
||||
const loading = ref(true);
|
||||
const CACHE_TTL_MS = 8000;
|
||||
const loading = ref(false);
|
||||
const loadError = ref('');
|
||||
const events = ref<OutrightEvent[]>([]);
|
||||
let lastLoadedAt = 0;
|
||||
let inFlight: Promise<OutrightEvent[]> | null = null;
|
||||
|
||||
type LoadOptions = {
|
||||
silent?: boolean;
|
||||
force?: boolean;
|
||||
};
|
||||
|
||||
export function useOutrightEvents() {
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
function mergeOddsOnly(fresh: OutrightEvent[]) {
|
||||
const freshMap = new Map<string, OutrightEvent>();
|
||||
@@ -30,15 +38,34 @@ export function useOutrightEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
async function load(options?: { silent?: boolean }) {
|
||||
async function load(options?: LoadOptions) {
|
||||
const hadData = events.value.length > 0;
|
||||
if (!options?.force && hadData && Date.now() - lastLoadedAt < CACHE_TTL_MS) {
|
||||
loading.value = false;
|
||||
loadError.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (inFlight) {
|
||||
if (!hadData && !options?.silent) loading.value = true;
|
||||
try {
|
||||
await inFlight;
|
||||
} finally {
|
||||
if (!hadData && !options?.silent) loading.value = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hadData && !options?.silent) loading.value = true;
|
||||
loadError.value = '';
|
||||
try {
|
||||
const { data } = await api.get('/player/outrights');
|
||||
inFlight = api.get('/player/outrights').then(({ data }) => {
|
||||
const list = (data?.data ?? []) as OutrightEvent[];
|
||||
const fresh = list.filter((e) => e.selections?.length > 0);
|
||||
if (!hadData) {
|
||||
return list.filter((e) => e.selections?.length > 0);
|
||||
});
|
||||
const fresh = await inFlight;
|
||||
lastLoadedAt = Date.now();
|
||||
if (!hadData || options?.force) {
|
||||
events.value = fresh;
|
||||
} else {
|
||||
mergeOddsOnly(fresh);
|
||||
@@ -53,10 +80,19 @@ export function useOutrightEvents() {
|
||||
}
|
||||
} finally {
|
||||
if (!hadData && !options?.silent) loading.value = false;
|
||||
inFlight = null;
|
||||
}
|
||||
}
|
||||
|
||||
useOnLocaleChange(load);
|
||||
onMounted(() => {
|
||||
void load({ silent: events.value.length > 0 });
|
||||
});
|
||||
|
||||
watch(locale, (next, prev) => {
|
||||
if (prev && next !== prev) {
|
||||
void load({ silent: events.value.length > 0, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
loading,
|
||||
|
||||
@@ -261,7 +261,7 @@ watch(
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
min-height: var(--player-header-h);
|
||||
background: rgba(17, 17, 17, 0.94);
|
||||
background: rgba(29, 27, 22, 0.88);
|
||||
border-bottom: 1px solid var(--border);
|
||||
z-index: 120;
|
||||
}
|
||||
@@ -371,7 +371,7 @@ watch(
|
||||
box-sizing: border-box;
|
||||
min-height: calc(var(--player-bottom-nav-h) + var(--safe-bottom));
|
||||
padding-bottom: var(--safe-bottom);
|
||||
background: rgba(17, 17, 17, 0.96);
|
||||
background: rgba(29, 27, 22, 0.9);
|
||||
border-top: 1px solid var(--border);
|
||||
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.35);
|
||||
z-index: 100;
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
--secondary: #1A1A1A;
|
||||
--tertiary: #000000;
|
||||
--neutral: #8E8E93;
|
||||
--bg-body: #000000;
|
||||
--bg-card: rgba(20, 20, 20, 0.65);
|
||||
--bg-hover: rgba(34, 34, 34, 0.65);
|
||||
--bg-elevated: rgba(42, 42, 42, 0.65);
|
||||
--bg-body: #080806;
|
||||
--bg-card: rgba(33, 31, 25, 0.76);
|
||||
--bg-hover: rgba(47, 44, 35, 0.82);
|
||||
--bg-elevated: rgba(58, 54, 43, 0.86);
|
||||
--text: #FFFFFF;
|
||||
--text-muted: #8E8E93;
|
||||
--border: #333333;
|
||||
--text-muted: #B7B0A0;
|
||||
--border: rgba(255, 226, 156, 0.18);
|
||||
--border-gold: #D4AF37;
|
||||
--border-gold-soft: rgba(212, 175, 55, 0.28);
|
||||
--border-w: 1px;
|
||||
@@ -209,9 +209,9 @@ body {
|
||||
/* 保留 bg 位图;用 scroll 替代 fixed,减轻移动端滚动重绘(后续可换 WebP/AVIF) */
|
||||
background-color: var(--tertiary);
|
||||
background-image:
|
||||
radial-gradient(ellipse 120% 60% at 50% -10%, rgba(212, 175, 55, 0.14), transparent 55%),
|
||||
linear-gradient(rgba(0, 0, 0, 0.88), rgba(0, 0, 0, 0.88)),
|
||||
url('./assets/images/bg.webp');
|
||||
radial-gradient(ellipse 120% 60% at 50% -10%, rgba(246, 210, 108, 0.2), transparent 56%),
|
||||
linear-gradient(rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0.7)),
|
||||
url('./assets/images/generated/saishi-gold-trophy-v1.webp');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
@@ -223,7 +223,10 @@ body {
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
background-image: url('./assets/images/pcbg.png');
|
||||
background-image:
|
||||
radial-gradient(ellipse 90% 70% at 50% 0%, rgba(246, 210, 108, 0.14), transparent 58%),
|
||||
linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.58)),
|
||||
url('./assets/images/generated/pc-global-bg-v1.webp');
|
||||
background-attachment: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #000;
|
||||
background-image: url('../../assets/images/pcbg.png');
|
||||
background-color: #0d0b08;
|
||||
background-image:
|
||||
radial-gradient(ellipse 90% 68% at 50% 0%, rgba(246, 210, 108, 0.14), transparent 58%),
|
||||
linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.58)),
|
||||
url('../../assets/images/generated/pc-global-bg-v1.webp');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-attachment: fixed;
|
||||
@@ -21,7 +24,7 @@
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: radial-gradient(ellipse 80% 60% at 50% 0%, rgba(212, 175, 55, 0.03) 0%, transparent 60%);
|
||||
background: radial-gradient(ellipse 80% 60% at 50% 0%, rgba(255, 221, 128, 0.08) 0%, transparent 60%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
@@ -29,10 +32,8 @@
|
||||
.desktop-header-wrap {
|
||||
flex-shrink: 0;
|
||||
height: var(--desktop-header-h);
|
||||
background: rgba(14, 14, 14, 0.88);
|
||||
backdrop-filter: var(--blur-md);
|
||||
-webkit-backdrop-filter: var(--blur-md);
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: #12100c url('../../assets/images/generated/pc-top-nav-bg-v1.webp') center / 100% 100% no-repeat;
|
||||
border-bottom: 0;
|
||||
z-index: var(--z-header);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -45,7 +46,7 @@
|
||||
flex-shrink: 0;
|
||||
z-index: var(--z-sticky);
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: rgba(14, 14, 14, 0.85);
|
||||
background: rgba(26, 24, 19, 0.78);
|
||||
backdrop-filter: var(--blur-sm);
|
||||
-webkit-backdrop-filter: var(--blur-sm);
|
||||
}
|
||||
@@ -90,7 +91,7 @@
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: rgba(14, 14, 14, 0.85);
|
||||
background: rgba(26, 24, 19, 0.76);
|
||||
backdrop-filter: var(--blur-sm);
|
||||
-webkit-backdrop-filter: var(--blur-sm);
|
||||
}
|
||||
@@ -105,7 +106,7 @@
|
||||
|
||||
.desktop-betting-left {
|
||||
border-right: 1px solid var(--border);
|
||||
background: rgba(14, 14, 14, 0.6);
|
||||
background: rgba(26, 24, 19, 0.56);
|
||||
backdrop-filter: var(--blur-sm);
|
||||
-webkit-backdrop-filter: var(--blur-sm);
|
||||
overflow-y: auto;
|
||||
@@ -209,7 +210,7 @@
|
||||
.desktop-hub-right {
|
||||
overflow-y: auto;
|
||||
padding: var(--space-5);
|
||||
background: rgba(10, 10, 10, 0.3);
|
||||
background: rgba(18, 16, 12, 0.18);
|
||||
}
|
||||
|
||||
/* ── Auth Layout: Centered container ── */
|
||||
|
||||
@@ -12,20 +12,20 @@
|
||||
--primary: #D4AF37;
|
||||
--primary-dark: #A8841F;
|
||||
--primary-light: #F0D875;
|
||||
--secondary: #141414;
|
||||
--tertiary: #0A0A0A;
|
||||
--secondary: #1c1a15;
|
||||
--tertiary: #0f0d09;
|
||||
--neutral: #8E8E93;
|
||||
--bg-body: #050505;
|
||||
--bg-card: rgba(20, 20, 20, 0.92);
|
||||
--bg-card-hover: rgba(28, 28, 28, 0.95);
|
||||
--bg-hover: rgba(38, 38, 38, 0.95);
|
||||
--bg-elevated: rgba(45, 45, 45, 0.95);
|
||||
--bg-surface: rgba(16, 16, 16, 0.88);
|
||||
--bg-body: #0d0b08;
|
||||
--bg-card: rgba(32, 30, 24, 0.82);
|
||||
--bg-card-hover: rgba(44, 40, 31, 0.9);
|
||||
--bg-hover: rgba(52, 48, 38, 0.9);
|
||||
--bg-elevated: rgba(62, 57, 45, 0.92);
|
||||
--bg-surface: rgba(28, 26, 21, 0.8);
|
||||
--text: #FFFFFF;
|
||||
--text-secondary: #B8B8BD;
|
||||
--text-muted: #8E8E93;
|
||||
--border: #262626;
|
||||
--border-subtle: rgba(255, 255, 255, 0.06);
|
||||
--text-secondary: #D3CBB8;
|
||||
--text-muted: #B5AD9B;
|
||||
--border: rgba(255, 226, 156, 0.16);
|
||||
--border-subtle: rgba(255, 235, 185, 0.1);
|
||||
--border-gold: #D4AF37;
|
||||
--border-gold-soft: rgba(212, 175, 55, 0.25);
|
||||
--border-w: 1px;
|
||||
@@ -62,10 +62,10 @@
|
||||
--desktop-right-betslip-collapsed-w: 40px;
|
||||
|
||||
/* ── Desktop specific vars ── */
|
||||
--desktop-bg: #050505;
|
||||
--desktop-border: #262626;
|
||||
--desktop-sidebar-bg: rgba(20, 20, 20, 0.95);
|
||||
--desktop-sidebar-panel-bg: linear-gradient(rgba(16, 16, 16, 0.88), rgba(16, 16, 16, 0.88)), url('../../assets/images/cebian.webp') no-repeat center / cover;
|
||||
--desktop-bg: #0d0b08;
|
||||
--desktop-border: rgba(255, 226, 156, 0.16);
|
||||
--desktop-sidebar-bg: rgba(30, 28, 23, 0.82);
|
||||
--desktop-sidebar-panel-bg: linear-gradient(rgba(18, 16, 12, 0.52), rgba(18, 16, 12, 0.64)), url('../../assets/images/generated/pc-sidebar-bg-v1.webp') no-repeat center / cover;
|
||||
|
||||
/* ── Spacing Scale (8dp rhythm) ── */
|
||||
--space-1: 4px;
|
||||
|
||||
@@ -364,12 +364,19 @@ const showPhaseWatermark = computed(
|
||||
.receipt {
|
||||
position: relative;
|
||||
border-radius: var(--radius);
|
||||
background: rgba(18, 18, 18, 0.96);
|
||||
background:
|
||||
linear-gradient(rgba(18, 16, 12, 0.78), rgba(16, 14, 10, 0.86)),
|
||||
url('../assets/images/generated/detail-ledger-ticket-bg-v1.webp') center / cover no-repeat;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.receipt > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* result strip */
|
||||
.receipt-result {
|
||||
display: flex;
|
||||
@@ -533,7 +540,7 @@ const showPhaseWatermark = computed(
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 14px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
background: rgba(255, 242, 198, 0.045);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { usePlayerMatches } from '../composables/usePlayerMatches';
|
||||
import LeagueAccordionItem from '../components/LeagueAccordionItem.vue';
|
||||
import MatchBetCard from '../components/MatchBetCard.vue';
|
||||
import OutrightPanel from '../components/outright/OutrightPanel.vue';
|
||||
import emptyMatchesImg from '../assets/images/empty-matches.svg';
|
||||
import emptyMatchesImg from '../assets/images/generated/empty-matches-stadium-v1.webp';
|
||||
import { useOnLocaleChange } from '../composables/useOnLocaleChange';
|
||||
import GoldSpinner from '../components/GoldSpinner.vue';
|
||||
import { usePullToRefresh } from '../composables/usePullToRefresh';
|
||||
@@ -452,7 +452,6 @@ function goMatch(id: string) {
|
||||
.bet-page {
|
||||
margin: 0 -16px;
|
||||
padding-bottom: 8px;
|
||||
background: var(--bg-body);
|
||||
}
|
||||
|
||||
/* Tab panel toggle: avoid display:none to prevent browser releasing image resources */
|
||||
@@ -772,16 +771,25 @@ function goMatch(id: string) {
|
||||
.state,
|
||||
.placeholder,
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
padding: 48px 20px;
|
||||
padding: 34px 20px 44px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
margin-bottom: 14px;
|
||||
width: min(270px, 82vw);
|
||||
aspect-ratio: 16 / 9;
|
||||
height: auto;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
opacity: 0.86;
|
||||
mask-image: linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
import { onActivated, computed, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import emptyMatchesImg from '../assets/images/empty-matches.svg';
|
||||
import vsImg from '../assets/images/vs.png';
|
||||
import cardBg from '../assets/images/card-bg.webp';
|
||||
import emptyMatchesImg from '../assets/images/generated/empty-matches-stadium-v1.webp';
|
||||
import vsImg from '../assets/images/generated/vs-gold-alpha-v1.webp';
|
||||
import hotCardBg from '../assets/images/generated/match-card-bg-gold-stadium-v1.webp';
|
||||
import upcomingCardBg from '../assets/images/generated/match-card-bg-gold-tunnel-v1.webp';
|
||||
import BannerCarousel from '../components/BannerCarousel.vue';
|
||||
import HomeAnnouncementCard from '../components/HomeAnnouncementCard.vue';
|
||||
import { usePlayerHome } from '../composables/usePlayerHome';
|
||||
import TeamEmblem from '../components/TeamEmblem.vue';
|
||||
import GoldSpinner from '../components/GoldSpinner.vue';
|
||||
@@ -16,7 +16,8 @@ import type { PlayerHomeMatch } from '../composables/usePlayerHome';
|
||||
|
||||
type HotTab = 'hot' | 'upcoming';
|
||||
|
||||
const matchCardBg = `url(${cardBg})`;
|
||||
const hotMatchCardBg = `url(${hotCardBg})`;
|
||||
const upcomingMatchCardBg = `url(${upcomingCardBg})`;
|
||||
const { t, locale } = useI18n();
|
||||
const router = useRouter();
|
||||
const { banners, hotMatches, upcomingMatches, loading, load, announcementItems } = usePlayerHome();
|
||||
@@ -54,10 +55,16 @@ function formatKickoff(startTime: string) {
|
||||
return formatLocalMatchDateTime(startTime, locale.value, { variant: 'full' });
|
||||
}
|
||||
|
||||
function matchCardStyle(tab: HotTab) {
|
||||
return {
|
||||
'--match-card-bg': tab === 'hot' ? hotMatchCardBg : upcomingMatchCardBg,
|
||||
};
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mobile-home">
|
||||
<div
|
||||
class="pull-indicator"
|
||||
:style="pullIndicatorStyle()"
|
||||
@@ -67,10 +74,6 @@ function formatKickoff(startTime: string) {
|
||||
|
||||
<BannerCarousel :banners="banners" :fallback-to="bannerFallbackTo" />
|
||||
|
||||
<div class="home-card-wrapper">
|
||||
<HomeAnnouncementCard />
|
||||
</div>
|
||||
|
||||
<div class="hot-tabs" role="tablist" :aria-label="t('home.hot_matches')">
|
||||
<button
|
||||
type="button"
|
||||
@@ -98,6 +101,7 @@ function formatKickoff(startTime: string) {
|
||||
:key="match.id"
|
||||
class="match-card"
|
||||
:class="{ 'match-card--live-anim': activeTab === 'hot' && index < 3 }"
|
||||
:style="matchCardStyle(activeTab)"
|
||||
@click="goMatch(match.id)"
|
||||
>
|
||||
<div class="match-info">
|
||||
@@ -153,8 +157,9 @@ function formatKickoff(startTime: string) {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home-card-wrapper {
|
||||
margin: 12px 16px;
|
||||
.mobile-home {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.pull-indicator {
|
||||
@@ -166,33 +171,79 @@ function formatKickoff(startTime: string) {
|
||||
}
|
||||
|
||||
.hot-tabs {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
margin: 14px 0;
|
||||
}
|
||||
|
||||
.hot-tab {
|
||||
flex: 1;
|
||||
min-height: 36px;
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
width: 100%;
|
||||
min-height: 44px;
|
||||
aspect-ratio: 1024 / 275;
|
||||
padding: 0 12px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg-card);
|
||||
color: var(--text-muted);
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
transition: background 0.2s, border-color 0.2s, color 0.2s;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0;
|
||||
transition: color 0.2s ease, transform 0.2s ease;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
user-select: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.hot-tab::before,
|
||||
.hot-tab::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
.hot-tab::before {
|
||||
z-index: -2;
|
||||
background: url('../assets/images/generated/home-tab-frame-gold-v1.webp') center / 100% 100% no-repeat;
|
||||
opacity: 0.58;
|
||||
filter: saturate(0.86) brightness(0.76);
|
||||
}
|
||||
|
||||
.hot-tab::after {
|
||||
z-index: -1;
|
||||
inset: 8px 16px;
|
||||
background: rgba(0, 0, 0, 0.16);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hot-tab.active {
|
||||
font-weight: 700;
|
||||
background: rgba(244, 162, 97, 0.1);
|
||||
border-color: var(--primary);
|
||||
color: var(--primary-light);
|
||||
text-shadow: 0 0 10px rgba(247, 211, 101, 0.32);
|
||||
}
|
||||
|
||||
.hot-tab.active::before,
|
||||
.hot-tab:active::before {
|
||||
opacity: 1;
|
||||
filter: saturate(1.08) brightness(1.02);
|
||||
}
|
||||
|
||||
.hot-tab.active::after {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.hot-tab:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.hot-tab:focus-visible::before {
|
||||
filter: drop-shadow(0 0 6px rgba(247, 211, 101, 0.45));
|
||||
}
|
||||
|
||||
.hot-tab:active {
|
||||
opacity: 0.92;
|
||||
transform: scale(0.985);
|
||||
}
|
||||
|
||||
.match-card {
|
||||
@@ -218,8 +269,8 @@ function formatKickoff(startTime: string) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: v-bind(matchCardBg) center / 100% 100% no-repeat;
|
||||
opacity: 0.25;
|
||||
background: var(--match-card-bg) center / 100% 100% no-repeat;
|
||||
opacity: 0.28;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -232,7 +283,7 @@ function formatKickoff(startTime: string) {
|
||||
.match-info,
|
||||
.match-flags {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.match-info {
|
||||
@@ -445,15 +496,24 @@ function formatKickoff(startTime: string) {
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
padding: 40px 20px;
|
||||
padding: 30px 20px 36px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
margin-bottom: 14px;
|
||||
width: min(260px, 82vw);
|
||||
aspect-ratio: 16 / 9;
|
||||
height: auto;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
opacity: 0.86;
|
||||
mask-image: linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,7 +14,7 @@ import CorrectScorePanel from '../components/match-detail/CorrectScorePanel.vue'
|
||||
import { isCorrectScoreMarket, parseScoreCode } from '../utils/correctScoreLayout';
|
||||
import { useOnLocaleChange } from '../composables/useOnLocaleChange';
|
||||
import { usePullToRefresh } from '../composables/usePullToRefresh';
|
||||
import vsImg from '../assets/images/vs.png';
|
||||
import vsImg from '../assets/images/generated/vs-gold-alpha-v1.webp';
|
||||
import GoldSpinner from '../components/GoldSpinner.vue';
|
||||
import { matchPhaseLabel, type MatchPhase } from '../utils/matchPhase';
|
||||
import { formatLocalMatchDateTime } from '@thebet365/shared';
|
||||
@@ -911,18 +911,36 @@ function onPickSelection(selId: string, marketId: string) {
|
||||
}
|
||||
|
||||
.my-bets-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-rows: repeat(2, minmax(72px, auto));
|
||||
grid-auto-columns: minmax(158px, calc((100% - 6px) / 2));
|
||||
gap: 6px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding-bottom: 2px;
|
||||
scroll-snap-type: x proximity;
|
||||
scrollbar-width: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.my-bets-list::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.my-bet-card {
|
||||
background: #1c1c1c;
|
||||
border: 1px solid #2e2e2e;
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
padding: 8px 9px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
min-width: 0;
|
||||
min-height: 72px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
.my-bet-card:active {
|
||||
@@ -931,27 +949,29 @@ function onPickSelection(selId: string, marketId: string) {
|
||||
|
||||
.bet-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.bet-type {
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
margin-right: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bet-status {
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
padding: 2px 8px;
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
line-height: 1.5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -972,18 +992,30 @@ function onPickSelection(selId: string, marketId: string) {
|
||||
|
||||
.bet-footer {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.bet-stake {
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
color: #888;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bet-return {
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 340px) {
|
||||
.my-bets-list {
|
||||
grid-auto-columns: minmax(148px, calc((100% - 6px) / 2));
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -281,13 +281,23 @@ function auditNoteDisplayText(remark: { kind: 'note'; text: string }) {
|
||||
|
||||
.summary-card,
|
||||
.audit-card {
|
||||
background: linear-gradient(135deg, #1a1810 0%, #1f1b0e 40%, #16140c 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(rgba(22, 20, 15, 0.74), rgba(18, 16, 12, 0.84)),
|
||||
url('../assets/images/generated/detail-ledger-ticket-bg-v1.webp') center / cover no-repeat;
|
||||
border: 1px solid rgba(212, 175, 55, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.summary-card > *,
|
||||
.audit-card > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.summary-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
||||
.method-badge { font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 4px; }
|
||||
.method-badge.bank { background: rgba(212,175,55,0.15); color: var(--primary-light); }
|
||||
|
||||
@@ -262,23 +262,31 @@ function goCashbackDetail() {
|
||||
gap: 6px;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
background: #141414;
|
||||
background:
|
||||
linear-gradient(rgba(22, 20, 15, 0.74), rgba(18, 16, 12, 0.84)),
|
||||
url('../assets/images/generated/detail-ledger-ticket-bg-v1.webp') center / cover no-repeat;
|
||||
border: 1px solid #222;
|
||||
}
|
||||
|
||||
.hero.pos {
|
||||
border-color: rgba(212, 175, 55, 0.25);
|
||||
background: linear-gradient(135deg, rgba(212, 175, 55, 0.1), rgba(212, 175, 55, 0.03));
|
||||
background:
|
||||
linear-gradient(135deg, rgba(212, 175, 55, 0.14), rgba(20, 16, 8, 0.76)),
|
||||
url('../assets/images/generated/detail-ledger-ticket-bg-v1.webp') center / cover no-repeat;
|
||||
}
|
||||
|
||||
.hero.neg {
|
||||
border-color: rgba(224, 80, 80, 0.2);
|
||||
background: linear-gradient(135deg, rgba(224, 80, 80, 0.1), rgba(224, 80, 80, 0.03));
|
||||
background:
|
||||
linear-gradient(135deg, rgba(224, 80, 80, 0.1), rgba(20, 12, 10, 0.78)),
|
||||
url('../assets/images/generated/detail-ledger-ticket-bg-v1.webp') center / cover no-repeat;
|
||||
}
|
||||
|
||||
.hero.zero {
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
background: #141414;
|
||||
background:
|
||||
linear-gradient(rgba(22, 20, 15, 0.76), rgba(18, 16, 12, 0.84)),
|
||||
url('../assets/images/generated/detail-ledger-ticket-bg-v1.webp') center / cover no-repeat;
|
||||
}
|
||||
|
||||
.hero-type {
|
||||
@@ -314,13 +322,22 @@ function goCashbackDetail() {
|
||||
}
|
||||
|
||||
.section {
|
||||
background: #141414;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(rgba(22, 20, 15, 0.78), rgba(18, 16, 12, 0.86)),
|
||||
url('../assets/images/generated/detail-ledger-ticket-bg-v1.webp') center / cover no-repeat;
|
||||
border: 1px solid #222;
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.section > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
|
||||
@@ -228,12 +228,21 @@ function legResultClass(status?: string) {
|
||||
.detail-right { display: flex; flex-direction: column; gap: 16px; }
|
||||
|
||||
.detail-card {
|
||||
background: var(--desktop-sidebar-bg);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(rgba(22, 20, 15, 0.76), rgba(18, 16, 12, 0.82)),
|
||||
url('../../assets/images/generated/detail-ledger-ticket-bg-v1.webp') center / cover no-repeat;
|
||||
border: 1px solid var(--desktop-border);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.detail-card > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card-title { font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 14px; padding-bottom: 10px; border-bottom: 1px solid var(--desktop-border); }
|
||||
|
||||
/* Selection rows */
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useDesktopParlayMatches, type DesktopParlayMatch } from '../../composab
|
||||
import { useOutrightEvents } from '../../composables/useOutrightEvents';
|
||||
import MatchBetCard from '../../components/MatchBetCard.vue';
|
||||
import DesktopOutrightEventCard from '../../components/desktop/DesktopOutrightEventCard.vue';
|
||||
import emptyMatchesImg from '../../assets/images/generated/empty-matches-stadium-v1.webp';
|
||||
import {
|
||||
isAfterLocalTodayMatchWindow as isAfterTodayMatchWindow,
|
||||
isInLocalTodayMatchWindow as isInTodayMatchWindow,
|
||||
@@ -159,7 +160,7 @@ function closeModal() {
|
||||
|
||||
async function refresh() {
|
||||
if (mainTab.value === 'outright') {
|
||||
await loadOutrights({ silent: false });
|
||||
await loadOutrights({ silent: false, force: true });
|
||||
return;
|
||||
}
|
||||
await loadParlayMatches(true);
|
||||
@@ -224,6 +225,7 @@ void loadParlayMatches(true);
|
||||
</template>
|
||||
|
||||
<div v-else class="empty-state">
|
||||
<img :src="emptyMatchesImg" alt="" class="empty-state-art" />
|
||||
<p>{{ t('bet.no_matches') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -254,6 +256,7 @@ void loadParlayMatches(true);
|
||||
</template>
|
||||
|
||||
<div v-else class="empty-state">
|
||||
<img :src="emptyMatchesImg" alt="" class="empty-state-art" />
|
||||
<p>{{ outrightError || t('bet.no_outright') }}</p>
|
||||
<p v-if="!outrightError" class="empty-hint">{{ t('bet.no_outright_hint') }}</p>
|
||||
</div>
|
||||
@@ -438,12 +441,31 @@ void loadParlayMatches(true);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
padding: 40px 16px;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.empty-state-art {
|
||||
width: min(460px, 70%);
|
||||
aspect-ratio: 16 / 9;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
opacity: 0.72;
|
||||
mask-image: linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
@@ -459,7 +481,8 @@ void loadParlayMatches(true);
|
||||
|
||||
.outright-event-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(340px, 420px));
|
||||
justify-content: start;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,6 +8,8 @@ import AnnouncementMarquee from '../../components/AnnouncementMarquee.vue';
|
||||
import { usePlayerHome } from '../../composables/usePlayerHome';
|
||||
import TeamEmblem from '../../components/TeamEmblem.vue';
|
||||
import GoldSpinner from '../../components/GoldSpinner.vue';
|
||||
import emptyMatchesImg from '../../assets/images/generated/empty-matches-stadium-v1.webp';
|
||||
import vsImg from '../../assets/images/generated/vs-gold-alpha-v1.webp';
|
||||
import { formatLocalMatchDateTime } from '@thebet365/shared';
|
||||
import type { PlayerHomeMatch } from '../../composables/usePlayerHome';
|
||||
import { useDesktopBetPopover } from '../../composables/useDesktopBetPopover';
|
||||
@@ -226,7 +228,9 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
/>
|
||||
<span class="featured-team-name">{{ featuredMatch.homeTeamName }}</span>
|
||||
</div>
|
||||
<div class="featured-vs vs-heartbeat">VS</div>
|
||||
<div class="featured-vs vs-heartbeat">
|
||||
<img :src="vsImg" alt="VS" class="featured-vs-img" />
|
||||
</div>
|
||||
<div class="featured-team">
|
||||
<TeamEmblem
|
||||
size="lg"
|
||||
@@ -293,7 +297,9 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
/>
|
||||
<span class="team-name">{{ match.homeTeamName }}</span>
|
||||
</div>
|
||||
<div class="vs-text">VS</div>
|
||||
<div class="vs-text">
|
||||
<img :src="vsImg" alt="VS" class="vs-mini-img" />
|
||||
</div>
|
||||
<div class="team-row">
|
||||
<TeamEmblem
|
||||
size="sm"
|
||||
@@ -317,7 +323,9 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
/>
|
||||
<span class="hover-team-name">{{ match.homeTeamName }}</span>
|
||||
</div>
|
||||
<div class="hover-vs-glow">VS</div>
|
||||
<div class="hover-vs-glow">
|
||||
<img :src="vsImg" alt="VS" class="hover-vs-img" />
|
||||
</div>
|
||||
<div class="hover-team away-slide">
|
||||
<TeamEmblem
|
||||
size="md"
|
||||
@@ -364,6 +372,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
</template>
|
||||
|
||||
<div v-else class="empty-state">
|
||||
<img :src="emptyMatchesImg" alt="" class="empty-state-art" />
|
||||
<p>{{ t('home.no_matches') }}</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -405,7 +414,9 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
/>
|
||||
<span class="team-name">{{ match.homeTeamName }}</span>
|
||||
</div>
|
||||
<div class="vs-text">VS</div>
|
||||
<div class="vs-text">
|
||||
<img :src="vsImg" alt="VS" class="vs-mini-img" />
|
||||
</div>
|
||||
<div class="team-row">
|
||||
<TeamEmblem
|
||||
size="sm"
|
||||
@@ -429,7 +440,9 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
/>
|
||||
<span class="hover-team-name">{{ match.homeTeamName }}</span>
|
||||
</div>
|
||||
<div class="hover-vs-glow">VS</div>
|
||||
<div class="hover-vs-glow">
|
||||
<img :src="vsImg" alt="VS" class="hover-vs-img" />
|
||||
</div>
|
||||
<div class="hover-team away-slide">
|
||||
<TeamEmblem
|
||||
size="md"
|
||||
@@ -474,6 +487,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="!loading" class="empty-state">
|
||||
<img :src="emptyMatchesImg" alt="" class="empty-state-art" />
|
||||
<p>{{ t('home.upcoming_empty') }}</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -505,7 +519,9 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
</div>
|
||||
<div class="sri-teams">
|
||||
<span>{{ m.homeTeamName }}</span>
|
||||
<span class="sri-vs">vs</span>
|
||||
<span class="sri-vs">
|
||||
<img :src="vsImg" alt="VS" class="sri-vs-img" />
|
||||
</span>
|
||||
<span>{{ m.awayTeamName }}</span>
|
||||
</div>
|
||||
|
||||
@@ -520,7 +536,9 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
<TeamEmblem size="md" :team-code="m.homeTeamCode" :team-name="m.homeTeamName" :logo-url="m.homeTeamLogoUrl" />
|
||||
<span>{{ m.homeTeamName }}</span>
|
||||
</div>
|
||||
<div class="sri-preview-vs-text">VS</div>
|
||||
<div class="sri-preview-vs-text">
|
||||
<img :src="vsImg" alt="VS" class="sri-preview-vs-img" />
|
||||
</div>
|
||||
<div class="sri-preview-team away-team">
|
||||
<TeamEmblem size="md" :team-code="m.awayTeamCode" :team-name="m.awayTeamName" :logo-url="m.awayTeamLogoUrl" />
|
||||
<span>{{ m.awayTeamName }}</span>
|
||||
@@ -623,7 +641,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
}
|
||||
|
||||
.content-section {
|
||||
background: rgba(20, 20, 20, 0.88);
|
||||
background: rgba(32, 30, 24, 0.78);
|
||||
backdrop-filter: var(--blur-md);
|
||||
-webkit-backdrop-filter: var(--blur-md);
|
||||
border: 1px solid var(--border);
|
||||
@@ -685,22 +703,39 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
.loading-state,
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 160px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.empty-state-art {
|
||||
width: min(420px, 72%);
|
||||
aspect-ratio: 16 / 9;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
opacity: 0.72;
|
||||
mask-image: linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Featured 焦点大卡 */
|
||||
.featured-match {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
padding: 18px 20px;
|
||||
padding: 18px 20px 20px;
|
||||
border-radius: var(--radius-lg);
|
||||
background:
|
||||
linear-gradient(135deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.04) 50%, rgba(15, 10, 0, 0.1)),
|
||||
url('../../assets/images/card-bg.webp') center/cover no-repeat;
|
||||
linear-gradient(90deg, rgba(0, 0, 0, 0.34), rgba(0, 0, 0, 0.12) 50%, rgba(10, 6, 0, 0.36)),
|
||||
url('../../assets/images/generated/pc-featured-match-bg-v1.webp') center / 100% 100% no-repeat;
|
||||
border: 1px solid var(--border-gold);
|
||||
box-shadow: var(--shadow-gold-lg), inset 0 0 20px rgba(212, 175, 55, 0.06);
|
||||
margin-bottom: var(--space-4);
|
||||
@@ -711,6 +746,11 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.featured-match > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.featured-match::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
@@ -718,10 +758,30 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
width: 180px; height: 180px;
|
||||
background: radial-gradient(circle at top right, rgba(212, 175, 55, 0.2), transparent 60%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
animation: goldGlowPulse 3.5s ease-in-out infinite;
|
||||
transform-origin: top right;
|
||||
}
|
||||
|
||||
.featured-match::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
background: url('../../assets/images/generated/pc-featured-match-bg-v1.webp') center / 100% 100% no-repeat;
|
||||
mask:
|
||||
linear-gradient(#000 0 0) top / 100% 22px no-repeat,
|
||||
linear-gradient(#000 0 0) bottom / 100% 22px no-repeat,
|
||||
linear-gradient(#000 0 0) left / 22px 100% no-repeat,
|
||||
linear-gradient(#000 0 0) right / 22px 100% no-repeat;
|
||||
-webkit-mask:
|
||||
linear-gradient(#000 0 0) top / 100% 22px no-repeat,
|
||||
linear-gradient(#000 0 0) bottom / 100% 22px no-repeat,
|
||||
linear-gradient(#000 0 0) left / 22px 100% no-repeat,
|
||||
linear-gradient(#000 0 0) right / 22px 100% no-repeat;
|
||||
}
|
||||
|
||||
.featured-match:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--glow-gold-lg), inset 0 0 24px rgba(212, 175, 55, 0.08);
|
||||
@@ -786,22 +846,35 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
}
|
||||
|
||||
.featured-vs {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
color: var(--primary-light);
|
||||
text-shadow: 0 0 12px rgba(212, 175, 55, 0.5);
|
||||
width: 72px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.featured-vs-img {
|
||||
width: 68px;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
filter:
|
||||
drop-shadow(0 0 4px rgba(255, 231, 150, 0.42))
|
||||
drop-shadow(0 0 8px rgba(212, 175, 55, 0.26));
|
||||
}
|
||||
|
||||
.featured-markets {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
margin: 14px -20px -18px;
|
||||
padding: 14px 20px 18px;
|
||||
background: #000000;
|
||||
border-top: 1px solid rgba(212, 175, 55, 0.2);
|
||||
margin: 14px -12px 0;
|
||||
padding: 14px 12px 8px;
|
||||
background: rgba(10, 8, 4, 0.46);
|
||||
border-top: 1px solid rgba(246, 210, 108, 0.22);
|
||||
border-radius: 0 0 8px 8px;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 236, 174, 0.08);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.featured-market-block {
|
||||
@@ -947,15 +1020,20 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
}
|
||||
|
||||
.vs-text {
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 36px;
|
||||
line-height: 1;
|
||||
margin: -2px 0;
|
||||
}
|
||||
|
||||
.vs-mini-img {
|
||||
width: 30px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
opacity: 0.78;
|
||||
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.75));
|
||||
}
|
||||
|
||||
.team-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1124,10 +1202,17 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
}
|
||||
|
||||
.sri-vs {
|
||||
color: var(--text-muted);
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
font-style: italic;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sri-vs-img {
|
||||
width: 22px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
opacity: 0.7;
|
||||
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.7));
|
||||
}
|
||||
|
||||
.sri-time {
|
||||
@@ -1309,19 +1394,26 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
}
|
||||
|
||||
.hover-vs-glow {
|
||||
font-size: 18px;
|
||||
font-weight: 900;
|
||||
font-style: italic;
|
||||
color: var(--primary-light);
|
||||
width: 58px;
|
||||
height: 38px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: scale(2.8);
|
||||
opacity: 0;
|
||||
text-shadow:
|
||||
0 0 10px rgba(212, 175, 55, 0.8),
|
||||
0 0 20px rgba(212, 175, 55, 0.4);
|
||||
transition: transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0.1s, opacity 0.3s ease 0.1s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hover-vs-img {
|
||||
width: 56px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
filter:
|
||||
drop-shadow(0 0 5px rgba(255, 231, 150, 0.52))
|
||||
drop-shadow(0 0 9px rgba(212, 175, 55, 0.34));
|
||||
}
|
||||
|
||||
.match-card:is(:hover, .match-card--engaged) .hover-vs-glow {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
@@ -1330,11 +1422,9 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
|
||||
@keyframes vs-clash-impact {
|
||||
0% {
|
||||
text-shadow: 0 0 25px rgba(212, 175, 55, 1);
|
||||
transform: scale(1.25);
|
||||
}
|
||||
100% {
|
||||
text-shadow: 0 0 8px rgba(212, 175, 55, 0.7);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@@ -1471,17 +1561,26 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: strin
|
||||
}
|
||||
|
||||
.sri-preview-vs-text {
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
font-style: italic;
|
||||
color: var(--primary-light);
|
||||
text-shadow: 0 0 8px rgba(212, 175, 55, 0.6);
|
||||
width: 48px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
opacity: 0;
|
||||
transform: scale(2.2);
|
||||
transition: opacity 0.3s ease 0.1s, transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0.1s;
|
||||
}
|
||||
|
||||
.sri-preview-vs-img {
|
||||
width: 46px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
filter:
|
||||
drop-shadow(0 0 4px rgba(255, 231, 150, 0.42))
|
||||
drop-shadow(0 0 8px rgba(212, 175, 55, 0.26));
|
||||
}
|
||||
|
||||
.side-recommend-item:hover .sri-preview-vs-text {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { formatLocalMatchDateTime, defaultMarketName, DETAIL_MARKET_TYPES } from
|
||||
import { resolveSelectionLabel } from '../../utils/selectionLabel';
|
||||
import { useDesktopBetPopover } from '../../composables/useDesktopBetPopover';
|
||||
import { useDesktopBetLocate, scrollToBetSelection } from '../../composables/useDesktopBetLocate';
|
||||
import vsImg from '../../assets/images/generated/vs-gold-alpha-v1.webp';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -360,7 +361,9 @@ function getHoveredSide() {
|
||||
<span class="divider">-</span>
|
||||
<span class="score">{{ match.score?.ftAway }}</span>
|
||||
</div>
|
||||
<div class="versus-row vs" v-else>VS</div>
|
||||
<div class="versus-row vs" v-else>
|
||||
<img :src="vsImg" alt="VS" class="score-vs-img" />
|
||||
</div>
|
||||
<span class="kickoff-time">{{ kickoff }}</span>
|
||||
</div>
|
||||
|
||||
@@ -537,13 +540,15 @@ function getHoveredSide() {
|
||||
}
|
||||
|
||||
.match-score-band {
|
||||
background:
|
||||
linear-gradient(135deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.04) 50%, rgba(15, 10, 0, 0.1)),
|
||||
url('../../assets/images/card-bg.webp') center/cover no-repeat;
|
||||
border: 1px solid var(--border-gold);
|
||||
box-shadow: var(--shadow-gold), inset 0 0 20px rgba(212, 175, 55, 0.06);
|
||||
border-radius: 12px;
|
||||
padding: 18px 24px;
|
||||
aspect-ratio: 1600 / 320;
|
||||
background-image: url('../../assets/images/generated/pc-featured-match-bg-v1.webp');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
border: 0;
|
||||
box-shadow: var(--shadow-gold);
|
||||
border-radius: 4px;
|
||||
padding: 12px 24px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -595,9 +600,17 @@ function getHoveredSide() {
|
||||
}
|
||||
|
||||
.versus-row.vs {
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: var(--text-muted);
|
||||
min-height: 42px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.score-vs-img {
|
||||
width: 72px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
filter:
|
||||
drop-shadow(0 0 4px rgba(255, 231, 150, 0.42))
|
||||
drop-shadow(0 0 8px rgba(212, 175, 55, 0.26));
|
||||
}
|
||||
|
||||
.kickoff-time {
|
||||
|
||||
@@ -8,11 +8,11 @@ import OutrightOptionCard from '../../components/outright/OutrightOptionCard.vue
|
||||
import OutrightBetModal, { type OutrightPick } from '../../components/outright/OutrightBetModal.vue';
|
||||
import { useAuthStore } from '../../stores/auth';
|
||||
import GoldSpinner from '../../components/GoldSpinner.vue';
|
||||
import saishiImg from '../../assets/images/saishi.webp';
|
||||
import cardBg from '../../assets/images/card-bg.webp';
|
||||
import saishiImg from '../../assets/images/generated/saishi-gold-trophy-v1.webp';
|
||||
import outrightDetailBg from '../../assets/images/generated/pc-outright-detail-bg-v1.webp';
|
||||
import { useDesktopBetLocate, scrollToBetSelection } from '../../composables/useDesktopBetLocate';
|
||||
|
||||
const matchCardBg = `url(${cardBg})`;
|
||||
const matchCardBg = `url(${outrightDetailBg})`;
|
||||
const ODDS_POLL_MS = 15000;
|
||||
|
||||
const route = useRoute();
|
||||
@@ -246,7 +246,7 @@ onUnmounted(stopPolling);
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: v-bind(matchCardBg) center / cover no-repeat;
|
||||
opacity: 0.22;
|
||||
opacity: 0.36;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useAppLocale } from '../../composables/useAppLocale';
|
||||
import { usePlayerProfile } from '../../composables/usePlayerProfile';
|
||||
import { formatMoney } from '../../utils/localeDisplay';
|
||||
import { parseCashbackApiData, sumCashbackAmount } from '../../utils/cashback';
|
||||
import walletBg from '../../assets/images/wallet-bg.webp';
|
||||
import walletBg from '../../assets/images/generated/pc-wallet-banner-v1.webp';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const router = useRouter();
|
||||
@@ -223,6 +223,12 @@ const quickLinks = computed(() => [
|
||||
overflow-y: auto;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.profile-page::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@@ -306,8 +312,8 @@ const quickLinks = computed(() => [
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
object-position: center center;
|
||||
transform: scale(1.05);
|
||||
filter: brightness(0.86);
|
||||
transform: scale(1.01);
|
||||
filter: brightness(0.94);
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -292,12 +292,21 @@ function auditNoteDisplayText(remark: { kind: 'note'; text: string }) {
|
||||
.detail-layout { display: flex; flex-direction: column; gap: 16px; max-width: 680px; }
|
||||
|
||||
.detail-card {
|
||||
background: var(--desktop-sidebar-bg);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(rgba(22, 20, 15, 0.76), rgba(18, 16, 12, 0.82)),
|
||||
url('../../assets/images/generated/detail-ledger-ticket-bg-v1.webp') center / cover no-repeat;
|
||||
border: 1px solid var(--desktop-border);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.detail-card > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.header-card { display: flex; flex-direction: column; gap: 12px; }
|
||||
|
||||
.order-no {
|
||||
|
||||
@@ -166,12 +166,21 @@ function txLabel(tx: TxDetail): string {
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: var(--desktop-sidebar-bg);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(rgba(22, 20, 15, 0.76), rgba(18, 16, 12, 0.82)),
|
||||
url('../../assets/images/generated/detail-ledger-ticket-bg-v1.webp') center / cover no-repeat;
|
||||
border: 1px solid var(--desktop-border);
|
||||
border-radius: 12px;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.detail-card > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Hero card (left) */
|
||||
.hero-card {
|
||||
display: flex;
|
||||
|
||||