feat: 重构代码架构 + 新增报表/跟单/输赢统计功能
- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController - 新增 Service 层: TransactionService, ReportService, FollowPlanService - pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll) - 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan) - 后台新增跟单计划管理 + 用户输赢明细统计 - 封盘状态显示倒计时 (x:xx) - 音效仅在开奖弹窗打开时播放 - 路由按模块分组整理 - autoload 支持 App\Services 命名空间
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,131 @@
|
||||
<?php use App\Core\I18n; I18n::init(); $t = function($k,$p=[]){return I18n::t($k,$p);}; ?>
|
||||
<!DOCTYPE html><html lang="<?=I18n::getLang()?>">
|
||||
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title><?=$t('follow_plan')?></title>
|
||||
<link rel="stylesheet" href="/Static/css/app.css">
|
||||
<style>
|
||||
.page-header{display:flex;align-items:center;padding:12px 16px;background:#fff;border-bottom:1px solid #F0F0F0;position:sticky;top:0;z-index:10}
|
||||
.page-header .back{width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer}
|
||||
.page-header .title{flex:1;text-align:center;font-size:17px;font-weight:600;color:#333}
|
||||
.page-header .balance{font-size:13px;color:#1E90FF;display:flex;align-items:center;gap:4px}
|
||||
.game-select{background:#fff;padding:12px 16px;border-bottom:1px solid #F0F0F0}
|
||||
.game-select select{width:100%;padding:8px 12px;border:1px solid #ddd;border-radius:8px;font-size:14px;color:#333;background:#F9F9F9}
|
||||
.stats-cards{display:flex;gap:12px;padding:16px;background:#fff;margin-top:1px}
|
||||
.stat-card{flex:1;background:linear-gradient(135deg,#E8F4FF,#D0E8FF);border-radius:12px;padding:16px;text-align:center}
|
||||
.stat-card .label{font-size:12px;color:#1E90FF;margin-bottom:6px}
|
||||
.stat-card .value{font-size:22px;font-weight:700;color:#333}
|
||||
.plan-card{background:#fff;margin:8px 16px;border-radius:12px;padding:16px;box-shadow:0 2px 8px rgba(0,0,0,.06)}
|
||||
.plan-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
|
||||
.plan-name{font-size:15px;font-weight:600;color:#333}
|
||||
.plan-type-badge{padding:2px 10px;border-radius:12px;font-size:11px;font-weight:600;color:#fff;background:#1E90FF}
|
||||
.plan-stats{display:flex;gap:16px;margin-bottom:12px}
|
||||
.plan-stat{text-align:center;flex:1}
|
||||
.plan-stat .ps-label{font-size:11px;color:#999;margin-bottom:2px}
|
||||
.plan-stat .ps-value{font-size:16px;font-weight:700}
|
||||
.plan-stat .ps-value.win{color:#4CAF50}
|
||||
.plan-stat .ps-value.lose{color:#FF4444}
|
||||
.plan-recent{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:12px}
|
||||
.plan-dot{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;color:#fff}
|
||||
.plan-dot.w{background:#4CAF50}
|
||||
.plan-dot.l{background:#FF4444}
|
||||
.plan-actions{display:flex;gap:8px}
|
||||
.follow-btn{flex:1;padding:10px;border-radius:8px;text-align:center;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s;border:none}
|
||||
.follow-btn.active{background:#FF4444;color:#fff}
|
||||
.follow-btn.inactive{background:linear-gradient(135deg,#1E90FF,#0066CC);color:#fff}
|
||||
.follow-btn:active{transform:scale(.98)}
|
||||
.empty-plans{text-align:center;padding:60px 20px}
|
||||
.empty-plans .icon{font-size:48px;margin-bottom:16px;opacity:.3}
|
||||
.empty-plans .text{font-size:14px;color:#999}
|
||||
.plan-type-select{background:#fff;padding:12px 16px;border-bottom:1px solid #F0F0F0}
|
||||
.type-tabs{display:flex;gap:8px;flex-wrap:wrap}
|
||||
.type-tab{padding:6px 16px;border-radius:20px;font-size:13px;color:#666;background:#F0F0F0;cursor:pointer;border:none}
|
||||
.type-tab.active{background:#1E90FF;color:#fff}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background:#F5F5F5;padding-bottom:20px">
|
||||
|
||||
<div class="page-header">
|
||||
<a href="/profile" class="back"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg></a>
|
||||
<div class="title"><?=$t('follow_plan')?></div>
|
||||
<div class="balance"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v12M8 10h8M8 14h8"/></svg> <?=number_format($user['balance']??0,2)?></div>
|
||||
</div>
|
||||
|
||||
<div class="game-select">
|
||||
<select>
|
||||
<option><?=htmlspecialchars($game['name'] ?? $t('pk10_title'))?></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="stats-cards">
|
||||
<div class="stat-card">
|
||||
<div class="label"><?=$t('total_win_rate')?></div>
|
||||
<div class="value"><?=$summary['win_rate']?>%</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label"><?=$t('total_profit')?></div>
|
||||
<div class="value" style="color:<?=$summary['profit']>=0?'#4CAF50':'#FF4444'?>"><?=$summary['profit']>=0?'+':''?><?=number_format($summary['profit'],2)?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if(empty($plans)): ?>
|
||||
<div class="empty-plans">
|
||||
<div class="icon">
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="#ccc" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 3v18"/></svg>
|
||||
</div>
|
||||
<div class="text"><?=$t('no_follow_plan')?></div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<?php
|
||||
$typeNames = ['bs'=>$t('big').'/'.$t('small'), 'oe'=>$t('odd').'/'.$t('even'), 'dt'=>$t('dragon').'/'.$t('tiger'), 'sum_bs'=>$t('sum_big').'/'.$t('sum_small')];
|
||||
foreach($plans as $plan):
|
||||
$isFollowing = isset($userFollows[$plan['id']]);
|
||||
?>
|
||||
<div class="plan-card">
|
||||
<div class="plan-header">
|
||||
<div class="plan-name"><?=htmlspecialchars($plan['name'])?></div>
|
||||
<span class="plan-type-badge"><?=$typeNames[$plan['plan_type']]??$plan['plan_type']?></span>
|
||||
</div>
|
||||
<div class="plan-stats">
|
||||
<div class="plan-stat">
|
||||
<div class="ps-label"><?=$t('total_win_rate')?></div>
|
||||
<div class="ps-value <?=$plan['win_rate']>=50?'win':'lose'?>"><?=$plan['win_rate']?>%</div>
|
||||
</div>
|
||||
<div class="plan-stat">
|
||||
<div class="ps-label"><?=$t('bet_amount')?></div>
|
||||
<div class="ps-value"><?=number_format($plan['bet_amount'],0)?></div>
|
||||
</div>
|
||||
<div class="plan-stat">
|
||||
<div class="ps-label">总期数</div>
|
||||
<div class="ps-value"><?=$plan['total_records']?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if(!empty($plan['recent'])): ?>
|
||||
<div class="plan-recent">
|
||||
<?php foreach(array_reverse($plan['recent']) as $rec): ?>
|
||||
<div class="plan-dot <?=$rec['is_win']?'w':'l'?>"><?=$rec['is_win']?'W':'L'?></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="plan-actions">
|
||||
<button class="follow-btn <?=$isFollowing?'active':'inactive'?>" onclick="toggleFollow(<?=$plan['id']?>,this)">
|
||||
<?=$isFollowing?'取消跟单':'开始跟单'?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
function toggleFollow(planId, btn) {
|
||||
fetch('/api/follow-plan/toggle', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({plan_id: planId})
|
||||
}).then(function(r) { return r.json(); }).then(function(d) {
|
||||
if (d.success) location.reload();
|
||||
else alert(d.message || '操作失败');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body></html>
|
||||
+43
-3
@@ -44,15 +44,15 @@
|
||||
<div class="user-balance"><?=number_format($user['balance']??0,2)?></div>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button class="act-btn" onclick="alert('<?=$t('deposit')?>')">
|
||||
<button class="act-btn" onclick="doDeposit()">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>
|
||||
<span><?=$t('deposit')?></span>
|
||||
</button>
|
||||
<button class="act-btn" onclick="alert('<?=$t('withdraw')?>')">
|
||||
<button class="act-btn" onclick="doWithdraw()">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>
|
||||
<span><?=$t('withdraw')?></span>
|
||||
</button>
|
||||
<button class="act-btn" onclick="alert('<?=$t('transfer')?>')">
|
||||
<button class="act-btn" onclick="doTransfer()">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>
|
||||
<span><?=$t('transfer')?></span>
|
||||
</button>
|
||||
@@ -93,5 +93,45 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function doDeposit(){
|
||||
var url='<?=\App\Core\SettingsHelper::get("customer_service_url")?>';
|
||||
if(!url){alert('<?=$t("contact_admin")?>');return;}
|
||||
var amount=prompt('<?=$t("deposit_amount")?>');
|
||||
if(!amount||isNaN(amount)||parseFloat(amount)<=0)return;
|
||||
fetch('/api/fund-request',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({type:'deposit',amount:parseFloat(amount)})
|
||||
}).then(function(r){return r.json();}).then(function(d){
|
||||
if(d.success){alert('<?=$t("deposit_submitted")?>');window.open(url,'_blank');location.reload();}
|
||||
else{alert(d.message||'<?=$t("error")?>');}
|
||||
});
|
||||
}
|
||||
function doWithdraw(){
|
||||
var amount=prompt('<?=$t("withdraw_amount")?>');
|
||||
if(!amount||isNaN(amount)||parseFloat(amount)<=0)return;
|
||||
fetch('/api/fund-request',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({type:'withdraw',amount:parseFloat(amount)})
|
||||
}).then(function(r){return r.json();}).then(function(d){
|
||||
if(d.success){alert('<?=$t("withdraw_submitted")?>');location.reload();}
|
||||
else{alert(d.message||'<?=$t("error")?>');}
|
||||
});
|
||||
}
|
||||
function doTransfer(){
|
||||
var to=prompt('<?=$t("transfer_to")?>');
|
||||
if(!to||!to.trim())return;
|
||||
var amount=prompt('<?=$t("transfer_amount")?>');
|
||||
if(!amount||isNaN(amount)||parseFloat(amount)<=0)return;
|
||||
if(!confirm('<?=$t("transfer_confirm")?> '+to+' , <?=$t("amount")?>: '+amount))return;
|
||||
fetch('/api/transfer',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({to_username:to.trim(),amount:parseFloat(amount)})
|
||||
}).then(function(r){return r.json();}).then(function(d){
|
||||
if(d.success){alert(d.message||'<?=$t("transfer_success")?>');location.reload();}
|
||||
else{alert(d.message||'<?=$t("error")?>');}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<?php $navActive='game'; include __DIR__.'/_nav.php'; ?>
|
||||
</body></html>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= htmlspecialchars($settings['site_title'] ?? 'PK10 Speed Racing') ?></title>
|
||||
<title><?= htmlspecialchars($settings['site_title'] ?? 'F1 Racing') ?></title>
|
||||
<meta name="description" content="<?= htmlspecialchars($settings['site_description'] ?? '') ?>">
|
||||
<link rel="shortcut icon" href="<?= htmlspecialchars($settings['site_favicon'] ?? '/Static/css/favicon.ico') ?>" type="image/x-icon">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
|
||||
+633
-176
@@ -1,36 +1,250 @@
|
||||
<?php use App\Core\I18n; I18n::init(); $t = function($k,$p=[]){return I18n::t($k,$p);}; $gameId = $game['id'] ?? 0; ?>
|
||||
<?php use App\Core\I18n; I18n::init(); $t = function($k,$p=[]){return I18n::t($k,$p);}; $gameId = $game['id'] ?? 0;
|
||||
// 赔率直接在视图查询,确保不依赖控制器传值
|
||||
$_oddsDb = new \Db\Database();
|
||||
$_oddsRaw = $_oddsDb->select('game_odds','*',['game_id'=>$gameId]);
|
||||
$oddsMap = [];
|
||||
foreach(($_oddsRaw??[]) as $_o){ $oddsMap[$_o['type'].'_'.$_o['target']]=(float)$_o['odds']; }
|
||||
unset($_oddsDb,$_oddsRaw,$_o);
|
||||
?>
|
||||
<!DOCTYPE html><html lang="<?=I18n::getLang()?>">
|
||||
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
|
||||
<title><?=$t('pk10_title')?></title>
|
||||
<link rel="stylesheet" href="/Static/css/app.css">
|
||||
<?php if(!empty($game['stream_url'])): ?><link rel="stylesheet" href="/Static/ckplayer/css/ckplayer.css"><?php endif; ?>
|
||||
<style>
|
||||
/* 赛车赛道 — 保留3D透视 */
|
||||
.race-wrapper{perspective:800px;perspective-origin:50% 100%}
|
||||
.race-track{background:linear-gradient(180deg,#2d5a2d 0%,#1e4a1e 50%,#163a16 100%);border-radius:12px;overflow:hidden;position:relative;transform:rotateX(25deg);transform-origin:50% 100%;box-shadow:0 -20px 60px rgba(0,128,0,.08),inset 0 0 80px rgba(0,0,0,.3)}
|
||||
.race-track::before{content:'';position:absolute;top:0;left:0;right:0;bottom:0;background:repeating-linear-gradient(90deg,transparent,transparent calc(10% - 1px),rgba(255,255,255,.05) calc(10% - 1px),rgba(255,255,255,.05) 10%);pointer-events:none;z-index:1}
|
||||
.finish-line{position:absolute;right:5%;top:0;bottom:0;width:4px;background:repeating-linear-gradient(180deg,#fff 0,#fff 4px,#000 4px,#000 8px);opacity:.4;z-index:2}
|
||||
.start-line{position:absolute;left:5%;top:0;bottom:0;width:2px;background:rgba(255,255,255,.2);z-index:2}
|
||||
.track-lane{height:36px;display:flex;align-items:center;border-bottom:1px solid rgba(255,255,255,.06);padding:0 8px;position:relative;overflow:hidden}
|
||||
.track-lane:nth-child(odd){background:rgba(255,255,255,.02)}
|
||||
.track-lane .progress-bar{position:absolute;left:0;top:0;height:100%;opacity:.15;width:5%;border-radius:0 4px 4px 0;transition:width .3s ease}
|
||||
.car-container{position:absolute;left:3%;top:50%;transform:translateY(-50%);z-index:10;display:flex;align-items:center;gap:4px;transition:left 0.3s ease}
|
||||
.race-car{width:32px;height:24px;border-radius:4px 12px 12px 4px;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:11px;color:#fff;flex-shrink:0;position:relative;box-shadow:2px 2px 6px rgba(0,0,0,.5),inset 0 1px 0 rgba(255,255,255,.3);text-shadow:0 1px 2px rgba(0,0,0,.5)}
|
||||
.race-car::after{content:'';position:absolute;right:-6px;top:50%;transform:translateY(-50%);width:0;height:0;border-left:6px solid currentColor;border-top:4px solid transparent;border-bottom:4px solid transparent;opacity:.4}
|
||||
.rc1{background:linear-gradient(180deg,#ff6b6b,#c0392b);color:#c0392b}.rc2{background:linear-gradient(180deg,#74b9ff,#2980b9);color:#2980b9}
|
||||
.rc3{background:linear-gradient(180deg,#55efc4,#27ae60);color:#27ae60}.rc4{background:linear-gradient(180deg,#ffeaa7,#f39c12);color:#f39c12}
|
||||
.rc5{background:linear-gradient(180deg,#dda0dd,#8e44ad);color:#8e44ad}.rc6{background:linear-gradient(180deg,#81ecec,#16a085);color:#16a085}
|
||||
.rc7{background:linear-gradient(180deg,#fab1a0,#e67e22);color:#e67e22}.rc8{background:linear-gradient(180deg,#fd79a8,#e91e63);color:#e91e63}
|
||||
.rc9{background:linear-gradient(180deg,#a0d2db,#00bcd4);color:#00bcd4}.rc10{background:linear-gradient(180deg,#c8e6c9,#689f38);color:#689f38}
|
||||
.exhaust{position:absolute;left:-8px;top:50%;transform:translateY(-50%);font-size:8px;opacity:0;pointer-events:none}
|
||||
/* ===== F1赛车开奖场景 ===== */
|
||||
/* 场景容器 */
|
||||
.race-scene{background:#1a1a2e;border-radius:10px;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,.6);margin:6px 0}
|
||||
/* 场景顶栏 — 深色渐变 */
|
||||
.race-scene-hd{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px;padding:6px 10px;background:linear-gradient(180deg,#2a2a3e,#1a1a2e);border-bottom:1px solid rgba(255,255,255,.08)}
|
||||
.rs-row1{display:flex;align-items:center;gap:6px;width:100%}
|
||||
.rs-title{font-size:13px;font-weight:900;font-style:italic;color:#ff3333;text-shadow:0 0 12px rgba(255,50,50,.5),1px 1px 0 #800;letter-spacing:1px;white-space:nowrap;font-family:'Arial Black',sans-serif;flex-shrink:0}
|
||||
.rs-period{font-size:12px;color:#ccc;font-family:monospace;white-space:nowrap;font-weight:700;flex:1;text-align:right}
|
||||
.rs-sound{width:24px;height:24px;border-radius:50%;background:rgba(255,255,255,.1);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;font-size:14px;line-height:1}
|
||||
.rs-toggle{cursor:pointer;font-size:14px;opacity:0.7;transition:transform 0.3s;width:24px;height:24px;border-radius:50%;background:rgba(255,255,255,.1);display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
||||
.race-scene.collapsed .rs-toggle{transform:rotate(180deg)}
|
||||
.race-scene.collapsed .race-track,.race-scene.collapsed .race-skyline,.race-scene.collapsed .race-overlay,.race-scene.collapsed .podium-overlay{display:none}
|
||||
.race-scene.collapsed{min-height:auto;height:auto}
|
||||
.race-scene.collapsed .race-scene-hd{margin-bottom:0}
|
||||
.rs-balls{display:flex;gap:3px;width:100%;justify-content:center;flex-wrap:nowrap}
|
||||
.rs-ball{width:20px;height:20px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.5);flex-shrink:0;border:1px solid rgba(255,255,255,.3);box-shadow:0 2px 4px rgba(0,0,0,.3)}
|
||||
/* 天际线 — 蓝天+北京地标(参考图核心元素)*/
|
||||
.race-skyline{height:72px;background:linear-gradient(180deg,#4a9ed8 0%,#6bb5e0 30%,#8cc8ea 60%,#b8dff0 85%,#d0e8d0 100%);position:relative;overflow:hidden}
|
||||
.race-skyline svg{position:absolute;bottom:0;left:0;width:100%;height:100%}
|
||||
/* 赛道地面 — 绿草+灰色赛道 */
|
||||
.race-track-ground{height:8px;background:linear-gradient(180deg,#5a8a3a,#4a7a2a);position:relative}
|
||||
.race-track-ground::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:repeating-linear-gradient(90deg,#fff 0,#fff 6px,transparent 6px,transparent 12px);opacity:.5}
|
||||
/* 赛道主体 */
|
||||
.race-track{background:#1a1a2e;position:relative;overflow:hidden;min-height:260px}
|
||||
/* 终点线 — 棋盘格(左侧) */
|
||||
.finish-line{position:absolute;left:3%;top:0;bottom:0;width:14px;z-index:2;background:repeating-conic-gradient(#fff 0% 25%,#333 0% 50%) 0 0/7px 7px;opacity:.4}
|
||||
.start-line{position:absolute;right:5%;top:0;bottom:0;width:2px;background:rgba(255,255,255,.15);z-index:2}
|
||||
/* 车道 */
|
||||
.track-lane{height:38px;display:flex;align-items:center;border-bottom:1px solid rgba(255,255,255,.06);position:relative;overflow:hidden}
|
||||
.track-lane:nth-child(even){background:rgba(255,255,255,.02)}
|
||||
.track-lane .progress-bar{position:absolute;right:0;top:0;height:100%;opacity:.08;width:5%;border-radius:4px 0 0 4px;transition:width .3s ease}
|
||||
.lane-label{position:absolute;right:6px;top:50%;transform:translateY(-50%);color:rgba(255,255,255,.12);font-size:9px;z-index:0;font-weight:700}
|
||||
/* 赛道叠加层(倒计时+红绿灯)— 参考图:中央显示,不完全遮住车 */
|
||||
.race-overlay{position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:20;pointer-events:none;gap:14px;background:rgba(10,10,30,.6);opacity:0;transition:opacity .4s}
|
||||
.race-overlay.show{opacity:1}
|
||||
/* 数字倒计时 — 复古LCD数码管风格(参考图核心) */
|
||||
@font-face{font-family:'LCD';src:local('Courier New')}
|
||||
.race-timer-display{font-family:'Courier New','LCD',monospace;font-size:52px;font-weight:900;color:#fff;text-shadow:0 0 30px rgba(255,255,255,.5),0 0 60px rgba(255,255,255,.15);letter-spacing:6px;background:rgba(0,0,0,.6);padding:8px 24px;border-radius:8px;border:2px solid rgba(255,255,255,.1)}
|
||||
.race-timer-display .ms{font-size:28px;opacity:.5;margin-left:2px;vertical-align:baseline}
|
||||
/* 红绿灯 — 5灯椭圆形(参考图风格) */
|
||||
.traffic-lights{display:flex;gap:8px;padding:10px 24px;background:linear-gradient(180deg,#333,#111);border-radius:30px;border:2px solid #555;box-shadow:0 4px 16px rgba(0,0,0,.8)}
|
||||
.tl{width:36px;height:28px;border-radius:14px;background:radial-gradient(ellipse at 45% 40%,#333,#1a1a1a);border:2px solid #555;box-shadow:inset 0 1px 3px rgba(255,255,255,.1);transition:all .15s}
|
||||
.tl.red{background:radial-gradient(ellipse at 40% 35%,#ff5555,#cc0000);box-shadow:0 0 16px rgba(255,0,0,.8),0 0 32px rgba(255,0,0,.3),inset 0 -2px 4px rgba(0,0,0,.3);border-color:#ff3333}
|
||||
.tl.yellow{background:radial-gradient(ellipse at 40% 35%,#ffdd44,#ccaa00);box-shadow:0 0 16px rgba(255,200,0,.8),0 0 32px rgba(255,200,0,.3),inset 0 -2px 4px rgba(0,0,0,.3);border-color:#ffcc33}
|
||||
.tl.green{background:radial-gradient(ellipse at 40% 35%,#44ff66,#00cc33);box-shadow:0 0 16px rgba(0,255,0,.8),0 0 32px rgba(0,255,0,.3),inset 0 -2px 4px rgba(0,0,0,.3);border-color:#33ff55}
|
||||
/* 赛车容器 */
|
||||
.car-container{position:absolute;right:3%;top:50%;transform:translateY(-50%);z-index:10;display:flex;align-items:center;transition:right 0.3s ease}
|
||||
/* 赛车图片 */
|
||||
.car-svg{width:60px;height:auto;flex-shrink:0;filter:drop-shadow(2px 2px 3px rgba(0,0,0,.6));object-fit:contain;pointer-events:none}
|
||||
/* 排气火焰(车尾=右侧) */
|
||||
.car-flame{position:absolute;right:-4px;top:50%;transform:translateY(-50%);width:14px;height:10px;opacity:0;pointer-events:none}
|
||||
.car-flame i{position:absolute;display:block;border-radius:50% 20% 50% 50%;transform-origin:left center}
|
||||
.car-flame i:nth-child(1){width:10px;height:6px;background:#ff5500;right:0;top:2px;animation:flame1 .12s infinite alternate}
|
||||
.car-flame i:nth-child(2){width:7px;height:4px;background:#ff8800;right:3px;top:3px;animation:flame2 .1s infinite alternate}
|
||||
.car-flame i:nth-child(3){width:4px;height:3px;background:#ffcc00;right:5px;top:3.5px;animation:flame3 .08s infinite alternate}
|
||||
@keyframes flame1{0%{opacity:.7;transform:scaleX(1) scaleY(1)}100%{opacity:1;transform:scaleX(1.4) scaleY(.6)}}
|
||||
@keyframes flame2{0%{opacity:.6;transform:scaleX(1)}100%{opacity:.9;transform:scaleX(1.3) translateX(1px)}}
|
||||
@keyframes flame3{0%{opacity:.5;transform:scaleX(1)}100%{opacity:1;transform:scaleX(1.5)}}
|
||||
.car-container.moving .car-flame{opacity:.6}
|
||||
.car-container.sprinting .car-flame{opacity:1;width:22px;right:-10px}
|
||||
/* 风尘粒子(车尾=右侧) */
|
||||
.car-wind{position:absolute;right:-16px;top:50%;transform:translateY(-50%);width:16px;height:20px;overflow:hidden;opacity:0;pointer-events:none}
|
||||
.car-wind i{position:absolute;display:block;height:1px;border-radius:1px;background:rgba(255,255,255,.25)}
|
||||
.car-wind i:nth-child(1){width:6px;top:3px;right:2px;animation:windP .5s infinite linear}
|
||||
.car-wind i:nth-child(2){width:4px;top:8px;right:5px;animation:windP .4s .1s infinite linear}
|
||||
.car-wind i:nth-child(3){width:5px;top:13px;right:1px;animation:windP .45s .2s infinite linear}
|
||||
.car-wind i:nth-child(4){width:3px;top:17px;right:6px;animation:windP .35s .05s infinite linear}
|
||||
@keyframes windP{0%{opacity:.6;transform:translateX(0)}100%{opacity:0;transform:translateX(14px)}}
|
||||
.car-container.moving .car-wind{opacity:.5}
|
||||
.car-container.sprinting .car-wind{opacity:.9;width:28px;right:-24px}
|
||||
/* 冲刺速度线(车尾=右侧) */
|
||||
.car-container.sprinting::before{content:'';position:absolute;right:-30px;top:50%;width:24px;height:1px;background:linear-gradient(270deg,transparent,rgba(255,255,255,.15));transform:translateY(-50%);animation:spdLine .3s infinite}
|
||||
.car-container.sprinting::after{content:'';position:absolute;right:-22px;top:calc(50% + 5px);width:16px;height:1px;background:linear-gradient(270deg,transparent,rgba(255,255,255,.08));transform:translateY(-50%);animation:spdLine .35s .1s infinite}
|
||||
@keyframes spdLine{0%{opacity:0;transform:translateY(-50%) scaleX(.5)}50%{opacity:1}100%{opacity:0;transform:translateY(-50%) scaleX(1) translateX(8px)}}
|
||||
/* 排烟(车尾=右侧) */
|
||||
.exhaust{position:absolute;right:-6px;top:50%;transform:translateY(-50%);font-size:6px;opacity:0;pointer-events:none;color:rgba(255,255,255,.3)}
|
||||
.car-container.moving .exhaust{animation:exhaustSmoke .8s infinite}
|
||||
@keyframes exhaustSmoke{0%{opacity:.6;transform:translateY(-50%) translateX(0) scale(1)}100%{opacity:0;transform:translateY(-50%) translateX(-15px) scale(1.5)}}
|
||||
.car-container.sprinting .race-car{box-shadow:2px 2px 6px rgba(0,0,0,.5),inset 0 1px 0 rgba(255,255,255,.3),-8px 0 20px rgba(255,255,255,.1)}
|
||||
.car-container.sprinting .exhaust{animation:exhaustSmoke .4s infinite}
|
||||
.lane-label{position:absolute;left:4px;top:50%;transform:translateY(-50%);color:rgba(255,255,255,.25);font-size:10px;z-index:0}
|
||||
.rank-badge{position:absolute;right:-24px;top:50%;transform:translateY(-50%);font-size:9px;font-weight:700;color:#ffd700;opacity:0;transition:opacity .5s;text-shadow:0 0 4px rgba(255,215,0,.6)}
|
||||
.car-container.show-rank .rank-badge{opacity:1}
|
||||
@keyframes readyShake{0%,100%{transform:translateY(-50%) translateX(0)}25%{transform:translateY(-50%) translateX(2px)}75%{transform:translateY(-50%) translateX(-2px)}}
|
||||
.car-container.ready{animation:readyShake .3s infinite}
|
||||
.car-container.sprinting .exhaust{animation:exhaustSmoke .35s infinite;font-size:8px}
|
||||
@keyframes exhaustSmoke{0%{opacity:.5;transform:translateY(-50%) translateX(0) scale(1)}100%{opacity:0;transform:translateY(-50%) translateX(18px) scale(2)}}
|
||||
/* ===== 升级特效1:轮下火花粒子(车尾=右侧) ===== */
|
||||
.car-sparks{position:absolute;right:4px;bottom:-2px;width:20px;height:16px;opacity:0;pointer-events:none;overflow:visible}
|
||||
.car-sparks i{position:absolute;display:block;width:3px;height:3px;border-radius:50%;background:#ffaa00;bottom:0;right:50%}
|
||||
.car-sparks i:nth-child(1){animation:spark1 .4s infinite linear;background:#ff6600}
|
||||
.car-sparks i:nth-child(2){animation:spark2 .35s .05s infinite linear;background:#ffcc00}
|
||||
.car-sparks i:nth-child(3){animation:spark3 .45s .1s infinite linear;background:#ff8800}
|
||||
.car-sparks i:nth-child(4){animation:spark4 .38s .15s infinite linear;background:#ffdd44}
|
||||
.car-sparks i:nth-child(5){animation:spark5 .42s .08s infinite linear;background:#ff5500}
|
||||
.car-sparks i:nth-child(6){animation:spark6 .36s .12s infinite linear;background:#ffaa00}
|
||||
@keyframes spark1{0%{opacity:1;transform:translate(0,0) scale(1)}100%{opacity:0;transform:translate(18px,-12px) scale(.3)}}
|
||||
@keyframes spark2{0%{opacity:1;transform:translate(0,0) scale(1)}100%{opacity:0;transform:translate(14px,10px) scale(.2)}}
|
||||
@keyframes spark3{0%{opacity:.9;transform:translate(0,0) scale(.8)}100%{opacity:0;transform:translate(22px,-6px) scale(.2)}}
|
||||
@keyframes spark4{0%{opacity:1;transform:translate(0,0) scale(.7)}100%{opacity:0;transform:translate(16px,8px) scale(.1)}}
|
||||
@keyframes spark5{0%{opacity:.8;transform:translate(0,0) scale(1)}100%{opacity:0;transform:translate(20px,-14px) scale(.2)}}
|
||||
@keyframes spark6{0%{opacity:.9;transform:translate(0,0) scale(.6)}100%{opacity:0;transform:translate(12px,12px) scale(.1)}}
|
||||
.car-container.sprinting .car-sparks{opacity:1}
|
||||
.car-container.moving .car-sparks{opacity:.3}
|
||||
/* ===== 升级特效2:赛道震动 ===== */
|
||||
@keyframes trackShake{0%,100%{transform:translateX(0) translateY(0)}10%{transform:translateX(.5px) translateY(-.3px)}30%{transform:translateX(-.4px) translateY(.5px)}50%{transform:translateX(.3px) translateY(.2px)}70%{transform:translateX(-.5px) translateY(-.4px)}90%{transform:translateX(.4px) translateY(.3px)}}
|
||||
.race-track.shaking{animation:trackShake .15s infinite linear}
|
||||
/* ===== 升级特效3:车身光晕 ===== */
|
||||
.car-container.sprinting .car-svg{filter:drop-shadow(2px 2px 3px rgba(0,0,0,.6)) drop-shadow(0 0 8px var(--car-glow,rgba(255,150,0,.6)))}
|
||||
.car-container.sprinting{z-index:11}
|
||||
/* ===== 升级特效7:刹车烟雾(车尾=右侧) ===== */
|
||||
.car-brake-smoke{position:absolute;right:-2px;bottom:-4px;width:30px;height:20px;opacity:0;pointer-events:none;overflow:visible}
|
||||
.car-brake-smoke i{position:absolute;display:block;border-radius:50%;background:rgba(200,200,200,.4);bottom:2px;right:8px}
|
||||
.car-brake-smoke i:nth-child(1){width:8px;height:8px;animation:brakeSmoke1 .6s ease-out forwards}
|
||||
.car-brake-smoke i:nth-child(2){width:6px;height:6px;animation:brakeSmoke2 .5s .05s ease-out forwards}
|
||||
.car-brake-smoke i:nth-child(3){width:10px;height:8px;animation:brakeSmoke3 .7s .1s ease-out forwards}
|
||||
@keyframes brakeSmoke1{0%{opacity:.6;transform:translate(0,0) scale(.5)}100%{opacity:0;transform:translate(20px,-8px) scale(2.5)}}
|
||||
@keyframes brakeSmoke2{0%{opacity:.5;transform:translate(0,0) scale(.4)}100%{opacity:0;transform:translate(12px,6px) scale(2)}}
|
||||
@keyframes brakeSmoke3{0%{opacity:.4;transform:translate(0,0) scale(.3)}100%{opacity:0;transform:translate(24px,-4px) scale(3)}}
|
||||
.car-container.braking .car-brake-smoke{opacity:1}
|
||||
.car-container.braking .car-flame{opacity:0!important}
|
||||
.car-container.braking .car-sparks{opacity:0!important}
|
||||
/* ===== 升级特效4:冲线爆炸(终点=左侧) ===== */
|
||||
.finish-burst{position:absolute;left:3%;top:0;bottom:0;width:60px;z-index:30;pointer-events:none;opacity:0}
|
||||
.finish-burst.active{opacity:1}
|
||||
.finish-burst::before{content:'';position:absolute;top:50%;left:50%;width:10px;height:10px;background:#fff;border-radius:50%;transform:translate(-50%,-50%);animation:finishFlash .6s ease-out forwards}
|
||||
@keyframes finishFlash{0%{opacity:1;transform:translate(-50%,-50%) scale(1);box-shadow:0 0 20px #fff,0 0 60px #ffd700}50%{opacity:.8;transform:translate(-50%,-50%) scale(8);box-shadow:0 0 40px #fff,0 0 80px #ffd700}100%{opacity:0;transform:translate(-50%,-50%) scale(12)}}
|
||||
.finish-burst .fp{position:absolute;width:4px;height:4px;border-radius:50%;top:50%;left:50%}
|
||||
.finish-burst.active .fp{animation:finishParticle .8s ease-out forwards}
|
||||
.fp:nth-child(1){background:#ff3333;animation-delay:0s}
|
||||
.fp:nth-child(2){background:#3399ff;animation-delay:.02s}
|
||||
.fp:nth-child(3){background:#ffcc00;animation-delay:.04s}
|
||||
.fp:nth-child(4){background:#33ff66;animation-delay:.06s}
|
||||
.fp:nth-child(5){background:#ff66cc;animation-delay:.08s}
|
||||
.fp:nth-child(6){background:#ff8800;animation-delay:.03s}
|
||||
.fp:nth-child(7){background:#66ffff;animation-delay:.05s}
|
||||
.fp:nth-child(8){background:#ffff33;animation-delay:.07s}
|
||||
@keyframes finishParticle{0%{opacity:1;transform:translate(0,0) scale(1)}100%{opacity:0;transform:translate(var(--fpx,20px),var(--fpy,-30px)) scale(.3)}}
|
||||
/* ===== 升级特效5:领奖台彩带雨 ===== */
|
||||
.confetti-container{position:absolute;top:0;left:0;right:0;bottom:0;overflow:hidden;pointer-events:none;z-index:3}
|
||||
.confetti{position:absolute;width:6px;height:10px;top:-20px;opacity:.9}
|
||||
.podium-overlay.show .confetti{animation:confettiFall var(--cf-dur,3s) var(--cf-delay,0s) linear forwards}
|
||||
@keyframes confettiFall{0%{opacity:1;transform:translateY(0) rotateZ(0deg) rotateX(0deg)}25%{transform:translateY(60px) rotateZ(90deg) rotateX(180deg) translateX(15px)}50%{transform:translateY(140px) rotateZ(200deg) rotateX(360deg) translateX(-10px)}75%{opacity:.7;transform:translateY(220px) rotateZ(300deg) rotateX(540deg) translateX(20px)}100%{opacity:0;transform:translateY(320px) rotateZ(400deg) rotateX(720deg) translateX(5px)}}
|
||||
/* 排名徽章(车头=左侧,徽章显示在左边) */
|
||||
.rank-badge{position:absolute;left:-32px;top:50%;transform:translateY(-50%);font-size:10px;font-weight:900;color:#ffd700;opacity:0;transition:opacity .3s,transform .3s;text-shadow:0 0 8px rgba(255,215,0,.8),0 0 16px rgba(255,215,0,.4);letter-spacing:1px}
|
||||
.car-container.show-rank .rank-badge{opacity:1;animation:rankPop .4s ease-out}
|
||||
@keyframes rankPop{0%{transform:translateY(-50%) scale(0);opacity:0}60%{transform:translateY(-50%) scale(1.3)}100%{transform:translateY(-50%) scale(1);opacity:1}}
|
||||
/* 就绪抖动 — 升级:更有力的引擎感 */
|
||||
@keyframes readyShake{0%,100%{transform:translateY(-50%) translateX(0)}15%{transform:translateY(-50%) translateX(1.5px) translateY(calc(-50% + .5px))}35%{transform:translateY(-50%) translateX(-1px) translateY(calc(-50% - .3px))}55%{transform:translateY(-50%) translateX(2px)}75%{transform:translateY(-50%) translateX(-1.5px) translateY(calc(-50% + .3px))}90%{transform:translateY(-50%) translateX(.5px)}}
|
||||
.car-container.ready{animation:readyShake .2s infinite}
|
||||
/* ===== 结算领奖台 ===== */
|
||||
.podium-overlay{position:absolute;top:0;left:0;right:0;bottom:0;z-index:25;display:flex;align-items:center;justify-content:center;background:radial-gradient(ellipse at 50% 60%,#0a2a4a 0%,#061428 50%,#020a14 100%);opacity:0;pointer-events:none;transition:opacity .6s}
|
||||
.podium-overlay.show{opacity:1;pointer-events:auto}
|
||||
/* 星光粒子 */
|
||||
.podium-stars{position:absolute;top:0;left:0;right:0;bottom:0;overflow:hidden}
|
||||
.podium-stars::before,.podium-stars::after{content:'';position:absolute;width:2px;height:2px;border-radius:50%;box-shadow:25px 20px #fff,80px 60px #fff,140px 30px #fff,200px 80px #fff,260px 15px #fff,320px 55px #fff,60px 110px #fff,170px 130px #fff,110px 75px rgba(255,255,255,.5),230px 100px rgba(255,255,255,.5),290px 40px rgba(255,255,255,.3),50px 45px rgba(255,255,255,.3),350px 90px #fff,15px 85px rgba(255,255,255,.4);animation:starsTwinkle 3s infinite alternate}
|
||||
.podium-stars::after{animation-delay:1.5s;box-shadow:35px 90px #fff,95px 25px #fff,155px 110px #fff,215px 45px #fff,275px 85px #fff,335px 20px #fff,75px 70px rgba(255,255,255,.5),185px 35px rgba(255,255,255,.5)}
|
||||
@keyframes starsTwinkle{0%{opacity:.3}50%{opacity:.8}100%{opacity:.4}}
|
||||
/* 光柱 */
|
||||
.podium-overlay.show .podium-stage::before{content:'';position:absolute;top:-80px;left:50%;width:120px;height:200px;transform:translateX(-50%);background:linear-gradient(180deg,rgba(100,200,255,.15),transparent);clip-path:polygon(30% 0%,70% 0%,100% 100%,0% 100%);pointer-events:none;animation:lightPillar 2s ease-in-out infinite alternate}
|
||||
@keyframes lightPillar{0%{opacity:.3}100%{opacity:.6}}
|
||||
/* 领奖台布局 */
|
||||
.podium-stage{display:flex;align-items:flex-end;justify-content:center;gap:6px;position:relative;z-index:2}
|
||||
.podium-car{display:flex;flex-direction:column;align-items:center;opacity:0;transform:translateY(30px) scale(.8)}
|
||||
.podium-overlay.show .podium-1st{animation:podiumIn .6s .3s forwards}
|
||||
.podium-overlay.show .podium-2nd{animation:podiumIn .6s .5s forwards}
|
||||
.podium-overlay.show .podium-3rd{animation:podiumIn .6s .7s forwards}
|
||||
@keyframes podiumIn{to{opacity:1;transform:translateY(0) scale(1)}}
|
||||
.podium-rank{font-weight:900;font-size:20px;color:#c0c0c0;text-shadow:0 2px 8px rgba(0,0,0,.5);margin-bottom:2px;font-style:italic}
|
||||
.podium-rank.gold{font-size:28px;color:#ffd700;text-shadow:0 0 20px rgba(255,215,0,.5),0 2px 4px rgba(0,0,0,.5)}
|
||||
.podium-car-svg{width:80px;height:auto;filter:drop-shadow(0 4px 8px rgba(0,0,0,.5));object-fit:contain}
|
||||
.podium-1st .podium-car-svg{width:110px;height:auto;filter:drop-shadow(0 6px 12px rgba(0,0,0,.5))}
|
||||
/* 赛道地面动态纹理 */
|
||||
.race-track::before{content:'';position:absolute;top:0;left:0;right:0;bottom:0;background:repeating-linear-gradient(90deg,transparent 0,transparent 40px,rgba(255,255,255,.02) 40px,rgba(255,255,255,.02) 42px);animation:roadScroll 1.5s linear infinite;z-index:0;pointer-events:none}
|
||||
@keyframes roadScroll{from{background-position-x:0}to{background-position-x:84px}}
|
||||
/* 车道分割虚线 */
|
||||
.track-lane::before{content:'';position:absolute;bottom:-1px;left:5%;right:10%;height:1px;background:repeating-linear-gradient(90deg,rgba(255,255,255,.1) 0,rgba(255,255,255,.1) 12px,transparent 12px,transparent 24px);animation:dashScroll 2s linear infinite;pointer-events:none}
|
||||
@keyframes dashScroll{from{background-position-x:0}to{background-position-x:48px}}
|
||||
/* 底部信息栏 */
|
||||
.race-scene-ft{display:flex;align-items:center;padding:6px 12px;background:linear-gradient(180deg,#1a1a2e,#0f0f1e);border-top:1px solid rgba(255,255,255,.06);gap:12px;font-size:11px;color:#aaa;flex-wrap:wrap}
|
||||
.race-ft-period{font-family:monospace;font-weight:700;color:#fff}
|
||||
.race-ft-tag{display:inline-flex;align-items:center;gap:3px;padding:1px 6px;border-radius:3px;font-weight:600;font-size:10px}
|
||||
.race-ft-tag.sum-tag{background:rgba(30,144,255,.15);color:#6ab5ff}
|
||||
.race-ft-tag.dt-tag{color:#ffaa33}
|
||||
/* 快捷投注面板 */
|
||||
.quick-section{background:#fff;border-radius:10px;padding:10px 12px;margin-bottom:8px;box-shadow:0 1px 3px rgba(0,0,0,.05)}
|
||||
.quick-title{font-size:12px;color:var(--text3);font-weight:600;margin-bottom:8px}
|
||||
.quick-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}
|
||||
.quick-btn{padding:14px 8px;border-radius:10px;text-align:center;font-size:14px;font-weight:700;color:#fff;cursor:pointer;transition:all .15s;user-select:none}
|
||||
.quick-btn:active{transform:scale(.96);opacity:.85}
|
||||
.qb-red{background:linear-gradient(135deg,#e74c3c,#c0392b)}
|
||||
.qb-blue{background:linear-gradient(135deg,#3498db,#2980b9)}
|
||||
.qb-orange{background:linear-gradient(135deg,#f39c12,#d68910)}
|
||||
.qb-purple{background:linear-gradient(135deg,#9b59b6,#7d3c98)}
|
||||
.quick-hint{text-align:center;font-size:11px;color:#bbb;margin-top:4px}
|
||||
/* 遗漏统计面板 */
|
||||
.miss-tabs{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:8px}
|
||||
.miss-tab{padding:4px 10px;border-radius:16px;font-size:12px;color:#666;background:#f0f0f0;cursor:pointer;font-weight:600;transition:all .15s}
|
||||
.miss-tab.active{background:var(--primary);color:#fff}
|
||||
.miss-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}
|
||||
.miss-cell{background:#fff;border-radius:8px;padding:8px 4px;text-align:center;box-shadow:0 1px 3px rgba(0,0,0,.05)}
|
||||
.miss-num{font-size:11px;font-weight:700;margin-bottom:4px}
|
||||
.miss-val{font-size:16px;font-weight:700;color:var(--primary)}
|
||||
.miss-val.hot{color:var(--danger)}
|
||||
.miss-val.cold{color:var(--text3)}
|
||||
.miss-section{background:#fff;border-radius:10px;padding:10px 12px;box-shadow:0 1px 3px rgba(0,0,0,.05)}
|
||||
.miss-label{font-size:12px;color:var(--text3);font-weight:600;margin-bottom:6px}
|
||||
.miss-two-side{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}
|
||||
.miss-ts-item{display:flex;align-items:center;justify-content:space-between;padding:6px 10px;background:#f9f9f9;border-radius:6px}
|
||||
.miss-ts-name{font-size:12px;color:#666}
|
||||
.miss-ts-val{font-size:14px;font-weight:700;color:var(--primary)}
|
||||
/* 长龙统计面板 */
|
||||
.dragon-empty{padding:30px 0;text-align:center;color:#999;font-size:13px}
|
||||
.dragon-item{display:flex;align-items:center;gap:8px;padding:8px 10px;background:#fff;border-radius:10px;margin-bottom:6px;box-shadow:0 1px 3px rgba(0,0,0,.05)}
|
||||
.dragon-rank{font-size:12px;color:var(--primary);font-weight:600;min-width:48px;flex-shrink:0}
|
||||
.dragon-type{display:inline-block;padding:2px 8px;border-radius:4px;color:#fff;font-size:11px;font-weight:600;min-width:32px;text-align:center}
|
||||
.dragon-bar-wrap{flex:1;height:16px;background:#f0f0f0;border-radius:8px;overflow:hidden;position:relative}
|
||||
.dragon-bar{height:100%;border-radius:8px;transition:width .4s ease;min-width:16px}
|
||||
.dragon-count{font-size:13px;font-weight:700;color:var(--text);min-width:28px;text-align:right}
|
||||
.dragon-fire{font-size:11px;margin-left:-2px}
|
||||
/* 开奖视频入口按钮 */
|
||||
.race-entry{display:flex;align-items:center;gap:8px;padding:8px 12px;margin:6px 0;background:#fff;border-radius:10px;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.08);border:1px solid #eee;transition:box-shadow .2s}
|
||||
.race-entry:active{box-shadow:0 1px 2px rgba(0,0,0,.05)}
|
||||
.race-entry-thumb{width:80px;height:48px;border-radius:6px;background:linear-gradient(135deg,#1a1a2e,#2a2a4e);display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;flex-shrink:0}
|
||||
.race-entry-thumb::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,#4a9ed8 0%,#6bb5e0 50%,#3a6a3a 100%);opacity:.3}
|
||||
.race-entry-thumb svg{position:relative;z-index:1}
|
||||
.race-entry-info{flex:1;min-width:0}
|
||||
.race-entry-title{font-size:13px;font-weight:700;color:#333}
|
||||
.race-entry-desc{font-size:11px;color:#999;margin-top:2px}
|
||||
.race-entry-live{background:#cc0000;color:#fff;font-size:9px;font-weight:700;padding:1px 5px;border-radius:3px;letter-spacing:1px}
|
||||
/* 弹窗遮罩 */
|
||||
.race-modal-mask{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.6);z-index:100;display:none;align-items:flex-start;justify-content:center;padding-top:40px}
|
||||
.race-modal-mask.show{display:flex}
|
||||
/* 弹窗内容 */
|
||||
.race-modal{width:100%;max-width:480px;max-height:90vh;overflow-y:auto;border-radius:12px;box-shadow:0 8px 40px rgba(0,0,0,.4);position:relative;animation:modalIn .25s ease}
|
||||
@keyframes modalIn{from{opacity:0;transform:translateY(-20px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}
|
||||
/* 弹窗顶栏 */
|
||||
.race-modal-hd{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:#1a1a2e;border-radius:12px 12px 0 0;border-bottom:1px solid rgba(255,255,255,.08)}
|
||||
.race-modal-title{font-size:14px;font-weight:700;color:#fff}
|
||||
.race-modal-close{display:flex;align-items:center;gap:6px;color:#aaa;font-size:12px;cursor:pointer;background:none;border:none;padding:4px 8px;border-radius:4px}
|
||||
.race-modal-close:active{background:rgba(255,255,255,.1)}
|
||||
/* 顶部栏 */
|
||||
.pk-header{position:sticky;top:0;z-index:40;background:#fff;border-bottom:1px solid #F0F0F0;padding:10px 15px;display:flex;align-items:center;justify-content:space-between}
|
||||
.pk-header .title{font-weight:700;font-size:16px;color:#333;display:flex;align-items:center;gap:6px}
|
||||
@@ -49,33 +263,55 @@
|
||||
.seal-status{color:var(--danger);font-weight:600;font-size:13px}
|
||||
.draw-label{font-size:13px;font-weight:700;color:#333}
|
||||
.draw-countdown{color:var(--primary);font-weight:700;font-size:15px;font-family:monospace}
|
||||
/* 左右布局 */
|
||||
/* 左右布局(参考图 dsn 风格) */
|
||||
.bet-layout{display:flex;min-height:50vh}
|
||||
.side-menu{width:72px;background:#fff;border-right:1px solid #F0F0F0;flex-shrink:0}
|
||||
.side-menu .mi{display:flex;align-items:center;justify-content:center;height:48px;font-size:13px;color:#666;cursor:pointer;border-left:3px solid transparent;transition:all .2s}
|
||||
.side-menu .mi{display:flex;align-items:center;justify-content:center;height:44px;font-size:13px;color:#666;cursor:pointer;border-left:3px solid transparent;transition:all .2s}
|
||||
.side-menu .mi.active{color:var(--primary);font-weight:600;border-left-color:var(--primary);background:#F0F8FF}
|
||||
.bet-content{flex:1;overflow-y:auto;max-height:60vh;background:#fff}
|
||||
/* 投注行 */
|
||||
.bet-section-title{text-align:center;padding:10px 0 6px;font-size:14px;font-weight:600;color:#333;border-bottom:1px solid #F0F0F0;position:relative}
|
||||
.bet-section-title::after{content:'▲';font-size:8px;color:#999;margin-left:4px}
|
||||
/* 投注行(参考图风格 — 宽松、清爽) */
|
||||
.bet-section-title{text-align:center;padding:12px 0 8px;font-size:14px;font-weight:600;color:#333;border-bottom:1px solid #F0F0F0;position:relative}
|
||||
.bet-section-title::after{content:'▲';font-size:8px;color:#bbb;margin-left:4px;vertical-align:middle}
|
||||
.bet-row{display:flex;border-bottom:1px solid #F5F5F5}
|
||||
.bet-cell{flex:1;display:flex;align-items:center;justify-content:center;gap:8px;padding:14px 10px;cursor:pointer;font-size:14px;color:#333;transition:background .15s}
|
||||
.bet-cell{flex:1;display:flex;align-items:center;justify-content:center;gap:8px;padding:16px 10px;cursor:pointer;font-size:14px;color:#333;transition:background .15s}
|
||||
.bet-cell:first-child{border-right:1px solid #F5F5F5}
|
||||
.bet-cell:active,.bet-cell.selected{background:#E8F4FF}
|
||||
.bet-cell:active{background:#E8F4FF}
|
||||
.bet-cell .odds{color:var(--primary);font-weight:600;font-size:14px}
|
||||
/* 筹码 */
|
||||
.chip-bar{background:#fff;padding:8px 15px;border-top:1px solid #F0F0F0;display:flex;align-items:center;gap:8px}
|
||||
.chip{border-radius:50%;width:40px;height:40px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;cursor:pointer;transition:transform .15s}
|
||||
.chip.active{transform:scale(1.15);box-shadow:0 0 0 2px var(--primary)}
|
||||
/* 提交栏 */
|
||||
.submit-bar{background:#fff;padding:10px 15px;border-top:1px solid #F0F0F0;display:flex;align-items:center;gap:10px}
|
||||
.submit-bar .info{flex:1;font-size:12px;color:#999}
|
||||
.submit-bar .info b{color:var(--primary)}
|
||||
/* 底部投注操作区 — 参考图 dsn 风格(在底部导航上方) */
|
||||
.bet-bottom-bar{position:fixed;bottom:60px;left:0;right:0;z-index:50;background:#fff;box-shadow:0 -2px 12px rgba(0,0,0,.1)}
|
||||
/* 已选N注浮动胶囊 */
|
||||
.bet-float-badge{position:absolute;top:-22px;left:50%;transform:translateX(-50%);background:#FF8C00;color:#fff;font-size:12px;font-weight:700;padding:4px 16px;border-radius:20px 20px 0 0;display:flex;align-items:center;gap:6px;white-space:nowrap;box-shadow:0 -2px 8px rgba(255,140,0,.3);cursor:pointer;transition:opacity .2s}
|
||||
.bet-float-badge.hide{opacity:0;pointer-events:none}
|
||||
.bet-float-badge .count{font-size:16px}
|
||||
.bet-float-badge svg{width:18px;height:18px}
|
||||
/* 金额按钮行 */
|
||||
.amount-row{display:flex;align-items:center;gap:6px;padding:8px 12px;border-top:1px solid #F0F0F0}
|
||||
.amount-btn{flex:1;height:36px;display:flex;align-items:center;justify-content:center;border:1px solid #ddd;border-radius:6px;font-size:14px;font-weight:600;color:#333;background:#fff;cursor:pointer;transition:all .15s}
|
||||
.amount-btn.active{border-color:var(--primary);color:var(--primary);background:#F0F8FF}
|
||||
.amount-btn:active{background:#F0F0F0}
|
||||
.amount-custom{width:36px;height:36px;flex:none;border-radius:50%;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;cursor:pointer;background:#fff;color:#1E90FF}
|
||||
/* 输入行 */
|
||||
.bet-input-row{display:flex;align-items:center;gap:8px;padding:6px 12px}
|
||||
.bet-input-row input{flex:1;height:36px;border:1px solid #ddd;border-radius:6px;padding:0 12px;font-size:13px;color:#333;outline:none}
|
||||
.bet-input-row input:focus{border-color:var(--primary)}
|
||||
.bet-input-row input::placeholder{color:#bbb}
|
||||
.bet-preset-btn{height:36px;padding:0 12px;border:1px solid #ddd;border-radius:18px;font-size:12px;color:#666;background:#fff;cursor:pointer;display:flex;align-items:center;gap:4px}
|
||||
/* 取消确认按钮行 */
|
||||
.bet-action-row{display:flex;gap:10px;padding:6px 12px 10px}
|
||||
.bet-action-row .btn-cancel{flex:1;height:40px;border:1px solid #ddd;border-radius:8px;font-size:15px;font-weight:600;color:#666;background:#fff;cursor:pointer}
|
||||
.bet-action-row .btn-confirm{flex:1;height:40px;border:none;border-radius:8px;font-size:15px;font-weight:600;color:#fff;background:linear-gradient(135deg,var(--primary),var(--primary-dark));cursor:pointer;box-shadow:0 2px 8px rgba(30,144,255,.3)}
|
||||
.bet-action-row .btn-confirm:active{transform:scale(.98)}
|
||||
/* 选中态 — 蓝色边框 + 勾勾(参考图) */
|
||||
.bet-cell.selected{background:#E8F4FF;border:1px solid var(--primary);position:relative}
|
||||
.bet-cell.selected::after{content:'✓';position:absolute;top:2px;right:4px;width:18px;height:18px;background:var(--primary);color:#fff;font-size:10px;border-radius:0 0 0 6px;display:flex;align-items:center;justify-content:center}
|
||||
/* 投注金额角标 */
|
||||
.bet-amount-badge{position:absolute;top:-4px;right:-4px;background:var(--danger);color:#fff;font-size:9px;font-weight:700;min-width:18px;height:18px;border-radius:9px;display:flex;align-items:center;justify-content:center;padding:0 4px;z-index:2}
|
||||
.bet-cell,.bet-btn{position:relative}
|
||||
/* 冠亚和结果角标 */
|
||||
.section-hot-badge{display:inline-flex;align-items:center;justify-content:center;background:var(--danger);color:#fff;font-size:10px;font-weight:700;min-width:20px;height:20px;border-radius:10px;padding:0 5px;margin-left:6px;vertical-align:middle}
|
||||
</style>
|
||||
</head>
|
||||
<body class="pb-nav" style="background:#F5F5F5">
|
||||
<body class="pb-nav" style="background:#F5F5F5;padding-bottom:240px">
|
||||
|
||||
<!-- 顶部栏 -->
|
||||
<header class="pk-header">
|
||||
@@ -117,33 +353,191 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3D赛车动画区 — 完整保留 -->
|
||||
<div class="race-wrapper">
|
||||
<div class="race-track p-2" id="raceArea">
|
||||
<div class="start-line"></div>
|
||||
<div class="finish-line"></div>
|
||||
<?php for($i=1;$i<=10;$i++): ?>
|
||||
<div class="track-lane" id="trackLane<?=$i?>">
|
||||
<div class="progress-bar c<?=$i?>" id="bar<?=$i?>"></div>
|
||||
<span class="lane-label">#<?=$i?></span>
|
||||
<div class="car-container" id="carContainer<?=$i?>">
|
||||
<div class="race-car rc<?=$i?>"><?=$i?></div>
|
||||
<span class="exhaust">•</span>
|
||||
<span class="rank-badge" id="rankBadge<?=$i?>"></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endfor; ?>
|
||||
<?php $hasVideo=!empty($game['stream_url']); ?>
|
||||
<?php if($hasVideo): ?>
|
||||
<div id="pk10VideoWrap" style="display:none">
|
||||
<div id="pk10VideoPlayer" data-stream="<?=htmlspecialchars($game['stream_url'])?>" style="width:100%;aspect-ratio:16/9;background:#000"></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- 开奖视频入口(点击打开弹窗) -->
|
||||
<div class="race-entry" id="raceEntryBtn" onclick="openRaceModal()">
|
||||
<div class="race-entry-thumb">
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="#fff" opacity=".8"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
</div>
|
||||
<div class="race-entry-info">
|
||||
<div class="race-entry-title">F1赛车开奖视频 <span class="race-entry-live">LIVE</span></div>
|
||||
<div class="race-entry-desc" id="raceEntryDesc">点击查看开奖动画</div>
|
||||
</div>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</div>
|
||||
|
||||
<!-- 左右布局投注面板 -->
|
||||
<!-- 开奖动画弹窗 -->
|
||||
<div class="race-modal-mask" id="raceModalMask" onclick="if(event.target===this)closeRaceModal()">
|
||||
<div class="race-modal">
|
||||
<!-- 弹窗顶栏 -->
|
||||
<div class="race-modal-hd">
|
||||
<span class="race-modal-title">F1赛车开奖视频</span>
|
||||
<button class="race-modal-close" onclick="closeRaceModal()">小屏 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
|
||||
</div>
|
||||
<!-- 赛车场景(移入弹窗内) -->
|
||||
<div class="race-scene" id="raceSceneWrap" style="margin:0;border-radius:0">
|
||||
<!-- 场景顶栏 -->
|
||||
<div class="race-scene-hd">
|
||||
<div class="rs-row1">
|
||||
<span class="rs-title">F1赛车</span>
|
||||
<span class="rs-period" id="raceScenePeriod">期数:---</span>
|
||||
<span class="rs-sound" id="rsSoundBtn" onclick="toggleRaceSound()" style="cursor:pointer">🔊</span>
|
||||
<span class="rs-toggle" onclick="toggleRaceScene()" title="收起/展开">▲</span>
|
||||
</div>
|
||||
<div class="rs-balls" id="raceSceneBalls"><span style="color:#666;font-size:10px">---</span></div>
|
||||
</div>
|
||||
<!-- 天际线 — 蓝天 + 北京地标(鸟巢、水立方、央视大楼、国家大剧院、冰丝带) -->
|
||||
<div class="race-skyline">
|
||||
<svg viewBox="0 0 500 72" preserveAspectRatio="xMidYMax slice" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 远景云 -->
|
||||
<ellipse cx="80" cy="15" rx="40" ry="8" fill="rgba(255,255,255,.3)"/>
|
||||
<ellipse cx="350" cy="12" rx="30" ry="6" fill="rgba(255,255,255,.25)"/>
|
||||
<ellipse cx="450" cy="20" rx="25" ry="5" fill="rgba(255,255,255,.2)"/>
|
||||
<!-- 国家大剧院(左侧椭圆穹顶)-->
|
||||
<ellipse cx="60" cy="60" rx="35" ry="18" fill="#8ab5cc" stroke="#a0c8d8" stroke-width=".5"/>
|
||||
<ellipse cx="60" cy="60" rx="28" ry="14" fill="#9bc5d8" opacity=".5"/>
|
||||
<line x1="60" y1="42" x2="60" y2="60" stroke="rgba(255,255,255,.1)" stroke-width=".5"/>
|
||||
<!-- 鸟巢(中左侧编织体育馆)-->
|
||||
<path d="M140,68 L142,40 Q155,32 168,32 Q181,32 194,40 L196,68 Z" fill="#9aaa8a" stroke="#b0c0a0" stroke-width=".5"/>
|
||||
<path d="M145,45 Q168,36 191,45" fill="none" stroke="rgba(255,255,255,.15)" stroke-width="1"/>
|
||||
<path d="M148,50 Q168,42 188,50" fill="none" stroke="rgba(255,255,255,.1)" stroke-width=".8"/>
|
||||
<path d="M150,56 Q168,49 186,56" fill="none" stroke="rgba(255,255,255,.08)" stroke-width=".6"/>
|
||||
<!-- 交叉编织纹理 -->
|
||||
<line x1="155" y1="35" x2="175" y2="65" stroke="rgba(255,255,255,.06)" stroke-width=".5"/>
|
||||
<line x1="168" y1="32" x2="150" y2="65" stroke="rgba(255,255,255,.06)" stroke-width=".5"/>
|
||||
<line x1="180" y1="35" x2="190" y2="65" stroke="rgba(255,255,255,.06)" stroke-width=".5"/>
|
||||
<!-- 水立方(蓝色方盒子)-->
|
||||
<rect x="220" y="38" width="50" height="30" rx="2" fill="#5ba8d0" stroke="#70bce0" stroke-width=".5"/>
|
||||
<!-- 气泡纹理 -->
|
||||
<circle cx="230" cy="45" r="3" fill="rgba(255,255,255,.1)" stroke="rgba(255,255,255,.08)" stroke-width=".3"/>
|
||||
<circle cx="240" cy="50" r="4" fill="rgba(255,255,255,.08)" stroke="rgba(255,255,255,.06)" stroke-width=".3"/>
|
||||
<circle cx="250" cy="44" r="3.5" fill="rgba(255,255,255,.1)" stroke="rgba(255,255,255,.08)" stroke-width=".3"/>
|
||||
<circle cx="260" cy="52" r="3" fill="rgba(255,255,255,.08)" stroke="rgba(255,255,255,.06)" stroke-width=".3"/>
|
||||
<circle cx="235" cy="58" r="3.5" fill="rgba(255,255,255,.06)" stroke="rgba(255,255,255,.05)" stroke-width=".3"/>
|
||||
<circle cx="248" cy="56" r="2.5" fill="rgba(255,255,255,.1)" stroke="rgba(255,255,255,.06)" stroke-width=".3"/>
|
||||
<circle cx="258" cy="42" r="2" fill="rgba(255,255,255,.12)"/>
|
||||
<!-- 央视大楼(Z字形/门框双塔)-->
|
||||
<rect x="310" y="22" width="12" height="46" fill="#7a8a9a" stroke="#90a0b0" stroke-width=".3"/>
|
||||
<rect x="340" y="22" width="12" height="46" fill="#7a8a9a" stroke="#90a0b0" stroke-width=".3"/>
|
||||
<rect x="310" y="22" width="42" height="10" rx="1" fill="#8595a5" stroke="#90a0b0" stroke-width=".3"/>
|
||||
<path d="M322,32 L340,32" stroke="rgba(255,255,255,.1)" stroke-width=".5"/>
|
||||
<!-- 窗户 -->
|
||||
<rect x="313" y="35" width="3" height="3" fill="rgba(255,255,200,.1)" rx=".5"/>
|
||||
<rect x="313" y="41" width="3" height="3" fill="rgba(255,255,200,.15)" rx=".5"/>
|
||||
<rect x="313" y="47" width="3" height="3" fill="rgba(255,255,200,.1)" rx=".5"/>
|
||||
<rect x="343" y="35" width="3" height="3" fill="rgba(255,255,200,.12)" rx=".5"/>
|
||||
<rect x="343" y="41" width="3" height="3" fill="rgba(255,255,200,.1)" rx=".5"/>
|
||||
<rect x="343" y="47" width="3" height="3" fill="rgba(255,255,200,.15)" rx=".5"/>
|
||||
<!-- 冰丝带(右侧弧形建筑)-->
|
||||
<path d="M400,68 L402,42 Q410,30 425,28 Q440,30 448,42 L450,68 Z" fill="#a0c8e0" stroke="#b0d8f0" stroke-width=".5"/>
|
||||
<path d="M405,35 Q425,25 445,35" fill="none" stroke="rgba(255,255,255,.2)" stroke-width="1.5"/>
|
||||
<path d="M408,42 Q425,34 442,42" fill="none" stroke="rgba(255,255,255,.12)" stroke-width="1"/>
|
||||
<!-- 远景小楼群 -->
|
||||
<rect x="105" y="55" width="8" height="13" fill="#7a9aaa" opacity=".5"/>
|
||||
<rect x="115" y="50" width="6" height="18" fill="#7090a0" opacity=".5"/>
|
||||
<rect x="285" y="52" width="10" height="16" fill="#7a8a9a" opacity=".4"/>
|
||||
<rect x="375" y="48" width="8" height="20" fill="#7a90a0" opacity=".4"/>
|
||||
<rect x="385" y="52" width="6" height="16" fill="#8aa0b0" opacity=".3"/>
|
||||
<!-- 地面线 -->
|
||||
<rect x="0" y="68" width="500" height="4" fill="#5a8a3a" opacity=".8"/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- 草地分割线 -->
|
||||
<div class="race-track-ground"></div>
|
||||
<!-- 赛道主体 -->
|
||||
<div class="race-track" id="raceArea" style="position:relative">
|
||||
<div class="start-line"></div>
|
||||
<div class="finish-line"></div>
|
||||
<!-- 冲线爆炸特效 -->
|
||||
<div class="finish-burst" id="finishBurst">
|
||||
<span class="fp" style="--fpx:25px;--fpy:-35px"></span>
|
||||
<span class="fp" style="--fpx:-20px;--fpy:-28px"></span>
|
||||
<span class="fp" style="--fpx:30px;--fpy:20px"></span>
|
||||
<span class="fp" style="--fpx:-15px;--fpy:32px"></span>
|
||||
<span class="fp" style="--fpx:18px;--fpy:-42px"></span>
|
||||
<span class="fp" style="--fpx:-28px;--fpy:15px"></span>
|
||||
<span class="fp" style="--fpx:35px;--fpy:8px"></span>
|
||||
<span class="fp" style="--fpx:-10px;--fpy:-20px"></span>
|
||||
</div>
|
||||
<!-- 倒计时+红绿灯叠加层 -->
|
||||
<div class="race-overlay" id="raceOverlay">
|
||||
<div class="race-timer-display" id="raceTimerDisplay">00:00</div>
|
||||
<div class="traffic-lights" id="trafficLights">
|
||||
<span class="tl" id="tl1"></span><span class="tl" id="tl2"></span>
|
||||
<span class="tl" id="tl3"></span><span class="tl" id="tl4"></span>
|
||||
<span class="tl" id="tl5"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 结算领奖台叠加层 -->
|
||||
<div class="podium-overlay" id="podiumOverlay">
|
||||
<div class="podium-stars"></div>
|
||||
<!-- 彩带雨 -->
|
||||
<div class="confetti-container" id="confettiBox"></div>
|
||||
<div class="podium-stage">
|
||||
<div class="podium-car podium-2nd" id="podium2nd">
|
||||
<span class="podium-rank">2nd</span>
|
||||
<img class="podium-car-svg" id="podiumImg2" src="" alt="2nd" draggable="false">
|
||||
</div>
|
||||
<div class="podium-car podium-1st" id="podium1st">
|
||||
<span class="podium-rank gold">1st</span>
|
||||
<img class="podium-car-svg" id="podiumImg1" src="" alt="1st" draggable="false">
|
||||
</div>
|
||||
<div class="podium-car podium-3rd" id="podium3rd">
|
||||
<span class="podium-rank">3rd</span>
|
||||
<img class="podium-car-svg" id="podiumImg3" src="" alt="3rd" draggable="false">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
$carColors = [
|
||||
1 =>['#f1c40f','#d4ac0d'],2 =>['#3498db','#2980b9'],3 =>['#555555','#333333'],
|
||||
4 =>['#e67e22','#ca6f1e'],5 =>['#2ecc71','#27ae60'],6 =>['#2c3e99','#1a2a6c'],
|
||||
7 =>['#999999','#777777'],8 =>['#e74c3c','#c0392b'],9 =>['#cc2222','#991111'],
|
||||
10=>['#27ae60','#1e8449']
|
||||
];
|
||||
for($i=1;$i<=10;$i++): $c=$carColors[$i]; ?>
|
||||
<div class="track-lane" id="trackLane<?=$i?>">
|
||||
<div class="progress-bar c<?=$i?>" id="bar<?=$i?>"></div>
|
||||
<span class="lane-label">#<?=$i?></span>
|
||||
<div class="car-container" id="carContainer<?=$i?>" style="--car-glow:<?=$c[0]?>80">
|
||||
<div class="car-flame"><i></i><i></i><i></i></div>
|
||||
<div class="car-wind"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="car-sparks"><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
<div class="car-brake-smoke"><i></i><i></i><i></i></div>
|
||||
<img class="car-svg" src="/Static/img/cars/car<?=$i?>.png" alt="Car <?=$i?>" draggable="false">
|
||||
<span class="exhaust">•</span>
|
||||
<span class="rank-badge" id="rankBadge<?=$i?>"></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
<!-- 底部信息栏(参考图风格:期号 + 冠亚和 + 龙虎) -->
|
||||
<div class="race-scene-ft" id="raceSceneFt">
|
||||
<span>期号:<span class="race-ft-period" id="ftPeriodNum">---</span></span>
|
||||
<span>冠亚和 <span class="race-ft-tag sum-tag" id="ftSumVal">-</span> <span class="race-ft-tag sum-tag" id="ftSumBs">-</span> <span class="race-ft-tag sum-tag" id="ftSumOe">-</span></span>
|
||||
<span>龙虎 <span id="ftDtTags" style="display:inline-flex;gap:2px">---</span></span>
|
||||
</div>
|
||||
</div><!-- /race-scene -->
|
||||
</div><!-- /race-modal -->
|
||||
</div><!-- /race-modal-mask -->
|
||||
|
||||
<!-- 投注面板(参考图:左侧菜单 + 右侧内容) -->
|
||||
<div id="betPanel">
|
||||
<div class="bet-layout">
|
||||
<!-- 左侧菜单 -->
|
||||
<div class="side-menu">
|
||||
<div class="side-menu" id="sideMenu">
|
||||
<div class="mi active" onclick="showBetTab('bs',this)" id="tabBs"><?=$t('two_side')?></div>
|
||||
<div class="mi" onclick="showBetTab('rank',this)" id="tabRank"><?=$t('rank_1_10')?></div>
|
||||
<div class="mi" onclick="showBetTab('sum',this)" id="tabSum"><?=$t('sum_bs_tab')?></div>
|
||||
<div class="mi" onclick="showBetTab('quick',this)" id="tabQuick"><?=$t('quick')?></div>
|
||||
<div class="mi" onclick="showBetTab('dragon',this)" id="tabDragon"><?=$t('streak')?></div>
|
||||
<div class="mi" onclick="showBetTab('miss',this)" id="tabMiss"><?=$t('missing')?></div>
|
||||
</div>
|
||||
<!-- 右侧投注内容 -->
|
||||
<div class="bet-content">
|
||||
@@ -151,30 +545,31 @@
|
||||
<!-- 两面(大小+单双+龙虎)-->
|
||||
<div id="panelBs">
|
||||
<!-- 冠亚和 大小单双 -->
|
||||
<div class="bet-section-title"><?=$t('sum')?></div>
|
||||
<?php $odds=function($t,$v)use($oddsMap){return $oddsMap[$t.'_'.$v]??1.95;}; ?>
|
||||
<div class="bet-section-title"><?=$t('sum')?><span class="section-hot-badge" id="sumHotBadge" style="display:none"></span></div>
|
||||
<div class="bet-row">
|
||||
<div class="bet-cell" onclick="addBet('sum_bs','sum_big',this)"><?=$t('sum_bs_big')?> <span class="odds">2.2</span></div>
|
||||
<div class="bet-cell" onclick="addBet('sum_bs','sum_small',this)"><?=$t('sum_bs_small')?> <span class="odds">1.79</span></div>
|
||||
<div class="bet-cell" onclick="addBet('sum_bs','sum_big',this)"><?=$t('sum_bs_big')?> <span class="odds"><?=$odds('sum_bs','sum_big')?></span></div>
|
||||
<div class="bet-cell" onclick="addBet('sum_bs','sum_small',this)"><?=$t('sum_bs_small')?> <span class="odds"><?=$odds('sum_bs','sum_small')?></span></div>
|
||||
</div>
|
||||
<div class="bet-row">
|
||||
<div class="bet-cell" onclick="addBet('sum_bs','sum_odd',this)"><?=$t('sum_bs_odd')?> <span class="odds">1.79</span></div>
|
||||
<div class="bet-cell" onclick="addBet('sum_bs','sum_even',this)"><?=$t('sum_bs_even')?> <span class="odds">2.2</span></div>
|
||||
<div class="bet-cell" onclick="addBet('sum_bs','sum_odd',this)"><?=$t('sum_bs_odd')?> <span class="odds"><?=$odds('sum_bs','sum_odd')?></span></div>
|
||||
<div class="bet-cell" onclick="addBet('sum_bs','sum_even',this)"><?=$t('sum_bs_even')?> <span class="odds"><?=$odds('sum_bs','sum_even')?></span></div>
|
||||
</div>
|
||||
<?php for($r=1;$r<=10;$r++): $label=$r<=2?($r==1?$t('champion'):$t('runner_up')):$t('rank_n',['n'=>$r]); ?>
|
||||
<!-- 名次标题 -->
|
||||
<div class="bet-section-title"><?=$label?></div>
|
||||
<div class="bet-row">
|
||||
<div class="bet-cell" onclick="addBet('bs','rank<?=$r?>_big',this)"><?=$t('big')?> <span class="odds">1.995</span></div>
|
||||
<div class="bet-cell" onclick="addBet('bs','rank<?=$r?>_small',this)"><?=$t('small')?> <span class="odds">1.995</span></div>
|
||||
<div class="bet-cell" onclick="addBet('bs','rank<?=$r?>_big',this)"><?=$t('big')?> <span class="odds"><?=$odds('bs','rank'.$r.'_big')?></span></div>
|
||||
<div class="bet-cell" onclick="addBet('bs','rank<?=$r?>_small',this)"><?=$t('small')?> <span class="odds"><?=$odds('bs','rank'.$r.'_small')?></span></div>
|
||||
</div>
|
||||
<div class="bet-row">
|
||||
<div class="bet-cell" onclick="addBet('oe','rank<?=$r?>_odd',this)"><?=$t('odd')?> <span class="odds">1.995</span></div>
|
||||
<div class="bet-cell" onclick="addBet('oe','rank<?=$r?>_even',this)"><?=$t('even')?> <span class="odds">1.995</span></div>
|
||||
<div class="bet-cell" onclick="addBet('oe','rank<?=$r?>_odd',this)"><?=$t('odd')?> <span class="odds"><?=$odds('oe','rank'.$r.'_odd')?></span></div>
|
||||
<div class="bet-cell" onclick="addBet('oe','rank<?=$r?>_even',this)"><?=$t('even')?> <span class="odds"><?=$odds('oe','rank'.$r.'_even')?></span></div>
|
||||
</div>
|
||||
<?php if($r<=5): $pairs=[[1,10],[2,9],[3,8],[4,7],[5,6]]; $p=$pairs[$r-1]; ?>
|
||||
<div class="bet-row">
|
||||
<div class="bet-cell" onclick="addBet('dt','dt<?=$r?>_dragon',this)"><?=$t('dragon')?> <span class="odds">1.995</span></div>
|
||||
<div class="bet-cell" onclick="addBet('dt','dt<?=$r?>_tiger',this)"><?=$t('tiger')?> <span class="odds">1.995</span></div>
|
||||
<div class="bet-cell" onclick="addBet('dt','dt<?=$r?>_dragon',this)"><?=$t('dragon')?> <span class="odds"><?=$odds('dt','dt'.$r.'_dragon')?></span></div>
|
||||
<div class="bet-cell" onclick="addBet('dt','dt<?=$r?>_tiger',this)"><?=$t('tiger')?> <span class="odds"><?=$odds('dt','dt'.$r.'_tiger')?></span></div>
|
||||
</div>
|
||||
<?php endif; endfor; ?>
|
||||
</div>
|
||||
@@ -186,9 +581,10 @@
|
||||
<div style="font-size:13px;font-weight:600;color:#333;margin-bottom:6px"><?=$label?></div>
|
||||
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:4px">
|
||||
<?php for($c=1;$c<=10;$c++): ?>
|
||||
<div onclick="addBet('rank','rank<?=$r?>_<?=$c?>',this)" class="bet-btn" data-odds="9.80">
|
||||
<?php $rankOdds=$odds('rank','rank'.$r.'_'.$c); ?>
|
||||
<div onclick="addBet('rank','rank<?=$r?>_<?=$c?>',this)" class="bet-btn" data-odds="<?=$rankOdds?>">
|
||||
<div class="car c<?=$c?>" style="width:24px;height:24px;font-size:11px;margin:0 auto 2px"><?=$c?></div>
|
||||
<div style="color:#999;font-size:10px">9.80</div>
|
||||
<div style="color:#999;font-size:10px"><?=$rankOdds?></div>
|
||||
</div>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
@@ -200,40 +596,103 @@
|
||||
<div id="panelSum" class="hidden" style="padding:10px">
|
||||
<div style="font-size:13px;font-weight:600;color:#333;margin-bottom:8px"><?=$t('sum_value')?> (3-19)</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:4px">
|
||||
<?php $sumOdds=[3=>140,4=>70,5=>35,6=>23,7=>17.5,8=>14,9=>11.6,10=>10,11=>8.8,12=>8.8,13=>10,14=>11.6,15=>14,16=>17.5,17=>23,18=>35,19=>70];
|
||||
foreach($sumOdds as $v=>$o): ?>
|
||||
<?php $sumOddsFallback=[3=>140,4=>70,5=>35,6=>23,7=>17.5,8=>14,9=>11.6,10=>10,11=>8.8,12=>8.8,13=>10,14=>11.6,15=>14,16=>17.5,17=>23,18=>35,19=>70];
|
||||
foreach($sumOddsFallback as $v=>$fallback): $sumO=$oddsMap['sum_sum_'.$v]??$fallback; ?>
|
||||
<div onclick="addBet('sum','sum_<?=$v?>',this)" class="bet-btn">
|
||||
<div style="font-weight:700"><?=$v?></div><div style="color:#999;font-size:10px"><?=$o?></div>
|
||||
<div style="font-weight:700"><?=$v?></div><div style="color:#999;font-size:10px"><?=$sumO?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-top:10px">
|
||||
<div onclick="addBet('sum_bs','sum_big',this)" class="bet-cell" style="border:1px solid #F0F0F0;border-radius:8px"><?=$t('sum_big')?> <span class="odds">1.95</span></div>
|
||||
<div onclick="addBet('sum_bs','sum_small',this)" class="bet-cell" style="border:1px solid #F0F0F0;border-radius:8px"><?=$t('sum_small')?> <span class="odds">1.95</span></div>
|
||||
<div onclick="addBet('sum_bs','sum_odd',this)" class="bet-cell" style="border:1px solid #F0F0F0;border-radius:8px"><?=$t('sum_odd')?> <span class="odds">1.95</span></div>
|
||||
<div onclick="addBet('sum_bs','sum_even',this)" class="bet-cell" style="border:1px solid #F0F0F0;border-radius:8px"><?=$t('sum_even')?> <span class="odds">1.95</span></div>
|
||||
<div onclick="addBet('sum_bs','sum_big',this)" class="bet-cell" style="border:1px solid #F0F0F0;border-radius:8px"><?=$t('sum_big')?> <span class="odds"><?=$odds('sum_bs','sum_big')?></span></div>
|
||||
<div onclick="addBet('sum_bs','sum_small',this)" class="bet-cell" style="border:1px solid #F0F0F0;border-radius:8px"><?=$t('sum_small')?> <span class="odds"><?=$odds('sum_bs','sum_small')?></span></div>
|
||||
<div onclick="addBet('sum_bs','sum_odd',this)" class="bet-cell" style="border:1px solid #F0F0F0;border-radius:8px"><?=$t('sum_odd')?> <span class="odds"><?=$odds('sum_bs','sum_odd')?></span></div>
|
||||
<div onclick="addBet('sum_bs','sum_even',this)" class="bet-cell" style="border:1px solid #F0F0F0;border-radius:8px"><?=$t('sum_even')?> <span class="odds"><?=$odds('sum_bs','sum_even')?></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快捷投注 -->
|
||||
<div id="panelQuick" class="hidden" style="padding:10px">
|
||||
<div class="quick-section">
|
||||
<div class="quick-title"><?=$t('two_side')?> — <?=$t('rank_n',['n'=>'1~10'])?></div>
|
||||
<div class="quick-grid">
|
||||
<div class="quick-btn qb-red" onclick="quickBet('bs','big')"><?=$t('big')?></div>
|
||||
<div class="quick-btn qb-blue" onclick="quickBet('bs','small')"><?=$t('small')?></div>
|
||||
<div class="quick-btn qb-orange" onclick="quickBet('oe','odd')"><?=$t('odd')?></div>
|
||||
<div class="quick-btn qb-purple" onclick="quickBet('oe','even')"><?=$t('even')?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quick-section">
|
||||
<div class="quick-title"><?=$t('dragon_tiger')?> — <?=$t('rank_n',['n'=>'1~5'])?></div>
|
||||
<div class="quick-grid">
|
||||
<div class="quick-btn qb-red" onclick="quickBet('dt','dragon')"><?=$t('dragon')?></div>
|
||||
<div class="quick-btn qb-blue" onclick="quickBet('dt','tiger')"><?=$t('tiger')?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quick-section">
|
||||
<div class="quick-title"><?=$t('sum')?></div>
|
||||
<div class="quick-grid">
|
||||
<div class="quick-btn qb-red" onclick="quickBet('sum_bs','sum_big')"><?=$t('sum_big')?></div>
|
||||
<div class="quick-btn qb-blue" onclick="quickBet('sum_bs','sum_small')"><?=$t('sum_small')?></div>
|
||||
<div class="quick-btn qb-orange" onclick="quickBet('sum_bs','sum_odd')"><?=$t('sum_odd')?></div>
|
||||
<div class="quick-btn qb-purple" onclick="quickBet('sum_bs','sum_even')"><?=$t('sum_even')?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quick-hint"><?=$t('quick_hint')?></div>
|
||||
</div>
|
||||
|
||||
<!-- 长龙统计 -->
|
||||
<div id="panelDragon" class="hidden" style="padding:8px 10px">
|
||||
<div id="dragonStreakList"></div>
|
||||
</div>
|
||||
|
||||
<!-- 遗漏统计 -->
|
||||
<div id="panelMiss" class="hidden" style="padding:8px 10px">
|
||||
<div class="miss-tabs" id="missTabs">
|
||||
<span class="miss-tab active" onclick="switchMissRank(0,this)"><?=$t('champion')?></span>
|
||||
<span class="miss-tab" onclick="switchMissRank(1,this)"><?=$t('runner_up')?></span>
|
||||
<?php for($i=3;$i<=10;$i++): ?>
|
||||
<span class="miss-tab" onclick="switchMissRank(<?=$i-1?>,this)"><?=$i?></span>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
<div id="missGrid" class="miss-grid"></div>
|
||||
<div class="miss-section" style="margin-top:8px">
|
||||
<div class="miss-label"><?=$t('two_side')?></div>
|
||||
<div id="missTwoSide" class="miss-two-side"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /bet-content -->
|
||||
</div><!-- /bet-layout -->
|
||||
|
||||
<!-- 筹码+提交栏 -->
|
||||
<div class="chip-bar">
|
||||
<div onclick="selectChip(10)" class="chip active" style="background:#e74c3c" data-amount="10">10</div>
|
||||
<div onclick="selectChip(50)" class="chip" style="background:#3498db" data-amount="50">50</div>
|
||||
<div onclick="selectChip(100)" class="chip" style="background:#2ecc71" data-amount="100">100</div>
|
||||
<div onclick="selectChip(500)" class="chip" style="background:#9b59b6" data-amount="500">500</div>
|
||||
<div onclick="showCustomChip()" class="chip" style="background:#f39c12" data-amount="custom" id="customChipBtn"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></div>
|
||||
</div>
|
||||
<div id="customChipInput" class="hidden" style="padding:8px 15px;background:#fff;display:flex;gap:8px">
|
||||
<input type="number" id="customAmount" min="1" step="1" placeholder="<?=$t('amount')?>" class="input" style="flex:1;text-align:center">
|
||||
<button onclick="applyCustomChip()" class="btn btn-primary btn-sm"><?=$t('confirm')?></button>
|
||||
</div>
|
||||
<div class="submit-bar">
|
||||
<div class="info"><?=$t('total')?>: <b id="betTotal">0</b> x<span id="betCount">0</span></div>
|
||||
<button onclick="clearBets()" class="btn btn-outline btn-sm"><?=$t('cancel')?></button>
|
||||
<button onclick="submitBets()" id="submitBtn" class="btn btn-primary btn-sm"><?=$t('place_bet')?></button>
|
||||
<!-- 底部投注操作栏(参考图 dsn 风格:已选N注 + 金额按钮 + 输入框 + 取消确认) -->
|
||||
<div class="bet-bottom-bar" id="betBottomBar">
|
||||
<!-- 已选N注浮动胶囊 -->
|
||||
<div class="bet-float-badge hide" id="betFloatBadge" onclick="toggleBetPanel()">
|
||||
已选 <span class="count" id="betCount">0</span> 注
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 0 1-8 0"/></svg>
|
||||
</div>
|
||||
<!-- 快捷金额按钮 -->
|
||||
<div class="amount-row">
|
||||
<div class="amount-btn" onclick="selectChip(50,this)" data-amount="50">50</div>
|
||||
<div class="amount-btn" onclick="selectChip(100,this)" data-amount="100">100</div>
|
||||
<div class="amount-btn" onclick="selectChip(200,this)" data-amount="200">200</div>
|
||||
<div class="amount-btn active" onclick="selectChip(500,this)" data-amount="500">500</div>
|
||||
<div class="amount-btn" onclick="selectChip(1000,this)" data-amount="1000">1000</div>
|
||||
<div class="amount-custom" onclick="toggleCustomInput()">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 自定义金额输入 -->
|
||||
<div class="bet-input-row" id="customInputRow">
|
||||
<input type="number" id="customAmount" min="1" step="1" placeholder="最低输入1元">
|
||||
<div class="bet-preset-btn" onclick="applyCustomChip()">预设 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/></svg></div>
|
||||
</div>
|
||||
<!-- 取消 / 确认 -->
|
||||
<div class="bet-action-row">
|
||||
<button class="btn-cancel" onclick="clearBets()"><?=$t('cancel')?></button>
|
||||
<button class="btn-confirm" onclick="submitBets()"><?=$t('confirm')?></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 本期已下注 -->
|
||||
@@ -266,85 +725,83 @@ foreach($sumOdds as $v=>$o): ?>
|
||||
<!-- 底部导航 -->
|
||||
<?php $navActive='game'; include __DIR__.'/_nav.php'; ?>
|
||||
|
||||
<!-- PK10 Config: PHP variables passed to JS -->
|
||||
<script>
|
||||
const GAME_ID=<?=$gameId?>;
|
||||
let selectedChip=10,bets={},periodData=null,pollTimer=null;
|
||||
const rankNames={1:'<?=$t('champion')?>',2:'<?=$t('runner_up')?>',3:'<?=$t('rank_n',['n'=>3])?>',4:'<?=$t('rank_n',['n'=>4])?>',5:'<?=$t('rank_n',['n'=>5])?>',6:'<?=$t('rank_n',['n'=>6])?>',7:'<?=$t('rank_n',['n'=>7])?>',8:'<?=$t('rank_n',['n'=>8])?>',9:'<?=$t('rank_n',['n'=>9])?>',10:'<?=$t('rank_n',['n'=>10])?>'};
|
||||
const dtPairs={1:[1,10],2:[2,9],3:[3,8],4:[4,7],5:[5,6]};
|
||||
const I18N={big:'<?=$t('big')?>',small:'<?=$t('small')?>',odd:'<?=$t('odd')?>',even:'<?=$t('even')?>',dragon:'<?=$t('dragon')?>',tiger:'<?=$t('tiger')?>',sum:'<?=$t('sum')?>',sum_big:'<?=$t('sum_big')?>',sum_small:'<?=$t('sum_small')?>',sum_odd:'<?=$t('sum_odd')?>',sum_even:'<?=$t('sum_even')?>',car:'<?=$t('car_no')?>',no_bets:'<?=$t('no_data')?>',total:'<?=$t('total')?>',items:'',profit:'<?=$t('win')?>',loss:'<?=$t('lose')?>',draw:'<?=$t('pending')?>',net_err:'<?=$t('error')?>',rank_prefix:'<?=$t('rank_n',['n'=>''])?>',drawing:'<?=$t('drawing')?>',settled_label:'<?=$t('settled')?>'};
|
||||
function formatBetLabel(type,value){
|
||||
let m;
|
||||
if(type==='rank'&&(m=value.match(/^rank(\d+)_(\d+)$/))){return(rankNames[+m[1]]||I18N.rank_prefix+m[1])+' #'+m[2];}
|
||||
if(type==='bs'&&(m=value.match(/^rank(\d+)_(big|small)$/))){return(rankNames[+m[1]]||I18N.rank_prefix+m[1])+' '+(m[2]==='big'?I18N.big:I18N.small);}
|
||||
if(type==='oe'&&(m=value.match(/^rank(\d+)_(odd|even)$/))){return(rankNames[+m[1]]||I18N.rank_prefix+m[1])+' '+(m[2]==='odd'?I18N.odd:I18N.even);}
|
||||
if(type==='dt'&&(m=value.match(/^dt(\d+)_(dragon|tiger)$/))){const p=dtPairs[+m[1]]||[0,0];return(rankNames[p[0]]||p[0])+'vs'+(rankNames[p[1]]||p[1])+' '+(m[2]==='dragon'?I18N.dragon:I18N.tiger);}
|
||||
if(type==='sum'&&(m=value.match(/^sum_(\d+)$/))){return I18N.sum+' '+m[1];}
|
||||
if(type==='sum_bs'){const sm={sum_big:I18N.sum_big,sum_small:I18N.sum_small,sum_odd:I18N.sum_odd,sum_even:I18N.sum_even};return sm[value]||value;}
|
||||
return type+':'+value;
|
||||
}
|
||||
function selectChip(a){selectedChip=a;document.querySelectorAll('.chip').forEach(c=>{const amt=c.dataset.amount;c.classList.toggle('active',amt!=='custom'&&parseInt(amt)===a);});document.getElementById('customChipInput').classList.add('hidden');}
|
||||
function showCustomChip(){const b=document.getElementById('customChipInput');b.classList.toggle('hidden');if(!b.classList.contains('hidden'))document.getElementById('customAmount').focus();}
|
||||
function applyCustomChip(){const v=parseInt(document.getElementById('customAmount').value);if(!v||v<=0){showToast('<?=$t('error')?>: > 0','error');return;}selectedChip=v;document.querySelectorAll('.chip').forEach(c=>c.classList.remove('active'));const cb=document.getElementById('customChipBtn');cb.classList.add('active');cb.textContent=v>=1000?(v/1000)+'k':v;document.getElementById('customChipInput').classList.add('hidden');}
|
||||
function addBet(type,target,el){if(periodData&&periodData.status!=='pending'){showToast('<?=$t('period_closed')?>','error');return;}const key=type+'_'+target;if(!bets[key])bets[key]={type,value:target,amount:0,el:el};bets[key].amount+=selectedChip;el.classList.add('selected');let badge=el.querySelector('.bet-amount-badge');if(!badge){badge=document.createElement('span');badge.className='bet-amount-badge';el.appendChild(badge);}badge.textContent=bets[key].amount>=1000?(bets[key].amount/1000).toFixed(1)+'k':bets[key].amount;updateBetSummary();}
|
||||
function clearBets(){bets={};document.querySelectorAll('.bet-btn.selected,.bet-cell.selected').forEach(e=>{e.classList.remove('selected');const b=e.querySelector('.bet-amount-badge');if(b)b.remove();});updateBetSummary();}
|
||||
function updateBetSummary(){let total=0,count=0;Object.values(bets).forEach(b=>{total+=b.amount;count++;});document.getElementById('betTotal').textContent=total.toFixed(0);document.getElementById('betCount').textContent=count;}
|
||||
async function submitBets(){const arr=Object.values(bets);if(!arr.length){showToast('<?=$t('place_bet')?>','error');return;}if(!periodData||!periodData.period_number){showToast('<?=$t('waiting')?>','error');return;}try{const r=await fetch('/api/bet',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({game_id:GAME_ID,period_number:periodData.period_number,bets:arr})});const d=await r.json();if(d.success){showToast(d.message,'ok');document.getElementById('balanceDisplay').textContent=parseFloat(d.new_balance).toFixed(2);clearBets();pollPeriod();}else{showToast(d.message,'error');}}catch(e){showToast(I18N.net_err,'error');}}
|
||||
function showBetTab(tab,btn){['Rank','Bs','Sum'].forEach(t=>{const p=document.getElementById('panel'+t);if(p)p.classList.toggle('hidden',t.toLowerCase()!==tab);});document.querySelectorAll('.side-menu .mi').forEach(m=>m.classList.remove('active'));if(btn)btn.classList.add('active');}
|
||||
|
||||
// ===== 赛车动画系统(完整保留) =====
|
||||
let raceAnimState='idle',lastAnimatedResult=null,idleAnimTimer=null,carPositions={},raceFrameTimer=null;
|
||||
const FINISH_LINE=88,START_LINE=3;
|
||||
for(let i=1;i<=10;i++)carPositions[i]=START_LINE;
|
||||
|
||||
function setCarPosition(n,pct,dur){const c=document.getElementById('carContainer'+n);const b=document.getElementById('bar'+n);if(!c)return;c.style.transition='left '+dur+'s ease';c.style.left=pct+'%';if(b){b.style.transition='width '+dur+'s ease';b.style.width=Math.min(pct+3,92)+'%';}carPositions[n]=pct;}
|
||||
function setCarMoving(n,moving,sprinting){const c=document.getElementById('carContainer'+n);if(!c)return;c.classList.toggle('moving',moving);c.classList.toggle('sprinting',!!sprinting);c.classList.remove('ready');}
|
||||
function setRankBadge(n,rank){const badge=document.getElementById('rankBadge'+n);if(!badge)return;const c=document.getElementById('carContainer'+n);if(rank>0){const labels=['1st','2nd','3rd','4th','5th','6th','7th','8th','9th','10th'];badge.textContent=labels[rank-1]||rank;if(c)c.classList.add('show-rank');}else{badge.textContent='';if(c)c.classList.remove('show-rank');}}
|
||||
// ANIM: idle
|
||||
function startIdleAnimation(){if(idleAnimTimer)return;raceAnimState='waiting';for(let i=1;i<=10;i++){setCarPosition(i,START_LINE+Math.random()*5,0.5);setCarMoving(i,true,false);setRankBadge(i,0);}idleAnimTimer=setInterval(function(){if(raceAnimState!=='waiting'){stopIdleAnimation();return;}for(let i=1;i<=10;i++){if(Math.random()<0.5){const cur=carPositions[i]||START_LINE;const target=Math.max(START_LINE,Math.min(15,cur+(Math.random()-0.5)*6));setCarPosition(i,target,0.5+Math.random()*0.3);}}},800);}
|
||||
function stopIdleAnimation(){if(idleAnimTimer){clearInterval(idleAnimTimer);idleAnimTimer=null;}if(raceFrameTimer){clearInterval(raceFrameTimer);raceFrameTimer=null;}for(let i=1;i<=10;i++){setCarMoving(i,false,false);const c=document.getElementById('carContainer'+i);if(c)c.classList.remove('ready');}}
|
||||
function startReadyAnimation(){stopIdleAnimation();raceAnimState='ready';for(let i=1;i<=10;i++){setCarPosition(i,START_LINE,0.8);setCarMoving(i,true,false);setRankBadge(i,0);const c=document.getElementById('carContainer'+i);if(c)c.classList.add('ready');}}
|
||||
// ANIM: race
|
||||
function animateRace(result){if(!result||result.length<10)return;const rk=result.join(',');if(lastAnimatedResult===rk&&raceAnimState==='finished')return;lastAnimatedResult=rk;stopIdleAnimation();raceAnimState='racing';const rankMap={};for(let i=0;i<10;i++)rankMap[result[i]]=i;const carSpeeds={},carTargets={};for(let i=1;i<=10;i++){const rank=rankMap[i]!==undefined?rankMap[i]:9;carSpeeds[i]=1.0-rank*0.06;carTargets[i]=FINISH_LINE-rank*7.5;setCarMoving(i,true,true);setRankBadge(i,0);}
|
||||
for(let i=1;i<=10;i++){const s=carSpeeds[i];setCarPosition(i,15+s*20+Math.random()*5,1.2+Math.random()*0.3);}
|
||||
setTimeout(function(){if(raceAnimState!=='racing')return;for(let i=1;i<=10;i++){const s=carSpeeds[i];setCarPosition(i,35+s*30+Math.random()*5,1.5+Math.random()*0.5);}},1500);
|
||||
setTimeout(function(){if(raceAnimState!=='racing')return;for(let i=1;i<=10;i++){const dur=1.5+rankMap[i]*0.05+Math.random()*0.2;setCarPosition(i,carTargets[i],dur);}},3500);
|
||||
setTimeout(function(){if(raceAnimState!=='racing')return;raceAnimState='finished';for(let i=1;i<=10;i++){setCarMoving(i,false,false);const rank=rankMap[i]!==undefined?rankMap[i]+1:0;setRankBadge(i,rank);}},5500);}
|
||||
function resetRace(){stopIdleAnimation();raceAnimState='idle';lastAnimatedResult=null;for(let i=1;i<=10;i++){const c=document.getElementById('carContainer'+i);const b=document.getElementById('bar'+i);if(c){c.style.transition='left 0.5s ease';c.style.left=START_LINE+'%';c.classList.remove('moving','sprinting','ready','show-rank');}if(b){b.style.transition='width 0.5s ease';b.style.width='5%';}carPositions[i]=START_LINE;setRankBadge(i,0);}}
|
||||
function renderResult(result,container){if(!result)return;const el=document.getElementById(container);el.innerHTML='';['rank_1','rank_2','rank_3','rank_4','rank_5','rank_6','rank_7','rank_8','rank_9','rank_10'].forEach(k=>{const v=result[k];if(!v)return;const d=document.createElement('div');d.className='car c'+v;d.style.cssText='width:28px;height:28px;font-size:11px';d.textContent=v;el.appendChild(d);});
|
||||
const tags=document.getElementById('lastResultTags');if(!tags)return;tags.innerHTML='';const r1=+result.rank_1,r2=+result.rank_2;if(r1&&r2){const sum=r1+r2;const items=[sum,(sum>=12?I18N.big:I18N.small),(sum%2?I18N.odd:I18N.even)];for(let i=1;i<=5;i++){const a=+result['rank_'+i],b=+result['rank_'+(11-i)];if(a&&b)items.push(a>b?I18N.dragon:I18N.tiger);}items.forEach(t=>{const s=document.createElement('span');s.textContent=t;tags.appendChild(s);});}}
|
||||
// 投注记录渲染
|
||||
function renderMyBets(myBets){const list=document.getElementById('myBetsList');const countEl=document.getElementById('myBetsCount');if(!myBets||!myBets.length){list.innerHTML='<div style="color:var(--text3);font-size:12px;text-align:center;padding:8px">'+I18N.no_bets+'</div>';countEl.textContent='0';return;}countEl.textContent=myBets.length;let total=0,html='';myBets.forEach(b=>{const label=formatBetLabel(b.bet_type,b.bet_value);const amt=parseFloat(b.amount);total+=amt;const sl=b.status==='pending'?'<span style="color:var(--warn)">'+I18N.draw+'</span>':b.status==='win'?'<span style="color:var(--success)">+'+parseFloat(b.win_amount).toFixed(0)+'</span>':'<span style="color:var(--danger)">-'+amt.toFixed(0)+'</span>';html+='<div style="display:flex;justify-content:space-between;align-items:center;font-size:12px;padding:4px 0;border-bottom:1px solid var(--border)"><span style="color:var(--text2)">'+label+'</span><span style="color:var(--primary)">'+amt.toFixed(0)+' <span style="color:var(--text3)">x'+b.odds+'</span></span>'+sl+'</div>';});html+='<div style="display:flex;justify-content:space-between;font-size:12px;padding-top:4px;color:var(--text3)"><span>'+I18N.total+'</span><span style="color:var(--primary);font-weight:700">'+total.toFixed(0)+'</span></div>';list.innerHTML=html;}
|
||||
function renderLastMyBets(lastMyBets){const panel=document.getElementById('lastResultPanel');const list=document.getElementById('lastMyBetsList');const profitEl=document.getElementById('lastProfitDisplay');if(!lastMyBets||!lastMyBets.length){panel.classList.add('hidden');return;}panel.classList.remove('hidden');let totalWin=0,totalBet=0,html='';lastMyBets.forEach(b=>{const label=formatBetLabel(b.bet_type,b.bet_value);const amt=parseFloat(b.amount);totalBet+=amt;if(b.status==='win'){const win=parseFloat(b.win_amount);totalWin+=win+amt;html+='<div style="display:flex;justify-content:space-between;font-size:12px;padding:2px 0"><span style="color:var(--text2)">'+label+'</span><span style="color:var(--success)">+'+win.toFixed(0)+'</span></div>';}else{html+='<div style="display:flex;justify-content:space-between;font-size:12px;padding:2px 0"><span style="color:var(--text2)">'+label+'</span><span style="color:var(--danger)">-'+amt.toFixed(0)+'</span></div>';}});list.innerHTML=html;const profit=totalWin-totalBet;if(profit>0){profitEl.style.color='var(--success)';profitEl.textContent='+'+profit.toFixed(0);}else if(profit<0){profitEl.style.color='var(--danger)';profitEl.textContent=profit.toFixed(0);}else{profitEl.style.color='var(--text3)';profitEl.textContent='0';}}
|
||||
// 倒计时
|
||||
let localCountdown=0,lastPollStatus='',lastPeriodId=0,drawnResultShown=false;
|
||||
setInterval(function(){if(localCountdown>0)localCountdown--;const m=Math.floor(localCountdown/60),s=localCountdown%60;document.getElementById('countdown').textContent=localCountdown>0?m+':'+(s<10?'0':'')+s:'--';},1000);
|
||||
// 轮询期号
|
||||
async function pollPeriod(){try{const r=await fetch('/api/period/current?game_id='+GAME_ID);const d=await r.json();if(!d.success)return;periodData=d.data;
|
||||
const serverRemaining=periodData.remaining_seconds||0;const periodChanged=(periodData.id&&periodData.id!==lastPeriodId);const statusChanged=(periodData.status!==lastPollStatus);const driftTooMuch=Math.abs(localCountdown-serverRemaining)>3;
|
||||
if(periodChanged||statusChanged||driftTooMuch||localCountdown<=0)localCountdown=serverRemaining;
|
||||
if(d.balance!==null&&d.balance!==undefined)document.getElementById('balanceDisplay').textContent=parseFloat(d.balance).toFixed(2);
|
||||
// 状态徽章
|
||||
const badge=document.getElementById('statusBadge');const statusMap={pending:['<?=$t('betting')?>','seal-status','color:var(--success)'],locked:['<?=$t('sealed')?>','seal-status','color:var(--danger)'],drawn:[I18N.drawing,'seal-status','color:var(--warn)'],settled:[I18N.settled_label,'seal-status','color:var(--warn)']};const s=statusMap[periodData.status]||statusMap.pending;badge.textContent=s[0];badge.className=s[1];badge.style.cssText=s[2];
|
||||
// 期号
|
||||
const fullPn=periodData.period_number||'---';const shortPn=fullPn.length>8?fullPn.slice(0,-4)+'-'+fullPn.slice(-4):fullPn;const pnEl=document.getElementById('periodNum');pnEl.textContent=shortPn;pnEl.title=fullPn;
|
||||
// 提取结果
|
||||
function extractResultArr(res){if(!res)return null;const arr=[res.rank_1,res.rank_2,res.rank_3,res.rank_4,res.rank_5,res.rank_6,res.rank_7,res.rank_8,res.rank_9,res.rank_10];return arr.every(v=>v)?arr:null;}
|
||||
// 投注中→待机动画
|
||||
if(periodData.status==='pending'){if(statusChanged||periodChanged){if(d.last_result&&periodChanged&&raceAnimState!=='finished'){const ra=extractResultArr(d.last_result);if(ra){animateRace(ra);renderResult(d.last_result,'lastResult');const lp=d.last_result.period_number||'';document.getElementById('lastPeriodNum').textContent=lp.length>4?'#'+lp.slice(-4):lp;setTimeout(function(){resetRace();setTimeout(startIdleAnimation,500);},6500);}else{resetRace();setTimeout(startIdleAnimation,600);}}else{resetRace();setTimeout(startIdleAnimation,600);}}else if(raceAnimState==='idle'){startIdleAnimation();}}
|
||||
// 封盘→比赛或预备
|
||||
if(periodData.status==='locked'){const raceRes=extractResultArr(d.race_result);if(raceRes&&raceAnimState!=='racing'&&raceAnimState!=='finished'){animateRace(raceRes);}else if(!raceRes&&(statusChanged||(raceAnimState!=='ready'&&raceAnimState!=='racing'&&raceAnimState!=='finished'))){startReadyAnimation();}}
|
||||
// 开奖/结算
|
||||
if(d.last_result&&(periodData.status==='drawn'||periodData.status==='settled')){if(statusChanged||periodChanged){const lp=d.last_result.period_number||'';document.getElementById('lastPeriodNum').textContent=lp.length>4?'#'+lp.slice(-4):lp;const ra=extractResultArr(d.last_result);if(ra&&raceAnimState!=='finished')animateRace(ra);setTimeout(()=>{renderResult(d.last_result,'lastResult');},5500);}}
|
||||
// 首次加载
|
||||
if(d.last_result&&!lastAnimatedResult&&raceAnimState==='idle'){const ra=extractResultArr(d.last_result);if(ra){const lp=d.last_result.period_number||'';document.getElementById('lastPeriodNum').textContent=lp.length>4?'#'+lp.slice(-4):lp;animateRace(ra);setTimeout(()=>{renderResult(d.last_result,'lastResult');if(periodData.status==='pending')setTimeout(function(){resetRace();setTimeout(startIdleAnimation,500);},1500);},5500);}}
|
||||
renderMyBets(d.my_bets||[]);renderLastMyBets(d.last_my_bets||[]);
|
||||
// 历史
|
||||
if(d.history&&periodChanged){const hl=document.getElementById('historyList');hl.innerHTML='';d.history.forEach(h=>{const row=document.createElement('div');row.className='card';row.style.cssText='padding:6px 8px;margin-bottom:4px';const pn=h.period_number||'';const sp=pn.length>4?'#'+pn.slice(-4):pn;let balls='';['rank_1','rank_2','rank_3','rank_4','rank_5','rank_6','rank_7','rank_8','rank_9','rank_10'].forEach(k=>{if(h[k])balls+='<div class="car c'+h[k]+'" style="width:18px;height:18px;font-size:8px;flex-shrink:0">'+h[k]+'</div>';});row.innerHTML='<div style="color:var(--text3);font-size:10px;margin-bottom:2px">'+sp+'</div><div style="display:flex;gap:2px;flex-wrap:wrap">'+balls+'</div>';hl.appendChild(row);});}
|
||||
lastPollStatus=periodData.status;lastPeriodId=periodData.id||0;
|
||||
}catch(e){console.error('pollPeriod error:',e);}}
|
||||
function showToast(msg,type){const t=document.createElement('div');t.style.cssText='position:fixed;top:80px;left:50%;transform:translateX(-50%);padding:10px 24px;border-radius:24px;font-size:13px;z-index:999;color:#fff;box-shadow:0 4px 16px rgba(0,0,0,.15)';t.style.background=type==='ok'?'var(--success)':'var(--danger)';t.textContent=msg;document.body.appendChild(t);setTimeout(()=>t.remove(),2500);}
|
||||
async function setLang(l){await fetch('/api/set-lang',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({lang:l})});location.reload();}
|
||||
pollPeriod();
|
||||
(function dynamicPoll(){const delay=(lastPollStatus==='locked'||lastPollStatus==='drawn'||lastPollStatus==='settled')?1500:3000;setTimeout(function(){pollPeriod().then(dynamicPoll).catch(dynamicPoll);},delay);})();
|
||||
var PK10_CONFIG={
|
||||
gameId:<?=$gameId?>,
|
||||
lang:{
|
||||
champion:'<?=$t('champion')?>',runner_up:'<?=$t('runner_up')?>',
|
||||
rank_3:'<?=$t('rank_n',['n'=>3])?>',rank_4:'<?=$t('rank_n',['n'=>4])?>',rank_5:'<?=$t('rank_n',['n'=>5])?>',
|
||||
rank_6:'<?=$t('rank_n',['n'=>6])?>',rank_7:'<?=$t('rank_n',['n'=>7])?>',rank_8:'<?=$t('rank_n',['n'=>8])?>',
|
||||
rank_9:'<?=$t('rank_n',['n'=>9])?>',rank_10:'<?=$t('rank_n',['n'=>10])?>',
|
||||
big:'<?=$t('big')?>',small:'<?=$t('small')?>',odd:'<?=$t('odd')?>',even:'<?=$t('even')?>',
|
||||
dragon:'<?=$t('dragon')?>',tiger:'<?=$t('tiger')?>',sum:'<?=$t('sum')?>',
|
||||
sum_big:'<?=$t('sum_big')?>',sum_small:'<?=$t('sum_small')?>',sum_odd:'<?=$t('sum_odd')?>',sum_even:'<?=$t('sum_even')?>',
|
||||
car_no:'<?=$t('car_no')?>',no_data:'<?=$t('no_data')?>',total:'<?=$t('total')?>',
|
||||
win:'<?=$t('win')?>',lose:'<?=$t('lose')?>',pending_status:'<?=$t('pending')?>',
|
||||
error:'<?=$t('error')?>',rank_prefix:'<?=$t('rank_n',['n'=>''])?>',
|
||||
drawing:'<?=$t('drawing')?>',settled:'<?=$t('settled')?>',
|
||||
period_closed:'<?=$t('period_closed')?>',place_bet:'<?=$t('place_bet')?>',
|
||||
waiting:'<?=$t('waiting')?>',bets_added:'<?=$t('bets_added')?>',
|
||||
sealed:'<?=$t('sealed')?>',betting:'<?=$t('betting')?>'
|
||||
}
|
||||
};
|
||||
// Shared global state (accessible by all pk10-*.js files)
|
||||
var GAME_ID=PK10_CONFIG.gameId;
|
||||
var selectedChip=500,bets={},periodData=null,pollTimer=null,historyCache=[];
|
||||
var rankNames={1:PK10_CONFIG.lang.champion,2:PK10_CONFIG.lang.runner_up,3:PK10_CONFIG.lang.rank_3,4:PK10_CONFIG.lang.rank_4,5:PK10_CONFIG.lang.rank_5,6:PK10_CONFIG.lang.rank_6,7:PK10_CONFIG.lang.rank_7,8:PK10_CONFIG.lang.rank_8,9:PK10_CONFIG.lang.rank_9,10:PK10_CONFIG.lang.rank_10};
|
||||
var dtPairs={1:[1,10],2:[2,9],3:[3,8],4:[4,7],5:[5,6]};
|
||||
var I18N={big:PK10_CONFIG.lang.big,small:PK10_CONFIG.lang.small,odd:PK10_CONFIG.lang.odd,even:PK10_CONFIG.lang.even,dragon:PK10_CONFIG.lang.dragon,tiger:PK10_CONFIG.lang.tiger,sum:PK10_CONFIG.lang.sum,sum_big:PK10_CONFIG.lang.sum_big,sum_small:PK10_CONFIG.lang.sum_small,sum_odd:PK10_CONFIG.lang.sum_odd,sum_even:PK10_CONFIG.lang.sum_even,car:PK10_CONFIG.lang.car_no,no_bets:PK10_CONFIG.lang.no_data,total:PK10_CONFIG.lang.total,items:'',profit:PK10_CONFIG.lang.win,loss:PK10_CONFIG.lang.lose,draw:PK10_CONFIG.lang.pending_status,net_err:PK10_CONFIG.lang.error,rank_prefix:PK10_CONFIG.lang.rank_prefix,drawing:PK10_CONFIG.lang.drawing,settled_label:PK10_CONFIG.lang.settled,period_closed:PK10_CONFIG.lang.period_closed,place_bet:PK10_CONFIG.lang.place_bet,waiting:PK10_CONFIG.lang.waiting,bets_added:PK10_CONFIG.lang.bets_added};
|
||||
// Race state
|
||||
var raceAnimState='idle',lastAnimatedResult=null,idleAnimTimer=null,carPositions={};
|
||||
var raceFrameTimer=null,ballRollTimer=null;
|
||||
var FINISH_LINE=88,START_LINE=3;
|
||||
for(var _i=1;_i<=10;_i++)carPositions[_i]=START_LINE;
|
||||
var CAR_COLORS={1:['#f1c40f','#d4ac0d'],2:['#3498db','#2980b9'],3:['#555555','#333333'],4:['#e67e22','#ca6f1e'],5:['#2ecc71','#27ae60'],6:['#2c3e99','#1a2a6c'],7:['#999999','#777777'],8:['#e74c3c','#c0392b'],9:['#cc2222','#991111'],10:['#27ae60','#1e8449']};
|
||||
var trafficLightTimers=[];
|
||||
var raceModalOpen=false,currentView='anim';
|
||||
// Poll state
|
||||
var localCountdown=0,localLockCountdown=0,lastPollStatus='',lastPeriodId=0,drawnResultShown=false;
|
||||
var cachedRaceResult=null,raceTriggered=false,pendingRaceHeader=null;
|
||||
// Bet state
|
||||
var curMissRank=0;
|
||||
</script>
|
||||
<!-- PK10 JS modules (order matters) -->
|
||||
<?php $jsVer = '20260325b'; ?>
|
||||
<script src="/Static/js/pk10-sound.js?v=<?=$jsVer?>"></script>
|
||||
<script src="/Static/js/pk10-race.js?v=<?=$jsVer?>"></script>
|
||||
<script src="/Static/js/pk10-bet.js?v=<?=$jsVer?>"></script>
|
||||
<script src="/Static/js/pk10-poll.js?v=<?=$jsVer?>"></script>
|
||||
<?php if(!empty($game['stream_url'])): ?>
|
||||
<script src="/Static/ckplayer/hls.js/hls.min.js"></script>
|
||||
<script src="/Static/ckplayer/flv.js/flv.min.js"></script>
|
||||
<script src="/Static/ckplayer/mpegts.js/mpegts.min.js"></script>
|
||||
<script src="/Static/ckplayer/js/ckplayer.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var el=document.getElementById('pk10VideoPlayer');
|
||||
if(!el||typeof ckplayer!=='function')return;
|
||||
var url=el.getAttribute('data-stream')||'';
|
||||
if(!url)return;
|
||||
var type='hls';
|
||||
if(url.indexOf('.flv')>-1)type='flv';
|
||||
else if(url.indexOf('.ts')>-1||url.indexOf('mpegts')>-1)type='mpegts';
|
||||
window.pk10Player=ckplayer({
|
||||
container:'#pk10VideoPlayer',
|
||||
live:true,
|
||||
autoplay:true,
|
||||
volume:0,
|
||||
controls:true,
|
||||
rightBar:false,
|
||||
video:url,
|
||||
type:type,
|
||||
playsinline:true
|
||||
});
|
||||
setTimeout(function(){
|
||||
var v=document.querySelector('#pk10VideoPlayer video');
|
||||
if(v){v.muted=true;v.play().catch(function(){});}
|
||||
},800);
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</body></html>
|
||||
|
||||
@@ -88,7 +88,7 @@ for($i=1;$i<=10;$i++) $rankLabels[$i]=$i<=2?($i==1?$t('champion'):$t('runner_up'
|
||||
|
||||
<script>
|
||||
const DATA=<?=json_encode(array_values($results??[]))?>;
|
||||
const COLORS=['','#e74c3c','#3498db','#2ecc71','#f39c12','#9b59b6','#1abc9c','#e67e22','#e91e63','#00bcd4','#8bc34a'];
|
||||
const COLORS=['','#f1c40f','#3498db','#555555','#e67e22','#2ecc71','#2c3e99','#999999','#e74c3c','#cc2222','#27ae60'];
|
||||
const rankLabels=<?=json_encode(array_values($rankLabels))?>;
|
||||
const I18N={big:'<?=$t('big')?>',small:'<?=$t('small')?>',odd:'<?=$t('odd')?>',even:'<?=$t('even')?>',dragon:'<?=$t('dragon')?>',tiger:'<?=$t('tiger')?>',sum_big:'<?=$t('sum_bs_big')?>',sum_small:'<?=$t('sum_bs_small')?>',sum_odd:'<?=$t('sum_bs_odd')?>',sum_even:'<?=$t('sum_bs_even')?>'};
|
||||
let curRank=1,curBead='rank';
|
||||
|
||||
@@ -49,15 +49,15 @@
|
||||
<div class="wallet-header">
|
||||
<div class="wallet-title"><?=$t('balance')?></div>
|
||||
<div class="wallet-actions">
|
||||
<button class="action-btn" onclick="alert('<?=$t('deposit')?>')">
|
||||
<button class="action-btn" onclick="doDeposit()">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>
|
||||
<span><?=$t('deposit')?></span>
|
||||
</button>
|
||||
<button class="action-btn" onclick="alert('<?=$t('withdraw')?>')">
|
||||
<button class="action-btn" onclick="doWithdraw()">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>
|
||||
<span><?=$t('withdraw')?></span>
|
||||
</button>
|
||||
<button class="action-btn" onclick="alert('<?=$t('transfer')?>')">
|
||||
<button class="action-btn" onclick="doTransfer()">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>
|
||||
<span><?=$t('transfer')?></span>
|
||||
</button>
|
||||
@@ -81,6 +81,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 报表与跟单 -->
|
||||
<div class="section">
|
||||
<div class="section-title"><?=$t('bet_history')?></div>
|
||||
<a href="/report" class="menu-item" style="text-decoration:none">
|
||||
<div class="menu-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/><path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/><path d="M18 12a2 2 0 0 0 0 4h4v-4z"/></svg></div>
|
||||
<div class="menu-text"><?=$t('report_query')?></div>
|
||||
<div class="menu-arrow">›</div>
|
||||
</a>
|
||||
<a href="/follow-plan" class="menu-item" style="text-decoration:none">
|
||||
<div class="menu-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#1E90FF" stroke-width="2"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/><path d="M9 14l2 2 4-4"/></svg></div>
|
||||
<div class="menu-text"><?=$t('follow_plan')?></div>
|
||||
<div class="menu-arrow">›</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 投注记录 -->
|
||||
<div class="section">
|
||||
<div class="section-title"><?=$t('bet_history')?></div>
|
||||
@@ -111,6 +126,25 @@
|
||||
<?php endforeach; endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- 充提记录 -->
|
||||
<div class="section">
|
||||
<div class="section-title"><?=$t('fund_request_history')?></div>
|
||||
<?php if(empty($fundRequests)): ?>
|
||||
<div style="text-align:center;color:#999;padding:40px 20px;font-size:14px"><?=$t('no_data')?></div>
|
||||
<?php else: foreach($fundRequests as $fr): $isDeposit=$fr['type']==='deposit'; ?>
|
||||
<div class="menu-item" style="cursor:default">
|
||||
<div style="flex:1">
|
||||
<div style="font-size:14px;color:#333"><?=$isDeposit?$t('deposit'):$t('withdraw')?></div>
|
||||
<div style="font-size:11px;color:#999;margin-top:2px"><?=$fr['created_at']?></div>
|
||||
</div>
|
||||
<div style="text-align:right">
|
||||
<div style="font-size:15px;font-weight:600;color:<?=$isDeposit?'#4CAF50':'#FF4444'?>"><?=$isDeposit?'+':'-'?><?=number_format($fr['amount'],2)?></div>
|
||||
<span style="font-size:12px;color:<?=$fr['status']==='approved'?'#4CAF50':($fr['status']==='rejected'?'#FF4444':'#FF9800')?>"><?=$t('fund_status_'.$fr['status'])?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- 交易记录 -->
|
||||
<div class="section">
|
||||
<div class="section-title"><?=$t('transaction_history')?></div>
|
||||
@@ -143,5 +177,52 @@
|
||||
|
||||
<script>
|
||||
async function bindUsdt(){const addr=document.getElementById('usdtAddr').value.trim();const r=await fetch('/api/bind-usdt',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({usdt_address:addr})});const d=await r.json();alert(d.message);if(d.success)location.reload();}
|
||||
|
||||
function doDeposit(){
|
||||
var url='<?=\App\Core\SettingsHelper::get("customer_service_url")?>';
|
||||
if(!url){alert('<?=$t("contact_admin")?>');return;}
|
||||
var amount=prompt('<?=$t("deposit_amount")?>');
|
||||
if(!amount||isNaN(amount)||parseFloat(amount)<=0)return;
|
||||
fetch('/api/fund-request',{
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({type:'deposit',amount:parseFloat(amount)})
|
||||
}).then(function(r){return r.json();}).then(function(d){
|
||||
if(d.success){
|
||||
alert('<?=$t("deposit_submitted")?>');
|
||||
window.open(url,'_blank');
|
||||
location.reload();
|
||||
} else {
|
||||
alert(d.message||'<?=$t("error")?>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doWithdraw(){
|
||||
var amount=prompt('<?=$t("withdraw_amount")?>');
|
||||
if(!amount||isNaN(amount)||parseFloat(amount)<=0)return;
|
||||
fetch('/api/fund-request',{
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({type:'withdraw',amount:parseFloat(amount)})
|
||||
}).then(function(r){return r.json();}).then(function(d){
|
||||
if(d.success){alert('<?=$t("withdraw_submitted")?>');location.reload();}
|
||||
else{alert(d.message||'<?=$t("error")?>');}
|
||||
});
|
||||
}
|
||||
function doTransfer(){
|
||||
var to=prompt('<?=$t("transfer_to")?>');
|
||||
if(!to||!to.trim())return;
|
||||
var amount=prompt('<?=$t("transfer_amount")?>');
|
||||
if(!amount||isNaN(amount)||parseFloat(amount)<=0)return;
|
||||
if(!confirm('<?=$t("transfer_confirm")?> '+to+' , <?=$t("amount")?>: '+amount))return;
|
||||
fetch('/api/transfer',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({to_username:to.trim(),amount:parseFloat(amount)})
|
||||
}).then(function(r){return r.json();}).then(function(d){
|
||||
if(d.success){alert(d.message||'<?=$t("transfer_success")?>');location.reload();}
|
||||
else{alert(d.message||'<?=$t("error")?>');}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body></html>
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php use App\Core\I18n; I18n::init(); $t = function($k,$p=[]){return I18n::t($k,$p);}; ?>
|
||||
<!DOCTYPE html><html lang="<?=I18n::getLang()?>">
|
||||
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title><?=$t('report_query')?></title>
|
||||
<link rel="stylesheet" href="/Static/css/app.css">
|
||||
<style>
|
||||
.page-header{display:flex;align-items:center;padding:12px 16px;background:#fff;border-bottom:1px solid #F0F0F0;position:sticky;top:0;z-index:10}
|
||||
.page-header .back{width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer}
|
||||
.page-header .title{flex:1;text-align:center;font-size:17px;font-weight:600;color:#333}
|
||||
.page-header .balance{font-size:13px;color:#1E90FF;display:flex;align-items:center;gap:4px}
|
||||
.date-tabs{display:flex;gap:0;background:#fff;padding:12px 16px 0;flex-wrap:wrap}
|
||||
.date-tab{padding:6px 14px;font-size:13px;color:#666;cursor:pointer;border-bottom:2px solid transparent;background:none;border-top:none;border-left:none;border-right:none}
|
||||
.date-tab.active{color:#1E90FF;border-bottom-color:#1E90FF;font-weight:600}
|
||||
.report-table{width:100%;background:#fff;margin-top:1px}
|
||||
.report-table th{font-size:12px;color:#999;font-weight:400;padding:10px 8px;text-align:center;border-bottom:1px solid #F0F0F0}
|
||||
.report-table td{font-size:13px;color:#333;padding:10px 8px;text-align:center;border-bottom:1px solid #F5F5F5}
|
||||
.report-table .win{color:#4CAF50;font-weight:600}
|
||||
.report-table .lose{color:#FF4444;font-weight:600}
|
||||
.empty-state{text-align:center;padding:60px 20px;color:#999;font-size:14px}
|
||||
.summary-bar{display:flex;justify-content:space-around;background:#fff;padding:12px 16px;border-bottom:1px solid #F0F0F0}
|
||||
.summary-item{text-align:center}
|
||||
.summary-item .label{font-size:11px;color:#999}
|
||||
.summary-item .value{font-size:16px;font-weight:700;margin-top:2px}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background:#F5F5F5;padding-bottom:20px">
|
||||
|
||||
<div class="page-header">
|
||||
<a href="/profile" class="back"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg></a>
|
||||
<div class="title"><?=$t('report_query')?></div>
|
||||
<div class="balance"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v12M8 10h8M8 14h8"/></svg> <?=number_format($user['balance']??0,2)?></div>
|
||||
</div>
|
||||
|
||||
<div class="date-tabs">
|
||||
<button class="date-tab active" data-range="today"><?=$t('today')?></button>
|
||||
<button class="date-tab" data-range="yesterday"><?=$t('yesterday')?></button>
|
||||
<button class="date-tab" data-range="7days"><?=$t('last_7_days')?></button>
|
||||
<button class="date-tab" data-range="30days"><?=$t('last_30_days')?></button>
|
||||
<button class="date-tab" data-range="custom"><?=$t('custom_date')?></button>
|
||||
</div>
|
||||
|
||||
<div id="customDateRow" style="display:none;background:#fff;padding:8px 16px;gap:8px;align-items:center">
|
||||
<input type="date" id="dateFrom" style="flex:1;padding:6px 8px;border:1px solid #ddd;border-radius:6px;font-size:13px">
|
||||
<span style="color:#999">-</span>
|
||||
<input type="date" id="dateTo" style="flex:1;padding:6px 8px;border:1px solid #ddd;border-radius:6px;font-size:13px">
|
||||
<button onclick="loadReport()" style="padding:6px 16px;background:#1E90FF;color:#fff;border:none;border-radius:6px;font-size:13px"><?=$t('search')?></button>
|
||||
</div>
|
||||
|
||||
<div class="summary-bar">
|
||||
<div class="summary-item">
|
||||
<div class="label"><?=$t('bet_amount')?></div>
|
||||
<div class="value" id="sumBet">0.00</div>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<div class="label"><?=$t('effective_flow')?></div>
|
||||
<div class="value" id="sumFlow">0.00</div>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<div class="label"><?=$t('win_loss')?></div>
|
||||
<div class="value" id="sumWinLoss" style="color:#333">0.00</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="report-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?=$t('bet_type')?></th>
|
||||
<th><?=$t('bet_amount')?></th>
|
||||
<th><?=$t('amount')?></th>
|
||||
<th><?=$t('effective_flow')?></th>
|
||||
<th><?=$t('rebate_amount')?></th>
|
||||
<th><?=$t('win_loss')?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="reportBody">
|
||||
<tr><td colspan="6" class="empty-state"><?=$t('no_data')?></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
var currentRange='today';
|
||||
document.querySelectorAll('.date-tab').forEach(function(btn){
|
||||
btn.addEventListener('click',function(){
|
||||
document.querySelectorAll('.date-tab').forEach(function(b){b.classList.remove('active');});
|
||||
btn.classList.add('active');
|
||||
currentRange=btn.dataset.range;
|
||||
document.getElementById('customDateRow').style.display=currentRange==='custom'?'flex':'none';
|
||||
if(currentRange!=='custom')loadReport();
|
||||
});
|
||||
});
|
||||
|
||||
function getDateRange(){
|
||||
var now=new Date();
|
||||
var fmt=function(d){var y=d.getFullYear(),m=('0'+(d.getMonth()+1)).slice(-2),dd=('0'+d.getDate()).slice(-2);return y+'-'+m+'-'+dd;};
|
||||
if(currentRange==='today')return{from:fmt(now),to:fmt(now)};
|
||||
if(currentRange==='yesterday'){var y=new Date(now);y.setDate(y.getDate()-1);return{from:fmt(y),to:fmt(y)};}
|
||||
if(currentRange==='7days'){var d7=new Date(now);d7.setDate(d7.getDate()-6);return{from:fmt(d7),to:fmt(now)};}
|
||||
if(currentRange==='30days'){var d30=new Date(now);d30.setDate(d30.getDate()-29);return{from:fmt(d30),to:fmt(now)};}
|
||||
return{from:document.getElementById('dateFrom').value,to:document.getElementById('dateTo').value};
|
||||
}
|
||||
|
||||
function loadReport(){
|
||||
var r=getDateRange();
|
||||
if(!r.from||!r.to)return;
|
||||
fetch('/api/user-report?from='+r.from+'&to='+r.to)
|
||||
.then(function(res){return res.json();})
|
||||
.then(function(d){
|
||||
if(!d.success)return;
|
||||
var body=document.getElementById('reportBody');
|
||||
var sumBet=0,sumFlow=0,sumWL=0;
|
||||
if(!d.data||!d.data.length){
|
||||
body.innerHTML='<tr><td colspan="6" class="empty-state"><?=$t('no_data')?></td></tr>';
|
||||
document.getElementById('sumBet').textContent='0.00';
|
||||
document.getElementById('sumFlow').textContent='0.00';
|
||||
document.getElementById('sumWinLoss').textContent='0.00';
|
||||
return;
|
||||
}
|
||||
var typeMap={bs:'<?=$t('big')?>/<?=$t('small')?>',oe:'<?=$t('odd')?>/<?=$t('even')?>',dt:'<?=$t('dragon')?>/<?=$t('tiger')?>',rank:'<?=$t('rank_1_10')?>',sum:'<?=$t('sum')?>',sum_bs:'<?=$t('sum_bs_tab')?>'};
|
||||
var html='';
|
||||
d.data.forEach(function(row){
|
||||
var wl=parseFloat(row.win_loss);
|
||||
var typeName=typeMap[row.type]||row.type;
|
||||
sumBet+=parseFloat(row.bet_count);
|
||||
sumFlow+=parseFloat(row.effective_flow);
|
||||
sumWL+=wl;
|
||||
html+='<tr><td>'+typeName+'</td><td>'+row.bet_count+'</td><td>'+parseFloat(row.amount).toFixed(2)+'</td><td>'+parseFloat(row.effective_flow).toFixed(2)+'</td><td>'+parseFloat(row.rebate).toFixed(2)+'</td><td class="'+(wl>=0?'win':'lose')+'">'+wl.toFixed(2)+'</td></tr>';
|
||||
});
|
||||
body.innerHTML=html;
|
||||
document.getElementById('sumBet').textContent=sumBet;
|
||||
document.getElementById('sumFlow').textContent=sumFlow.toFixed(2);
|
||||
var wlEl=document.getElementById('sumWinLoss');
|
||||
wlEl.textContent=sumWL.toFixed(2);
|
||||
wlEl.style.color=sumWL>=0?'#4CAF50':'#FF4444';
|
||||
});
|
||||
}
|
||||
loadReport();
|
||||
</script>
|
||||
</body></html>
|
||||
Reference in New Issue
Block a user